多线程
-
线程与进程
操作系统为进程分配系统资源,进程中的线程共享这些资源。线程是系统分配时间片的接受者。
-
创建线程
如果需要执行一个耗时的任务,就应该使用独立的线程。下面是一个单独的线程执行一个简单任务的过程:
- 将任务代码移到实现了
Runnable
接口的类的run
方法中。
//该接口只有一个run方法 public interface Runnable{ void run(); }
实现一个类:
class MyRunnable implements Runnable{ public void run(){ do something...; } }
- 创建一个类对象
Runnable r = new MyRunnable();
- 由
Runnable
创建一个Thread
对象
Thread t - new Thread(r);
- 启动线程
t.start();
也可以构建一个
Thread
的子类定义一个线程:class MyThread extends Thread{ public void run(){ do something...; } }
然而,并不推荐这样的实现。如果任务很多的话,这样做将会创建非常多的并行线程,创建线程的代价是很大的。使用实现
Runnable
接口的方式,我们就可以使用线程池,减少不必要的线程数量。不要调用
Thread
类或者是Runnable
类的run
方法。调用run
方法,只会执行同一线程中的任务,而不会启动新线程。 - 将任务代码移到实现了
-
中断线程
调用
Thread
对象的interrupt
方法, 可以用来请求终止线程。package com.heisenberg.test; import java.util.logging.Logger; public class Chapter14{ public static void main(String[] args){ Runnable r1 = new MyRunnable("thread1"); Thread t1 = new Thread(r1); Runnable r2 = new MyRunnable("thread2"); Thread t2 = new Thread(r2); t1.start(); t2.start(); try{ Thread.sleep(100); t1.interrupt(); Thread.sleep(500); t2.interrupt(); }catch(InterruptedException e){ } } } class MyRunnable implements Runnable{ public static final Logger logger = Logger.getLogger("com.heisenberg.test"); private String name; public MyRunnable(String name){ this.name = name; } public void run(){ int i = 0; try{ while(!Thread.currentThread().isInterrupted()){ i++; /* *在每次工作迭代之后调用sleep方法,isInterrupted检测将没有作用 *因为当中断位被置位后,调用slepp方法线程不会休眠还会清楚该状态且抛出InterruptedException, *因此,在代码什么位置捕获该异常就非常重要了 *在循环内捕获,线程将不会中断 *在循环外铺货,线程会中断 */ Thread.sleep(200); if(Thread.currentThread().isInterrupted()){ logger.info("isInterrupted is Ture"); } } }catch(InterruptedException e){ logger.severe("InterruptedException happend"); if(!Thread.currentThread().isInterrupted()){ logger.info("isInterrupted is False"); } } logger.info("Thread:" + name + " runs "+ i + " times"); } }
通过
Thread
的interrupt
方法申请中断线程,在线程的run
方法内通过isInterrupted
检测中断位,如此配合来起到中断线程的作用。 -
线程状态
线程可以6种状态:
- 新创建(New)
- 可运行(Runnable)
- 被阻塞(Blocked)
- 等待(Waiting)
- 计时等待(Timed waiting)
- 被终止(Terminated)
Runnable r = new MyRunnable(); Thread t = new Thread(r);//新创建状态 t.start();//可运行状态 //可运行状态的线程可能正在运行,也可能没有运行,这取决于是否获得时间片 //若此是线程t试图获取一个内部的对象锁,而该锁被其它线程持有,该线程进入阻塞状态。 //若此时线程等待另一个线程通知调度器一个条件时,它自己进入等待状态。 t.sleep(100);//线程t现在处于计时等待状态。还有几个类似sleep的方法。 //当t线程的run方法正常退出而自然死亡,或因一个没有捕获异常终止了run的而意外死亡,此时t处于被终止状态。
-
线程优先级
Java程序设计语言中,每个线程都有一个优先级,子线程默认继承父线程的优先级。优先级从
Thread.MIN_PRIORITY
(1)到Thread.MXA_PRIORITY
(10)之间的任意值。使用setPriority()
为线程设置优先级。 -
守护线程
守护线程的唯一作用就是为用户线程提供服务,例如计时线程或清空过时缓存的线程。使用
setDaemon(true)
方法,将线程设置为守护线程。这个语句必须在start
方法被调用之前执行。 -
未捕获异常处理器
线程的
run
方法不能抛出任何的已检查异常。未检测异常会导致线程终止,但是在线程死亡之前,异常被传递到一个用于未捕获异常的处理器。该处理器实现Thread.UncaughtExceptionHandler
接口的类,该接口只有一个方法:void uncaughtException(Thread t, Throwable e)
可以用
setUncaughtExceptionHandler
方法为任何线程安装一个处理器:Thread t = new Thread(r); t.setUncaughtExceptionHandler(new UncaughtExceptionHandler(){ public void uncaughtException(Thread t, Throwable e){ ...; } }); t.start();
可以使用静态方法
Thread.setDefaultUnCaughtExceptionHandler()
为所有的线程设置一个默认的处理器。如果安装默认的处理器,默认的处理器为空。如果不为独立的线程安装处理器,那么这个线程的处理器就是该线程的ThreadGroup
对象。ThreadGroup
类是想了Thread.UncauthghtException
接口。它的uncaughtException
方法处理流程如下:- 如果该线程组有父线程组,调用父线程组的
uncaughtException
; - 否则,如果
Thread.getDefualtUncaughtExcetionHandler
方法返回一个非空处理器,则调用该该处理器的uncaughtException
; - 否则,如果
Throwable
是ThreadDeath
的实例(当调用线程的stop
方法时,线程终止,且抛出ThreadDeath
异常),什么都不做; - 否则,线程的名字以及
Throwable
的栈踪迹被输出到System.err
上。
- 如果该线程组有父线程组,调用父线程组的
-
同步-锁对象
ReentrantLock
类为临界区加锁与释放锁。Lock lock = new ReentrantLock(); lock.lock(); try{ //临界区 ... }finally{ lock.unlock(); }
-
同步-条件锁
锁对象实现临界资源的互斥访问,条件锁实现临界资源的同步访问。
使用Condition
类为一个条件添加锁。- 当一个线程不满足某个条件时,调用条件锁对象的
await
方法,使得该线程放弃资源锁,并进入该条件的等待集,进入阻塞状态。 - 当另一个线程进入临界资源,执行了一些可能会导致其它线程满足条件的操作后,调用该条件锁对象的
signalAll
方法,通知所有在该条件等待集中的线程,使得这些线程进入可运行状态。 - 其中一个线程获得时间片后,从上次阻塞的地方重新执行,再次检查是否满足条件,满足便执行下去,不满足便再次进入等待集。
也可以调用
signal
方法,随机通知一个等待集中的线程进入可运行状态,但是这样做风险很高,更容易造成死锁。Lock lock = new ReentrantLock(); Condition condition = new Condition(); lock.lock(); try{ if (!(ok to proceed)){ condition.await(); } //临界区 ... condition.signalAll(); }finally{ lock.unlock(); }
- 当一个线程不满足某个条件时,调用条件锁对象的
内部方法锁
Java的每个对象都有一个内部锁,使用synchronized
关键字来声明一个方法,那么该对象的内部锁将保护这个方法。一个内有多个方法被synchronized
声明,那么并行调用同一个对象的多个方法时,不同的方法之间竞争同一个内部锁。
public synchronized void method(){
method body;
}
内部锁只有一个相关条件。wait
方法将线程添加到等待集,notifyAll
或者notify
方法解除等待线程的阻塞状态。
public synchronized void method(){
method body
if(!(ok to proceed)){
wait();
}
method body
notifyAll();
}
Lock/Condition
对象还是同步方法(内部锁)?
- 最好两者都不使用,在通常情况下,最好使用
java.util.concurrent
包中的机制,它会处理所有的加锁。 - 如果
synchronized
关键字适合你的程序,大胆的使用。 - 如果特别需要
Lock/Condition
结构提供的独有特性时,才使用它。
- 同步阻塞
synchronized(obj){ //获得了obj对象的锁
//对obj对象的操作
}
或:
Object lock1 = new Object();
Object lock2 = new Object();
synchronized(lock1){//获得第一个锁
critical section
}
...
synchronized(lock2){//获得第二个锁
critical section
}
上面的Object对象仅仅是用来充当代码块的锁。
我们可以使用一个对象的锁来实现原子操作:
//下面两行代码应该是一个原子操作,但是现在不是
from -= amount;
to += amount;
//使用一个对象锁,使该代码块成为一个原子操作
Object lock = new Object();
synchronized(lock){
from -= amount;
to += amount;
}
-
Volatile域
Volatile
关键字为实例域的同步访问提供了一种免锁机制。private volatile boolean done; public boolean isDone(){return done} public void setDone(){done=true}
Volaatile
域不能提供原子性。 -
线程局部变量
使用
ThreadLocal
辅助类为各个线程提供各自的实例,来避免共享变量。//为每个线程都实例化一个随机数生成器 public static final ThreadLocal<Random> random = new ThreadLocal<Random>(){ protected Random initialValue(){ return new Random(); } }; //第一次调用get时,会调用initialValue方法,以后将返回特定于该线程的Radom实例 random.get().nextInt();
-
锁测试与超时
线程在调用
lock
方法来获得另外一个线程所持有的锁是,很可能发生阻塞。应该更加谨慎的申请锁。tryLock
方法试图申请一个锁,在成功获得锁后返回true
,否则立即返回false
,而线程可以立即离开去做其它事情:if (myLock.tryLock()){ //申请到了锁 }else{ //没有申请到锁 }
在调用`tryLock`方法时,可以使用超时参数:
```java
/*
*尝试获得锁,阻塞不会超过给定的时间
*TimeUnit是一个枚举类型,可以取的值包括:SECONDS、MILLISECONDS、MICROSECONDS和NANOSECONDS
*/
if (myLock.lock(100,TimeUnit.MILLISECONDS)){
...
}else{
...
}
-
读/写锁
我们可以使用
ReentrantLock
为任何的临界区加锁,现在,我们可以使用ReentrantReadWriteLock
为读数据临界区加读锁,为写数据临界区加写锁//构造一个ReentrantReadWriteLock对象 ReentrantReadWriteLock rwl = new ReentrantReadWriteLock(); //抽取读锁和写锁 private Lock readLock = rwl.readLock(); private Lock writeLock = rwl.writeLock(); //对所有的读去方法加读锁 public double getTotalBalance(){ readLock.lock(); try{ ... }finally{ readLock.unlock(); } } //对所有的写方法加写锁 public void transfer(){ writeLock.lock(); try{ ... }finally{ writeLock.unlock(); } }
-
阻塞队列
生产这线程向队列插入数据,消费者线程向队列取数据。使用队列,可以安全地从一个线程向另一个线程传递数据。
阻塞队列方法:
阻塞队列分类方法 正常动作 特殊情况下动作 add 添加一个元素 如果队列满,则抛出 IllegalStateException
element 返回队列的头元素 如果队列空,抛出 NoSuchElementException
offer 添加一个元素并返回 true
如果队列满,则返回 false
peek 返回队列的头元素 如果队列空,则返回 null
poll 移除 并返回队列头元素 如果队列空,则返回 null
put 添加一个元素 如果队列满,则阻塞 remove 移除 并返回头元素 如果队列空,则抛出 NoSuchElementException
take 移除 并返回头元素 如果队列空,则阻塞 如果要将阻塞队列作为线程管理工具来使用,则要使用
put
和teak
方法。 -
线程安全的集合
java.util.concurrent
包提供了一下线程安全的集合类线程安全的集合类具体使用参考
java.util.concurrent
包的AIP -
Callable
与Future
Runable
封装了一个异步方法,可以把它想成是没有返回参数的异步方法。Callable
与Runable
类似,但是又返回值。Callable
接口是一个参数化的类型,只有一个方法call
:public interface Callable<V>{ V call() throws Exception; }
Future
保存异步计算的结果。可以启动一个计算,将Future
对象交给某个线程。Future
接口有一下方法:public interface Future<V>{ //阻塞,直到计算完成;计算过程中被中断,抛出InterruptedException V get() throws ...; //调用超时,抛出TimeoutException异常;计算过程中被中断,抛出InterruptedException V get(long timeout, TimeUnit unit) throws ...; //取消计算 void cancle(boolean mayInterrrupt); boolean isCancelled(); //获取计算执行状态 boolean isDone(); }
FutureTask
包装器是一个非常便利的机制,它将Callable
转换为Future
和Runable
,因为它实现了这两个接口。Callable<Integer> call = new myCallable(); FuterTask<Integer> task = new FuterTask(call); Thread t = new Thread(task); //此时task是一个Callable t.start(); Integer result = t.get();//此时task是一个Future
通过上面的方式来使用
Futer
和Callable
,以获取异步计算结果。 -
执行器
如果程序中创建了大量生命周期很短的线程,就应该使用线程池。使用线程池可以减少并发线程的数目。
执行器(Executor) 类有许多静态工厂方法用来创建线程池。下面是对这些方法的汇总:
方法 描述 newCachedThreadPool 必要时创建新线程,空闲线程保留60秒 newFixedThreadPool 该池保留固定数量的线程,空闲线程会被一直保留 newSingleThreadExecutor 只有一个线程的“池”,该线程顺序执行提交的任务 newScheduledThreadPool 用于预定执行而构建的固定线程池,替代 java.util.Timer
newSingleThreadScheduledExecutor 用于预定执行而构架的单线程“池” newCachedThreadPool
、newFixedThreadPool
和newSingleThreadExecutor
三个静态方法都会返回一个实现了ExecutorService
接口的ThreadPoolExecutor
,ExecutorService
接口主要有一下几个方法:public interface ExecutorService extends Executor{ //提交一个Callable对象,返回的Future对象将在计算结果准备好时得到 <T> Future<T> submit(Callable<T> task); //提交一个Runnable对象,并且Future的get方法在完成时返回给result对象 <T> Future<T> submit(Runnable task, T result); //返回一个Futre<?>对象,可以使用这个对象来调用isDone,cancel,isCancelled方法,但是get方法只会在完成时返回一个null Future<?> submit(Runnable task); //该方法启动该池的关闭序列,被关闭的执行器接收新的任务;当所有任务都完成后,所有线程死亡 void shutdown(); }
预订执行:
newScheduledThreadPool
和newSingleThreadScheduledExecutor
方法返回实现了ScheduledExecutorService
接口的对象。ScheduledExecutorService
接口具有为预定执行(Scheduled Execution)或重复执行任务而设计的方法。public interface ScheduledExecutorService extends ExecutorService{ //预定在指定时间后执行指定任务 <V> ScheduledFuture<V> schedule(Callable<V> callable, long delay, TimeUnit unit); //预定在指定时间后执行指定任务 ScheduledFuture<?> schedule(Runnable command, long delay, TimeUnit unit); //预定在初始的延迟结束后,周期性的运行指定的任务,周期长队为period ScheduledFuture<?> scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit); //预定在初始延迟结束后周期性的执行指定任务,在一次调用完成后和下一次调用开始之间有长度为delay的延迟 ScheduledFuture<?> scheduleWithFixedDelay(Runnable command, long initialDelay, long delay, TimeUnit unit); }
-
fork-join框架
- 提供扩展了
RecursiveTask<T>
(有计算结果)或者RecursiceAction
(无计算结果)的类。 - 覆盖
comput
方法来生成并调用子任务 - 将结果合并
public class ForkJoinTest(){ public void main(String[] args){ final int SIZE = 10000000; double[] numbers = new double[SIZE]; for(int i = 0; i < SIZE; i++){ numbers[i] = Math.random(); } Counter counter = new Counter(0,SIZE,numbers); ForkJoinPool pool = new ForkJoinPool(); pool.invoke(counter); } } class Counter extends RecursiveTask<Integer>{ private int from,to; private double[] values; public static final int THRESHOLD = 1000; public Counter(int from,int to,double[] values){ this.from = from; this.to = to; this.values = values; } protected Integer comput(){ if (to - from < THRESHOLD){ int count = 0; for (int i = from; i < to; i++){ if (values[i] > 0.5){ count++; } } return count; }else{ int mid = (to+from)/2; Counter counter1 = new Counter(from,mid,values); Counter counter2 = new Counter(mid,to,values); invokeAll(counter1,counter2); return counter1.join() + counter2.join(); } } }
- 提供扩展了