一、终结任务
1、在阻塞中终结
线程的状态
进入阻塞状态
1、调用sleep()是任务进入休眠状态
2、通过wait()是线程挂起,直到被notify()或notifyAll()唤醒后进入就绪状态。
3、任务在等待输入/输出完成
4、任务试图在某个对象上调用其同步控制方法,但是对象锁不可用,因为另一个任务已经获取了这个锁。
2、中断
- Thread类包含interrupt()方法,设置线程的中断状态,可以终止被阻塞的任务。
- 在Executor上调用shutdownNow(),它将发送一个interrupt()调用给它启动的所有线程。
- 如果只是想中断某个单一的任务,可以在使用Executor时调用submit(),就可以拥有该任务的上下文,它将返回一个泛型Future
class SleepTest implements Runnable{
@Override
public void run() {
System.out.println("SleepTest start");
try {
TimeUnit.SECONDS.sleep(100);
} catch (InterruptedException e) {
System.out.println("SleepTest interrupted");
}
}
}
class IOBlocked implements Runnable{
//试图执行I/O操作
private InputStream in;
public IOBlocked(InputStream is) {
in = is;
}
@Override
public void run() {
try {
System.out.println("IOBlocked start");
in.read();
} catch (IOException e) {
if(Thread.currentThread().isInterrupted()){
System.out.println("IO interrupted");
}else{
throw new RuntimeException();
}
}
}
}
class SynTest implements Runnable{
public synchronized void syn(){
while (true) {
Thread.yield();//不会释放锁
}
}
public SynTest() {
new Thread(new Runnable() {
//需要新开一个线程,因为一个线程可以多次获得某个对象锁
@Override
public void run() {
syn();//试图获得锁
}
}).start();
}
@Override
public void run() {
System.out.println("synTest start");
syn();//阻塞
System.out.println("synTest end");
}
}
public class Test1 {
public static void main(String[] args) throws InterruptedException {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new SynTest());
exec.execute(new SleepTest());
exec.execute(new IOBlocked(System.in));
TimeUnit.SECONDS.sleep(1);
exec.shutdownNow();
}
}
输出:
synTest start
SleepTest start
IOBlocked start
SleepTest interrupted
I/O和在synchronized块上的等待是不可中断的。
I/O中断
关闭任务在其上发生阻塞的底层资源
scoketInput.close();
System.in.close();
被互斥所阻塞
ReentrantLock上阻塞的任务具备可以被中断的能力
class SynBlocked{
private Lock lock = new ReentrantLock();
public SynBlocked() {
lock.lock();//不释放锁
System.out.println("locked");
}
public void releaseLock(){
try {
lock.lockInterruptibly();
System.out.println("lock acquired");
} catch (InterruptedException e) {
System.out.println("lock released");
}
}
}
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
SynBlocked synBlocked = new SynBlocked();
@Override
public void run() {
synBlocked.releaseLock();//被阻塞
//不同于创建SynBlocked的对象
}
});
thread1.start();
TimeUnit.SECONDS.sleep(2);
thread1.interrupt();
}
}
结果:
locked
lock released
检查中断
可以调用Thread.interrupted()来检查中断状态
看一下下面这段代码:
while(!Thread.interrupted()){
//point1
try {
TimeUnit.SECONDS.sleep(2);
//point2
dosomething();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
如果interrupt()在注释point2之后(非阻塞操作)被调用,那么循环将结束满所有的本地对象将被销毁,最后循环会经由while语句的顶部退出。如果在point1和point2之间(sleep()之前或过程中)被调用,这个任务就会在第一次试图调用阻塞操作之前,经由InterruptedException退出。
二、线程之间的协作
如果两个任务在交替着步入某项共享资源,可以使用互斥来使得任何时刻只有一个任务可以访问这项资源。当多个任务一起工作的时候,某项工作依赖于前一项工作的完成,这就需要线程之间的协作。
wait()、notify()、notifyAll()
三种方法都是基类Object中的,只能在同步控制方法或同步控制块中调用,否则会抛出IllegalMonitorStateException异常,wait()、notify()、notifyAll()在调用前必须得到对象的锁。
wait()释放锁,任务被挂起,知道外部将它唤醒。(sleep()和yield()时锁没有被释放)
notify()唤醒等待同一个锁的任务中的一个,任务必须等待相同的条件,当条件发生变化时,必须只有一个任务能够从中受益。
notifyAll()唤醒所有在等待的任务,因某个特定锁而被调用时只有等待这个锁的任务才能被唤醒
信号的错失
假如两个线程已下面的方式实现
T1:
synchronized (sharedMonitor) {
sharedMonitor.notify();
}
T2:
while(someCondition){
synchronized(sharedMonitor){
sharedMonitor.wait();
}
}
这种方式可能会导致信号的错失
错失了notify(),T2将进入wait(),并一直等待着一个已经发出的信号,从而产生死锁。
解决方法,防止在someCondition变量上产生竞争条件
synchronized(sharedMonitor){
while(someCondition){
sharedMonitor.wait();
}
生产者消费者问题
可以参考我另外一篇文章:http://blog.csdn.net/linsawako/article/details/53289126
使用显示的Lock和Condition对象
使用互斥并允许任务挂起的基本类是Condition,单个Lock将产出一Condition对象,这个对象被用来管理任务间的通信。调用await()来挂起一个任务,signal()和signalAll()来唤醒等待中的任务。和synchronized一样,调用这些方法之前,需要先取得这个锁。
class Buffer{
private ArrayList<Integer> list = new ArrayList<>();
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public int get(){
int num;
lock.lock();//加锁
try {
while(list.size() < 1){
System.out.println(" 等待数据输出");
condition.await();
}
num = list.remove(0);//移除数据
condition.signalAll();//唤醒等待中的数据
return num;
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
return 0;
}
public void push(int num){
lock.lock();
try {
while(list.size() > 0){
System.out.println("等待数据输入");
condition.await();
}
list.add(num);//添加数据
condition.signalAll();
}catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
class Producer implements Runnable{
//生产者
private Buffer buffer;
private Random random = new Random();
public Producer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while (!Thread.interrupted()) {
int num = random.nextInt(100);
buffer.push(num);
System.out.println("写入数据:" + num);
//休眠一段时间
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer implements Runnable{
//消费者
private Buffer buffer;
public Consumer(Buffer buffer) {
this.buffer = buffer;
}
@Override
public void run() {
while(!Thread.interrupted()){
System.out.println(" 得到数据:" + buffer.get());
//休眠一段时间
try {
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class LockCondition {
private Buffer buffer = new Buffer();
private Producer producer = new Producer(buffer);
private Consumer consumer = new Consumer(buffer);
public LockCondition() {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(producer);
exec.execute(consumer);
exec.shutdown();
}
public static void main(String[] args) {
new LockCondition();
}
}
生产者-消费者与队列
可以使用同步队列来解决任务协作问题,同步队列在任何时刻都只允许一个任务插入或移除元素,在java.util.concurrent.BlockingQueue接口提供了这个队列
- SynchronousQueue是无界的,是一种无缓冲的等待队列
- LinkedBlockingQueue是无界的,是一个无界缓存的等待队列。
- ArrayListBlockingQueue是有界的,是一个有界缓存的等待队列。
class Producer1 implements Runnable{
//生产者
private BlockingQueue<Integer> queue;
private Random random = new Random();
public Producer1(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while (!Thread.interrupted()) {
//休眠一段时间
try {
int num = random.nextInt(100);
queue.put(num);
System.out.println("写入数据:" + num);
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Consumer1 implements Runnable{
//消费者
private BlockingQueue<Integer> queue;
public Consumer1(BlockingQueue<Integer> queue) {
this.queue = queue;
}
@Override
public void run() {
while(!Thread.interrupted()){
//休眠一段时间
try {
System.out.println(" 得到数据:" + queue.take());
Thread.sleep((long) (Math.random() * 1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class BlockingQueueTest {
private static ArrayBlockingQueue<Integer> queue = new ArrayBlockingQueue<>(2);//阻塞队列,容量为2
public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
exec.execute(new Producer1(queue));
exec.execute(new Consumer1(queue));
exec.shutdown();
}
}
运行后:
写入数据:82
得到数据:82
写入数据:56
得到数据:56
写入数据:25
写入数据:3
得到数据:25
写入数据:48
得到数据:3
得到数据:48
写入数据:31
写入数据:37
得到数据:31
得到数据:37
写入数据:44
得到数据:44
对于不受限的队列而言,put方法将永远不会阻塞
任务间使用管道进行输入/输出
java输入/输出对应物为Pipedwriter类(允许任务向管道写)和PipedReader(允许不同任务从同一个管道中读取)
三、死锁
有时两个或多个线程需要在几个共享对象上获取锁,就可能导致死锁
先来看一下下面这段情况
线程1
synchronized (object1) {
//do something
synchronized (object2) {
//do something
}
}
线程2
synchronized (object2) {
//do something
synchronized (object1) {
//do something
}
}
假如线程1获得了object1的锁,在等着object2的锁,线程2获得了object2的锁,在等着object1的锁,这样每个线程都在等待另一个线程释放它所需要的锁,导致都无法继续运行。
以下四个条件同时满足时,就会发生死锁:
1)互斥条件,任务使用的资源中至少有一个是不能共享的
2)至少有一个任务它必须持有一个资源且在等待获取一个当前被别的任务持有的资源
3)资源不能被任务抢占,不能抢夺其他线程已经获得的锁
4)必须持有循环等待
要防止死锁,只需要破坏其中的一个。
最容易的是破坏第4个条件,使用资源排序,给每一个需要锁的对象指定一个顺序,确保每个线程都按这个顺序来获得锁,假如上面的例子,按object1、object2的顺序来对两个顺序排序。
经典的死锁问题为哲学家就餐问题:有五个哲学家,他们会花部分时间思考,花部分时间就餐。当他们思考的时候,他们不需要共享任何资源互不影响。而当他们就餐时,因为这里只有五只筷子,在他们每人都需要两只筷子的情况下,就会形成对筷子的竞争
代码来自《Thinking in java》
class Chopstick{
private boolean token=false;
public synchronized void take() throws InterruptedException{
while(token){
wait();
}
token=true;
}
public synchronized void drop(){
token=false;
notifyAll();
}
}
class Philosopers implements Runnable{
private Chopstick left;
private Chopstick right;
private final int id;
private final int pauseFactor;
private Random rand = new Random(200);
public Philosopers(Chopstick left,Chopstick right,int id,int pauseFactor){
this.left=left;
this.right=right;
this.id=id;
this.pauseFactor=pauseFactor;
}
private void pause() throws InterruptedException{
TimeUnit.MILLISECONDS.sleep(pauseFactor*rand.nextInt(100));
}
public void run(){
try{
while(!Thread.interrupted()){
System.out.println(this+" "+"thinking");
pause();
left.take();
System.out.println(this+" 取得左边的筷子");
right.take();
System.out.println(this+" 取得右边的筷子");
System.out.println(this+"Eating");
pause();
left.drop();
right.drop();
}
}catch(InterruptedException ex){
System.out.println(this+" 通过中断异常退出");
}
}
public String toString(){
return (id+1)+"号哲学家 ";
}
}
public class Test4 {
public static void main(String[] args) throws Exception{
int pauseFactor=0;
int size=5;
Chopstick[] chopstick = new Chopstick[size];
for(int i=0;i<size;i++){
chopstick[i] = new Chopstick();
}
ExecutorService exec = Executors.newCachedThreadPool();
for(int i=0; i<size; i++){
exec.execute(new Philosopers(chopstick[i],chopstick[(i+1)%size],i,pauseFactor));
}
exec.shutdown();
System.out.println("press 'Enter' to quit");
System.in.read();
exec.shutdownNow();
}
}
运行结果:
...
1号哲学家 取得左边的筷子//1左
4号哲学家 取得右边的筷子
4号哲学家 Eating
4号哲学家 thinking
5号哲学家 取得左边的筷子//5左
3号哲学家 取得右边的筷子
3号哲学家 Eating
3号哲学家 thinking
4号哲学家 取得左边的筷子//4左
2号哲学家 取得右边的筷子
2号哲学家 Eating
2号哲学家 thinking
2号哲学家 取得左边的筷子//2左
3号哲学家 取得左边的筷子//3左
想象着五个哲学家围成一圈,都举着左手的筷子,并等待着其他人放下,结果都傻傻地等着。导致死锁
可以设置最后一个哲学家先放下右手的Chopstick,移除死锁。