-
Java所使用的并发系统会共享
内存和I/O等资源,因此编写多线程程序的基本困难在于协调不同线程所驱动的任务之间对这些共享资源的使用,使得这些任务不会被同时被多个任务访问. -
Java线程的机制是
抢占式的,这表示调度机制会周期性地中断线程,将上下文切换到另一个线程,从而为每个线程都提供时间片,使每个线程都会分配到数量合理的时间去驱动他的任务. -
线程机制是一种建立透明的,可扩展的程序方法,为机器添加一个CPU就能很容易的加快程序运行速度.
-
Thread.yield(): 对线程调度器的一种建议,表示可以切换给其他任务执行一段时间.Thread.sleep()(旧方法)/TimeUnit.MILLISECONDS.sleep(): 使当前任务中止执行一段时间(线程阻塞).threadObj.join(): 挂起当前线程,等待threadObj线程执行结束才恢复.(可被interrupt()中断)Thread.currentThread(): 获得驱动该任务的Thread对象的引用.
- 建立实现
Runnable接口的任务并传入Thread构造器. - 直接继承
Thread.
- Executor(执行器)在客户端和任务执行之间提供了一个中介层,这个中介层将代替客户端执行任务,而无需程序员显示地管理线程的生命周期.
- 任务对象知道如何运行具体的任务,而ExecutorService(具有服务生命周期的Executor)知道如何构建恰当的上下文来执行Runnable对象.
Executor其实就是线程对象管理池,代替我们管理线程的生命周期,ExecutorService提供各种Executor.
-
-
FixedThreadPool: 使用有限(自己设定)的线程集来执行所提交的任务.(预先进行了线程的分配,驱动任务的时候就直接使用) -
CacheThreadPool: 在程序执行的过程中创建与所需数量相同的线程,然后在Executor回收旧线程时停止创建新线程.(在程序运行的过程中进行线程分配再驱动任务运行) -
SingleThreadPool: 就像是线程数量为1的FixedThreadPool.
-
- 静态的
ExecutorService创建方法可以接受一个ThreadFactory对象(用于定制线程优先级,是否后台,名称),Executor将用这个对象来创建进行. - 调用某个
ExecutorService的shutdownNow()时,它会调用所有由它控制的线程的interrupt().
- 实现
Callable接口而不是Runnable接口,并且要使用ExecutorService.submit()方法调用. submit()调用会产生Future对象,可以使用isDone()来查询任务是否完成,或者直接用get()获取结果,如果任务未完成,get()将阻塞直至结果就绪.
-
线程的
优先级将该线程的重要器传给Executor,Executor将倾向于让优先权最高的任务先执行. -
JDK有10个优先级,但由于操作系统的优先级多样性.在调整优先级的时候只使用:
MAX_PRIORITY,NORM_PRIORITY和MIN_PRIORITY三种级别.
-
在程序运行的时候在后台提供一种通用服务的线程,并且这种线程不属于程序中不可或缺的部分.当所有
非后台线程结束,会杀死进程中的所有后台线程. -
一个
后台线程创建的任何子线程都将被自动设为后台线程. -
后台线程存在不执行finally子句就退出的情况.
Java多线程程序中,所有线程都不允许抛出未经捕获的异常,所有的异常都要在自己线程内解决.(run()方法没有throws exception)
当是线程仍有可能抛出异常.当此类异常跑抛出时,线程就会终结,而对于主线程和其他线程完全不受影响,且完全感知不到某个线程抛出的异常(也是说完全无法catch到这个异常).
-
Thread对象提供的setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)方法.通过该方法给某个thread设置一个UncaughtExceptionHandler,可以确保在该线程出现异常时能通过回调UncaughtExceptionHandler接口的public void uncaughtException(Thread t, Throwable e)方法来处理异常,这样的好处是可以在线程代码边界之外(Thread的run()方法之外),有一个地方能处理未捕获异常。- 但是要特别明确的是:虽然是在
回调方法中处理异常,但这个回调方法在执行时依然还在抛出异常的这个线程中!另外还要特别说明一点:如果线程是通过线程池创建,线程异常发生时UncaughtExceptionHandler接口不一定会立即回调。
- 但是要特别明确的是:虽然是在
(待补充)
隐式锁:
-
所有对象都自动含有单一的锁(monitor),在对象上调用任意
synchronized方法的时候,对象都被加锁,此时其他该对象上其他synchronized方法只有等到前一个方法调用完毕并释放锁才能被调用. -
每个类对象都有一个锁,所以
synchronized static方法可以在类的范围内防止对static数据的并发访问.
显式锁:
-
Java类库中的显式
互斥机制,Lock对象必须被显式地创建,锁定和释放.与内建锁相比,代码缺乏优雅性,但对于某些问题的解决更加灵活. -
ReentrantLock允许尝试着获取一个锁,如果其他人已经获取了这个锁,可以选择离开去执行其他事情,而不是一直等待锁的释放.
- 一个任务可以多次获得对象的锁(锁的计数递增)
-
原子性可以应用于除long,double以外的所有基本类型之上的 "简单操作". -
JVM将64位的读取写入当做两个分离的32位操作来执行,产生了在
读取和写入操作中间发生上下文(线程)切换,导致不同任务看到不同结果的可能性(字撕裂). -
volatile关键字确保可视性.(修饰的域的修改立即写入主存,不进行任何读写优化) -
同步也会导致锁释放前向主存中刷新.
- JavaSE5引入了
AutomicInteger,AutomicLong,AutomicReference等特殊的原子性变量类,并提供原子性的更新操作.
-
synchronized创建临界区的方法:将
synchronized用来指定某个对象,此对象的锁被用来对花括号内的代码进行同步synchronized(obj){ // 这部分代码一次只能被一个线程访问 }
- 防止任务在共享资源上发生冲突的第二种方式是根除对变量的共享.
线程本地存储是一种自动化机制,可以为使用相同变量的每个不同的线程都创建不同的存储.通过ThreadLocal实现.
-
有两种形式的
wait():-
接收毫秒数作为参数,在一段时期内暂停(阻塞):
- 在这段时期内锁是释放的
- 可以通过
notify(),notifyAll()或者指令时间到期,恢复执行.
-
不接受任何参数,这时线程将无限执行下去,直至收到
notify()或notifyAll()消息
-
-
wait(),notify()和notifyAll()都是基类Object的一部分. -
只能在同步代码块里调用
wait(),notify()和notifyAll(). -
在进行协作时,信号量可能会丢失,从而导致线程无限阻塞.(
notify()/notifyAll()在wait()之前发生)
-
使用
notify()时,众多等待同个锁的任务中只有一个会唤醒,要保证唤醒的是恰当的任务. -
notifyAll()只唤醒所有等待这个锁的任务.
通过在Condition上调用await()来挂起一个任务.
当外部条件变化,某个任务需要执行时,通过调用signal()唤醒一个任务或signalAll()唤醒在这个Condition上被其挂起的任务.
-
与使用
notifyAll()相比,signalAll()是更安全的方式:notifyAll()唤醒所有在此对象上的等待synchronized锁的任务;signalAll()唤醒被Condition挂起的任务,控制粒度更细. -
每个
lock()的调用都必须紧跟一个try-finally子句,保证所有情况下都能释放锁.
更高的抽象级别解决线程协作,是使用同步队列,同步队列在任何时刻都只允许一个任务插入或移除元素.
LinkedListBlockingQueue: 无界同步队列.ArrayBlockingQueue: 指定尺寸的同步队列.
通过输入/输出在线程间进行通讯,Java输入/输出类库中的对应物是PipedWriter类(允许任务向管道写)和PipedReader类(允许不同任务从同一管道读取).这个"管道"基本上是一个阻塞队列.
某个任务在等待另一个任务,后者又在等待其他的任务,这样一直下去,直到这个链条上的任务又在等待第一个任务释放锁.任务间循环相互等待,没有哪个线程能继续,称之为死锁.
- 互斥条件.
- 至少有一个任务,它持有一个资源且正在等待获取一个被别的任务持有的资源.
- 资源不能被抢占.
- 有循环等待.
- Java并没有提供语言层面上的支持来避免死锁.要防止
死锁的发生,只需破坏上述条件之一.
- 向
CountDownLatch对象设置一个初始计数值,任何在这个对象上调用await()的方法都将阻塞,直至计数值达到0. - 其他任务在结束其工作时,可以在该对象上调用
countDown()来减小这个计数值.
CountDownLatch被设计为只能触发一次,计数值不能重置.
- 可以向
CyclicBarrier提供一个Runnable的"栅栏动作",当计数值达到0时自动执行.
ScheduleThreadPoolExecutor提供了任务的定时执行功能,通过使用schedule()(运行一次)或者scheduleAtFixedRate()(每隔规则的时间重复执行),将Runnable对象设置为在将来的某个时刻执行.
(待补充)