线程
一、线程概述
1.线程和进程
进程是系统进行资源分配和调度的一个独立单位。
特征:独立性(独立资源,私有地址空间,不可未经允许访问其他进程地址空间),动态性(具有生命周期和状态),并发性(多处理器并发执行而不受影响)。
并发性(concurrency)和并行性(parallel)是两个概念,并行指在同一时刻,有多条指令在多个处理器上同时执行;并发指在同一时刻只能有一条指令执行,但多个进程指令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。
线程(Thread)也被称作轻量级进程(Lightweight Process),线程是进程的执行单元。当进程被初始化后,主线程就被创建了。每个线程也是互相独立的。线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程。线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的全部资源。线程的执行是抢占式的,一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
一个程序运行后至少有一个进程,一个进程里可以包含多个线程,但至少要包含一个线程。
2.多线程的优势
多线程程序的并发性高,具有更高的性能,共享同一进程虚拟空间(进程代码段、进程的公有数据),利用这些共享的数据,线程很容易实现相互之间的通信。
当操作系统创建一个进程时,必须为该进程分配独立的内存空间,并分配大量的相关资源;但创建一个线程则简单得多,因此使用多线程来实现并发比使用多进程实现并发的性能要高得多。
优点:
线程之间共享内存非常容易。
系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多
Java语言内置了多线程功能支持,而不是单纯地作为底层操作系统的调度方式,从而简化了Java的多线程编程。
二、线程的创建和启动
Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
1.继承Thread类
(1)定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行体。
(2)创建Thread子类的实例,即创建了线程对象。
(3)调用线程对象的start()方法来启动该线程。
main()方法的方法体代表主线程的线程执行体。
Thread.currentThread():currentThread()是Thread类的静态方法,该方法总是返回当前正在执行的线程对象。
getName():该方法是Thread类的实例方法,该方法返回调用该方法的线程名字。
可以通过 setName(String name)方法为线程设置名字,默认为main、Thread-0、Thread-1、Thread-2、…、Thread-n等。
使用继承 Thread 类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量。
2.实现Runnable接口
(1)定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
(2)创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象。
new Thread(st);
new Thread(st,"新线程");
Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
(3)调用线程对象的start()方法来启动该线程。
通过继承Thread 类来获得当前线程对象比较简单,直接使用this就可以了;但通过实现Runnable接口来获得当前线程对象,则必须使用Thread.currentThread()方法。
前者直接创建的Thread子类即可代表线程对象;后者创建的Runnable对象只能作为线程对象的target。
采用Runnable接口的方式创建的多个线程可以共享线程类的实例属性。
3.Callable和Future
Java5提供Callable接口,提供了一个call()方法可以作为线程执行体。
call()方法可以有返回值。
call()方法可以声明抛出异常。
Java 5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口——可以作为Thread类的target。
boolean cancel(boolean mayInterruptIfRunning):试图取消该Future里关联的Callable任务。
V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值。
V get(long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值。该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后Callable任务依然没有返回值,将会抛出TimeoutException异常。
boolean isCancelled():如果在Callable任务正常完成前被取消,则返回 true。
boolean isDone():如果Callable任务已完成,则返回true。
Callable接口有泛型限制
(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,且该call()方法有返回值。
(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。
(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
(1)
public class ThirdThread implements Callable<Integer>{
……
(2)
ThirdThread rt = new ThirdThread();
FutureTask<Integer> task = new FutureTask<>(rt);
(3)
new Thread(task,"有返回值的线程").start();
(4)
task.get();
}
4.创建线程三种方式对比
Runnable、Callable【推荐】
可以继承其他类、共享同一个 target 对象
访问当前线程,则必须使用Thread.currentThread()方法。
继承Thread类
不能再继承其他父类
使用this即可获得当前线程
三、线程的生命周期
新建(New)、就绪(Runnable)、运行(Running)、阻塞(Blocked)和死亡(Dead)5种状态。
1.新建和就绪状态
调用start()方法之后,该线程处于就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,
调用start()方法来启动线程,系统会把该run()方法当成线程执行体来处理;但如果直接调用线程对象的run()方法,则run()方法立即就会被执行,而且在run()方法返回之前其他线程无法并发执行——也就是说,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,而不是线程执行体。
如果直接调用线程对象的run()方法,则 run()方法里不能直接通过 getName()方法来获得当前执行线程的名字,而是需要使用Thread.currentThread()方法先获得当前线程,再调用线程对象的 getName()方法来获得线程的名字
只能对处于新建状态的线程调用 start()方法,否则将引发IllegalThreadStateException异常。
如果希望调用子线程的start()方法后子线程立即开始执行,程序可以使用Thread.sleep (1)来让当前运行的线程(主线程)睡眠1毫秒。
2.运行和阻塞状态
运行:如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
阻塞:
线程调用sleep()方法主动放弃所占用的处理器资源。
线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞。
线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。
线程在等待某个通知(notify)。
程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法。
解除阻塞:
调用sleep()方法的线程经过了指定时间。
线程调用的阻塞式IO方法已经返回。
线程成功地获得了试图取得的同步监视器。
线程正在等待某个通知时,其他线程发出了一个通知。
处于挂起状态的线程被调用了resume()恢复方法。
3.线程死亡
run()或call()方法执行完成,线程正常结束。
线程抛出一个未捕获的 Exception或Error。
直接调用该线程的stop()方法来结束该线程——该方法容易导致死锁,通常不推荐使用。
【正常老死、突发意外死亡、死神来了】
四、控制线程
1.join线程
让一个线程等待另一个线程完成
join():等待被join的线程执行完成。
join(long millis):等待被join的线程的时间最长为millis毫秒。如果在millis毫秒内被join的线程还没有执行结束,则不再等待。
join(long millis,int nanos):等待被join的线程的时间最长为millis毫秒加nanos毫微秒。【不用--用不到+无法精确】
2.后台线程
为其他线程提供服务的线程,守护线程、精灵线程。典型:JVM垃圾回收线程。
如果所有前台线程死亡,后台线程会自动死亡。
Thread对象的setDaemon(true)方法可将指定线程设置成后台线程
Thread类还提供了一个isDaemon()方法,用于判断指定线程是否为后台线程。
前台线程创建的子线程默认是前台线程,后台线程创建的子线程默认是后台线程。
setDaemon(true)必须在start()方法之前调用,否则会引发IllegalThreadStateException异常。
3.线程睡眠:sleep()
static void sleep(long millis):让当前正在执行的线程暂停millis毫秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
static void sleep(long millis,int nanos):让当前正在执行的线程暂停millis毫秒加nanos毫微秒,并进入阻塞状态,该方法受到系统计时器和线程调度器的精度与准确度的影响。
4.线程让步:yield
它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态。
thread1.setPriority(Thread.MAX_PRIORITY);设置线程thread1为最高优先级
sleep和yield的区别
sleep()方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程的优先级;但yield()方法只会给优先级相同,或优先级更高的线程执行机会。
sleep()方法会将线程转入阻塞状态,直到经过阻塞时间才会转入就绪状态;而yield()不会将线程转入阻塞状态,它只是强制当前线程进入就绪状态。因此完全有可能某个线程调用yield()方法暂停之后,立即再次获得处理器资源被执行。
sleep()方法声明抛出了 InterruptedException 异常,所以调用 sleep()方法时要么捕捉该异常,要么显式声明抛出该异常;而yield()方法则没有声明抛出任何异常。
sleep()方法比 yield()方法有更好的可移植性,通常不建议使用 yield()方法来控制并发线程的执行。
5.改变线程优先级
每个线程默认的优先级都与创建它的父线程的优先级相同
Thread类提供了setPriority(int newPriority)、getPriority()方法,其中setPriority()方法的参数可以是一个整数,范围是1~10之间,也可以使用Thread类的如下3个静态常量。
MAX_PRIORITY:其值是10。
MIN_PRIORITY:其值是1。
NORM_PRIORITY:其值是5。
Java提供了10个优先级级别,但这些优先级级别需要操作系统的支持,不同操作系统上的优先级并不相同,使用MAX_PRIORITY、MIN_PRIORITY和NORM_PRIORITY三个静态常量来设置优先级能保证可移植性。
五、线程同步
1.线程安全问题
取钱问题:线程调度具有不确定性
2.同步代码块
obj:同步监视器
synchronized(obj) { ... //此处的代码就是同步代码块 }
任何时刻只能有一个线程可以获得对同步监视器的锁定,同步代码块执行完毕后解锁。
推荐使用可能被并发访问的共享资源充当同步监视器。
加锁→修改→释放锁”
保证并发线程在任一时刻只有一个线程可以进入修改共享资源的代码区(也被称为临界区)
3.同步方法
使用synchronized关键字来修饰某个方法,则该方法称为同步方法。
无须显式指定同步监视器,同步方法的同步监视器是this,也就是该对象本身。
线程安全的类具有如下特征。
该类的对象可以被多个线程安全地访问。
每个线程调用该对象的任意方法之后都将得到正确结果。
每个线程调用该对象的任意方法之后,该对象状态依然保持合理状态。
Domain Driven Design(领域驱动设计,DDD),这种方式认为每个类都应该是完备的领域对象,例如Account代表用户账户,应该提供用户账户的相关方法;通过draw()方法来执行取钱操作(实际上还应该提供transfer()等方法来完成转账等操作),而不是直接将setBalance()方法暴露出来任人操作,这样才可以更好地保证Account对象的完整性和一致性。
可变类的线程安全是以降低程序的运行效率作为代价的。
不要对线程安全类的所有方法都进行同步,只对那些会改变竞争资源(竞争资源也就是共享资源)的方法进行同步。例如上面Account类中的accountNo属性就无须同步,所以程序只对draw()方法进行了同步控制。
如果可变类有两种运行环境:单线程环境和多线程环境,则应该为该可变类提供两种版本,即线程不安全版本和线程安全版本。在单线程环境中使用线程不安全版本以保证性能,在多线程环境中使用线程安全版本。
例如:单线程:StringBuilder 多线程:StringBuffer
4.释放同步监视器的锁定
释放情况:
当前线程的同步方法、同步代码块执行结束,当前线程即释放同步监视器。
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、该方法的继续执行,当前线程将会释放同步监视器。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致了该代码块、该方法异常结束时,当前线程将会释放同步监视器。
当前线程执行同步代码块或同步方法时,程序执行了同步监视器对象的wait()方法,则当前线程暂停,并释放同步监视器。
不会释放的情况:
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法来暂停当前线程的执行,当前线程不会释放同步监视器。
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程挂起,该线程不会释放同步监视器。当然,我们应该尽量避免使用suspend()和resume()方法来控制线程。
5.同步锁lock
Lock实现允许更灵活的结构,可以具有差别很大的属性,并且支持多个相关的Condition对象
每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
Lock、ReadWriteLock是Java 5新提供的两个根接口,并为Lock提供了ReentrantLock(可重入锁)实现类;为ReadWriteLock提供了ReentrantReadWriteLock实现类
finally 块来确保在必要时释放锁
Lock和同步方法类似,Lock显式使用Lock对象作为同步锁,同步方法隐式使用当前对象作为同步监视器。
同步方法或同步代码块使用与竞争资源相关的、隐式的同步监视器,并且强制要求加锁和释放锁要出现在一个块结构中,而且当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的范围内释放所有锁。
Lock 提供了同步方法和同步代码块所没有的其他功能,包括用于非块结构的 tryLock()方法,以及试图获取可中断锁的lockInterruptibly()方法,还有获取超时失效锁的tryLock(long,TimeUnit)方法。
ReentrantLock锁具有可重入性,也就是说,一个线程可以对已被加锁的ReentrantLock锁再次加锁,ReentrantLock对象会维持一个计数器来追踪lock()方法的嵌套调用,线程在每次调用lock()加锁后,必须显式调用unlock()来释放锁,所以一段被锁保护的代码可以调用另一个被相同锁保护的方法。
6.死锁
当两个线程相互等待对方释放同步监视器时就会发生死锁
由于Thread类的suspend()方法也很容易导致死锁,不推荐使用。
上面程序中A对象和B对象的方法都是同步方法,也就是A对象和B对象都是同步锁。程序中两个线程执行,一个线程的线程执行体是DeadLock类的run()方法,另一个线程的线程执行体是DeadLock的init()方法(主线程调用了init()方法)。其中run()方法中让B对象调用bar()方法,而init()方法让A对象调用foo()方法。图11.11显示init()方法先执行,调用了A对象的foo()方法,进入foo()方法之前,该线程对A对象加锁——当程序执行到①号代码时,主线程暂停200ms;CPU切换到执行另一个线程,让B对象执行bar()方法,所以看到副线程开始执行B实例的bar()方法,进入bar()方法之前,该线程对B对象加锁——当程序执行到②号代码时,副线程也暂停200ms;;接下来主线程会先醒过来,继续向下执行,直到③号代码处希望调用B对象的last()方法——执行该方法之前必须先对B对象加锁,但此时副线程正保持着 B 对象的锁,所以主线程阻塞;接下来副线程应该也醒过来了,继续向下执行,直到④号代码处希望调用A对象的last()方法——执行该方法之前必须先对A对象加锁,但此时主线程没有释放对A对象的锁——至此,就出现了主线程保持着A对象的锁,等待对B对象加锁,而副线程保持着B对象的锁,等待对A对象加锁,两个线程互相等待对方先释放,所以就出现了死锁。
A同步锁的执行体要调用B对象的方法,B同步锁的执行体要调用A对象的方法,通过sleep让A对象加锁后切换成B对象执行,在B对象加锁后切换成A对象执行。
6.线程通信
6.1传统
要求每当存款者将钱存入指定账户后,取钱者就立即取出该笔钱。不允许存款者连续两次存钱,也不允许取钱者连续两次取钱。
借助于Object类提供的wait()、notify()和notifyAll()3个方法
对于使用synchronized修饰的同步方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中直接调用这3个方法。
对于使用 synchronized 修饰的同步代码块,同步监视器是 synchronized 后括号里的对象,所以必须使用该对象调用这3个方法。
wait():导致当前线程等待,直到其他线程调用该同步监视器的 notify()方法或notifyAll()方法来唤醒该线程。无参数,带毫秒参数和带毫秒、毫微妙参数。
notify():唤醒在此同步监视器上等待的单个线程,[插图] notify():唤醒在此同步监视器上等待的单个线程
notifyAll():唤醒在此同步监视器上等待的所有线程。只有当前线程放弃对该同步监视器的锁定后,才可以执行被唤醒的线程。
取钱者线程已经执行结束,而存款者线程只是在等待其他线程来取钱而已,并不是等待其他线程释放同步监视器。不要把死锁和程序阻塞等同起来!
6.2Condition
使用Lock对象来同步时使用Condition,Condition将同步监视器方法(wait()、notify() 和notifyAll())分解成截然不同的对象,以便通过将这些对象与Lock 对象组合使用,为每个对象提供多个等待集(wait-set)。Lock 替代了同步方法或同步代码块,Condition替代了同步监视器的功能。
Condition实例被绑定在一个Lock对象上。要获得特定Lock实例的Condition实例,调用Lock对象的newCondition()方法即可。Condition类提供了如下3个方法。
await():类似于隐式同步监视器上的wait()方法,导致当前线程等待,直到其他线程调用该Condition的signal()方法或signalAll()方法来唤醒该线程。该await()方法有更多变体,如long awaitNanos(long nanosTimeout)、void awaitUninterruptibly()、awaitUntil(Date deadline)等,可以完成更丰富的等待操作。
signal():唤醒在此Lock对象上等待的单个线程。如果所有线程都在该Lock对象上等待,则会选择唤醒其中一个线程。选择是任意性的。只有当前线程放弃对该Lock对象的锁定后(使用await()方法),才可以执行被唤醒的线程。
signalAll():唤醒在此Lock对象上等待的所有线程。只有当前线程放弃对该Lock对象的锁定后,才可以执行被唤醒的线程。
6.3阻塞队列BlockingQueue
当生产者线程试图向BlockingQueue中放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue中取出元素时,如果该队列已空,则该线程被阻塞。
put(E e):放入E元素
take():头部取出元素
queue接口中的方法
在队列尾部插入元素。包括add(E e)、offer(E e)和put(E e)方法,当该队列已满时,这3个方法分别会抛出异常、返回false、阻塞队列。
在队列头部删除并返回删除的元素。包括 remove()、poll()和 take()方法。当该队列已空时,这3个方法分别会抛出异常、返回false、阻塞队列。
在队列头部取出但不删除元素。包括element()和peek()方法,当队列已空时,这两个方法分别抛出异常、返回false。
PriorityBlockingQueue:取出队列中最小的元素,可根据元素(实现 Comparable 接口)的本身大小来自然排序,也可使用 Comparator进行定制排序。
SynchronousQueue:同步队列。对该队列的存、取操作必须交替进行。
DelayQueue:底层基于PriorityBlockingQueue实现,实现Delay接口(该接口里只有一个long getDelay()方法),DelayQueue根据集合元素的getDalay()方法的返回值进行排序。
public class Producer extends Thread{
private BlockingQueue<String> bq;
public Producer(BlockingQueue<String> bq) {
this.bq = bq;
}
@Override
public void run() {
String[] str = new String[]{
"JAVA","STRING","HAHA"
};
for (int i = 0; i < 99999; i++) {
System.out.println(getName()+"生产者在准备生产集合元素");
try {
Thread.sleep(200);
bq.put(str[i%3]);
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(getName()+"生产完成"+bq);
}
}
}
static class Consumer extends Thread{
private BlockingQueue<String> bq;
public Consumer(BlockingQueue<String> bq) {
this.bq = bq;
}
@Override
public void run() {
while (true) {
System.out.println(getName() + "消费者准备消费集合元素");
try {
Thread.sleep(200);
bq.take();
} catch (InterruptedException e) {
e.printStackTrace();
System.out.println(getName()+"消费完成"+bq);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> bq = new ArrayBlockingQueue<>(1);
new Producer(bq).start();
new Producer(bq).start();
new Producer(bq).start();
new Consumer(bq).start();
}
}
线程组ThreadGroup和未处理的异常
线程组可对一批线程进行分类管理,没有显式指定的线程属于默认线程组,加入指定线程组后直至死亡不可更改所属线程组。
构造器-指定线程组:
Thread(ThreadGroup group,Runnable target):以target的run()方法作为线程执行体创建新线程,属于group线程组。
Thread(ThreadGroup group,Runnable target,String name):以target的run()方法作为线程执行体创建新线程,该线程属于group线程组,且线程名为name。
Thread(ThreadGroup group,String name):创建新线程,新线程名为name,属于group线程组。
getThreadGroup()方法来返回该线程所属的线程组,getThreadGroup()方法的返回值是 ThreadGroup 对象
构造器-创建实例:
ThreadGroup(String name):以指定的线程组名字来创建新的线程组。
ThreadGroup(ThreadGroup parent,String name):以指定的名字、指定的父线程组创建一个新线程组。
调用ThreadGroup的getName()方法来获取线程组的名字,但不允许改变线程组的名字。
常用方法:
int activeCount():返回此线程组中活动线程的数目。
interrupt():中断此线程组中的所有线程。
isDaemon():判断该线程组是否是后台线程组。
setDaemon(boolean daemon):把该线程组设置成后台线程组。后台线程组具有一个特征——当后台线程组的最后一个线程执行结束或最后一个线程被销毁后,后台线程组将自动销毁。
setMaxPriority(int pri):设置线程组的最高优先级。
void uncaughtException(Thread t,Throwable e),该方法可以处理该线程组内的任意线程所抛出的未处理异常。
从Java 5开始,Java加强了线程的异常处理,如果线程执行过程中抛出了一个未处理异常,JVM 在结束该线程之前会自动查找是否有对应的 Thread.UncaughtExceptionHandler对象,如果找到该处理器对象,则会调用该对象的uncaughtException(Thread t,Throwable e)方法来处理该异常。
设置异常处理器:
static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):为该线程类的所有线程实例设置默认的异常处理器。
setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh):为指定的线程实例设置异常处理器。
线程组处理异常的流程
(1)如果该线程组有父线程组,则调用父线程组的uncaughtException()方法来处理该异常。
(2)如果该线程实例所属的线程类有默认的异常处理器(由setDefaultUncaught ExceptionHandler()方法设置的异常处理器),那么就调用该异常处理器来处理该异常。
(3)如果该异常对象是ThreadDeath的对象,则不做任何处理;否则,将异常跟踪栈的信息打印到System.err错误输出流,并结束该线程。
【线程组异常处理:有爹用爹的-没爹用自己设置默认的-threadDeath的死了不管没死的登记一下打印输出】
当使用catch捕获异常时,异常不会向上传播给上一级调用者;但使用异常处理器对异常进行处理之后,异常依然会传播给上一级调用者。
【catch不打小报告,异常处理器会打小报告】
线程池
启动新线程由于涉及与OS交互成本高,启用线程池,与数据库连接池类似的是,线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象或Callable对象传给线程池,线程池就会启动一个线程来执行它们的run()或call()方法,当run()或call()方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run()或call()方法。大量并发线程会导致性能下降甚至JVM崩溃。
1.Java5实现的线程池
Executors工厂类
创建线程池方法
newCachedThreadPool():创建一个具有缓存功能的线程池,系统根据需要创建线程,这些线程将会被缓存在线程池中。
newFixedThreadPool(int nThreads):创建一个可重用的、具有固定线程数的线程池。
newSingleThreadExecutor():创建一个只有单线程的线程池,它相当于调用newFixedThread Pool()方法时传入参数为1。
返回ExecutorService对象,代表一个线程池,代表尽快执行的线程池。
Future<?> submit(Runnable task):将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务。其中Future对象代表Runnable任务的返回值——但run()方法没有返回值,所以Future对象将在run()方法执行结束后返回null。但可以调用Future的isDone()、isCancelled()方法来获得Runnable对象的执行状态。
<T>Future<T>submit(Runnable task,T result):将一个Runnable对象提交给指定的线程池,线程池将在有空闲线程时执行Runnable对象代表的任务。其中result显式指定线程执行结束后的返回值,所以Future对象将在run()方法执行结束后返回result。
<T> Future<T> submit(Callable<T> task):将一个Callable对象提交给指定的线程池,线程池将在有空闲线程时执行Callable对象代表的任务。其中Future代表Callable对象里call()方法的返回值。
newScheduledThreadPool(int corePoolSize):创建具有指定线程数的线程池,它可以在指定延迟后执行线程任务。corePoolSize指池中所保存的线程数,即使线程是空闲的也被保存在线程池内。
newSingleThreadScheduledExecutor():创建只有一个线程的线程池,它可以在指定延迟后执行线程任务。
返回一个ScheduledExceptionSerivice线程池,代表可在指定延迟后或周期性执行线程任务的线程池。
ScheduledFuture<V> schedule(Callable<V>callable, long delay, TimeUnit unit):指定callable任务将在delay延迟后执行。
ScheduledFuture<?> schedule(Runnable command,long delay, TimeUnit unit):指定command任务将在delay延迟后执行。
ScheduledFuture<?>scheduleAtFixedRate(Runnable command, long initialDelay, long period,TimeUnit unit):指定command任务将在delay延迟后执行,而且以设定频率重复执行。也就是说,在 initialDelay 后开始执行,依次在initialDelay+period、initialDelay+2*period…处重复执行,依此类推。
ScheduledFuture<?>scheduleWithFixedDelay(Runnable command, long initialDelay, long delay,TimeUnit unit):创建并执行一个在给定初始延迟后首次启用的定期操作,随后在每一次执行终止和下一次执行开始之间都存在给定的延迟。如果任务在任一次执行时遇到异常,就会取消后续执行;否则,只能通过程序来显式取消或终止该任务。
当用完一个线程池后,应该调用该线程池的shutdown()方法,不再接收新任务,但会将以前所有已提交任务执行完成。用线程池的shutdownNow()方法来关闭线程池,该方法试图停止所有正在执行的活动任务,暂停处理正在等待的任务,并返回等待执行的任务列表。
使用线程池来执行线程任务的步骤
(1)调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程
(2)创建Runnable实现类或Callable实现类的实例,作为线程执行任务。
(3)调用ExecutorService对象的submit()方法来提交Runnable实例或Callable实例。
(4)当不想提交任何任务时,调用ExecutorService对象的shutdown()方法来关闭线程池。
【用静态方法创建对象-创建线程-提交线程-关闭线程池】
2. Java 7新增的ForkJoinPool
ForkJoinPool来支持将一个任务拆分成多个“小任务”并行计算,再把多个“小任务”的结果合并成总的计算结果。ForkJoinPool是ExecutorService的实现类,因此是一种特殊的线程池。
构造器:
ForkJoinPool(int parallelism):创建一个包含parallelism个并行线程的ForkJoinPool。
ForkJoinPool():以Runtime.availableProcessors()方法的返回值作为parallelism参数来创建Fork JoinPool。
submit(ForkJoinTask task)
invoke(ForkJoinTask task)
ForkJoinTask代表一个可以并行、合并的任务
ForkJoinTask 是一个抽象类,它还有两个抽象子类:RecursiveAction 和RecursiveTask。其中RecursiveTask代表有返回值的任务,而RecursiveAction代表没有返回值的任务。
没返回值用action 有返回值用task
具体使用方法看不大懂
线程相关类
1.ThreadLocal类
JDK1.2提供的,5.0后引入泛型支持,可以简化多线程编程时的并发访问,使用这个工具类可简洁隔离多线程程序的竞争资源。
ThreadLocal,是 Thread Local Variable(线程局部变量)的意思,功能是为每一个使用该变量的线程都提供一个变量值的副本,使每一个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突。
T get():返回此线程局部变量中当前线程副本中的值。
void remove():删除此线程局部变量中当前线程的值。
void set(T value):设置此线程局部变量中当前线程副本中的值。
由于程序中的账户名是一个ThreadLocal变量,所以虽然程序中只有一个Account对象,但两个子线程将会产生两个账户名(主线程也持有一个账户名的副本)。
看不懂
普通同步机制加锁、同步方法、同步代码块都没有复制资源,采用安全机制控制资源访问,是为了同步多个线程对相同资源的并发访问,是多个线程之间进行通信的有效方式
ThreadLocal是把需要并发访问的资源复制多份,是为了隔离多个线程的数据共享,从根本上避免多个线程之间对共享资源(变量)的竞争。
如果多个线程之间需要共享资源,以达到线程之间的通信功能,就使用同步机制;如果仅仅需要隔离多个线程之间的共享冲突,则可以使用ThreadLocal。
2.包装线程不安全的集合
ArrayList、LinkedList、HashSet、TreeSet、HashMap、TreeMap线程不安全,使用Collections的静态方法
<T>Collection<T>synchronizedCollection(Collection<T>c):返回指定 collection 对应的线程安全的collection。
static<T>List<T>synchronizedList(List<T>list):返回指定List对象对应的线程安全的List对象。
static<K,V>Map<K,V>synchronizedMap(Map<K,V>m):返回指定Map对象对应的线程安全的Map对象。
static <T> Set<T> synchronizedSet(Set<T> s):返回指定Set对象对应的线程安全的Set对象。
static <K,V>SortedMap<K,V>synchronizedSortedMap(SortedMap<K,V> m):返回指定SortedMap对象对应的线程安全的SortedMap对象。
static <T> SortedSet<T>synchronizedSortedSet(SortedSet<T> s):返回指定SortedSet对象对应的线程安全的SortedSet对象。
如果需要把某个集合包装成线程安全的集合,则应该在创建之后立即包装。
HashMap m = Collections.synchronizedMap(new HashMap());
3.线程安全的集合类
java5开始,java.util.concurrent包提供
以 Concurrent 开头的集合类,如ConcurrentHashMap、ConcurrentSkipListMap、ConcurrentSkipListSet、ConcurrentLinkedQueue和ConcurrentLinkedDeque。
以CopyOnWrite开头的集合类,如CopyOnWriteArrayList、CopyOnWriteArraySet。
当多个线程共享访问一个公共集合时,使用ConcurrentLinkedQueue,不允许使用null元素,高效访问,访问无需等待。
在默认情况下,ConcurrentHashMap支持16个线程并发写入,设置 concurrencyLevel构造参数(默认值为16)可支持更多的并发写入线程。
因为ConcurrentLinkedQueue 和ConcurrentHashMap支持多线程并发访问,所以当使用迭代器来遍历集合元素时,该迭代器可能不能反映出创建迭代器之后所做的修改,但程序不会抛出任何异常。
CopyOnWriteArraySet的底层封装了CopyOnWriteArrayList,采用复制底层数组的方式来实现写操作。读取无需加锁/阻塞,性能较好,写入会复制一份新数组,对新数组写入,写入时性能较差,适用于缓存。