多线程同步-线程之间通信:wait notify 另外还有interrupt,join方法
2017-03-14 10:45
253 查看
多线程的作用:有的线程需要I/O,有的需要CPU资源,多线程编程的目的就是充分使用CPU资源,达到加快程序执行的目的。
线程之间的相互作用:线程之间通信
(生产者-消费者问题:可以看成两个线程,生产者一个线程,消费者一个线程,生产者这生产一个东西告诉消费者可以消费了,消费者消费完这个东西就通知生产者可以生产了,绝对不允许生产者连续生产而无消费者消费的情况)
(哲学家就餐问题: 一群哲学家围着一个圆桌吃饭,吃饭需要筷子,但是筷子并不够,并不能每个人分到一双筷子,筷子的分布情况是这样的: 哲学家的左手和右手边分别有一只筷子,一只筷子是无法吃饭,出现的情况时哲学家每个人手里拿着一只筷子等待着别人释放筷子,但是每个哲学家都这么想,都在等待,因此大家都只有一只筷子没办法吃饭,就饿死了,在操作系统中这种现象被称为:死锁 deadlock)
wait 方法:造成当前线程等待,直到另外一个线程调用了notify或者notifyAll方法 ,当前线程必须拥有该对象的 monitor 才可以,意味着当前线程在该对象的synchronized快里或者synchronized方法里面。
wait 方法的调用必须在synchronized快里或者synchronized方法里面调用才可以 不能被重写 wait(time) 方法, 定义等待线程多长时间
notify() 和notifyAll() 也是在synchronized快里或者synchronized方法里面调用才可以 不能被重写
当一个线程执行完毕,释放掉了对象的锁,调用了 notify()方法,就随机的选择了一个正在等待的线程被唤醒,这个线程不是由我们决定的,而是系统自己决定的,
调用了 notifyAll()方法,就通知所有正在等待的线程,然后多个线程通过竞争得到对象的锁。
wait 与 notify 方法都是定义在 Object 类中,而且是 final 的,因此会被所有的 Java类所继承并且无法重 写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在 synchronized 方法或块当中。当线程执行了 wait方法时,它会释放掉对象的锁。
sleep:另一个会导致线程暂停的方法就是 Thread 类的 sleep 方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的 (这是跟wait方法不同的一点)。
wait方法一般都是要加异常处理的,这是因为:当某代码并不持有监视器的使用权时,去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。
等待池(wait blocked pool ):线程使用了wait方法之后,就进入了等待池,
锁池 (lock blocked pool ):当一个线程释放掉锁后,使用notify或者notifyAll方法通知等待池中的线程,等待池中的线程就会进入锁池,等待获取对象的锁。
join() 方法:
当一个线程A在另一个线程B之中调用了join方法,或者在B线程之前调用了join方法,则B 必须等待A执行完之后才能执行。但是要注意的是,A必须在B启动(调用start方法)之前调用join方法,否则不起任何作用,下面的例子说明了此点。
Thread.interrupt()方法
首先,看看Thread类里的关于中断的有关几个方法的信息:
public static boolean interrupted() : 测试当前线程是否已经中断, 线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false。
public boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响。
public void interrupt() 中断线程。调用此方法将会设置该线程的中断状态位,即设置为true,线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会停止一个正在运行的线程(而且stop方法是不被建议使用的)。
注意的是,Java的中断是一种协作机制,也就是说调用线程对象的interrupt方法并不一定就中断了正在运行的线程,每个线程都有一个boolean的中断状态,interrupt方法仅仅只是将该状态置为true,中断之后会抛出一个InterruptedException, 中断的结果,线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身对于中断的异常处理的情况。
可以中断的有哪些?
第一: sleep, wait , join, 都是可以被中断的
第二: I/O操作,
传统的的I/O操作(阻塞式,流的形式)被阻塞时,是不可以被中断的,调用流的close方法也会被阻塞;
Java 1.4中引入的新的I/O —NI/O (基于通道的,非阻塞式的)是可以被中断的,可以调用流的close方法关闭流,NI/O的特性之一就是当文件读取堵塞时可以进行其他操作,而不会一直阻塞,而且一个I/O连接可以在空闲时帮助其他的线程进行I/O进行工作。
第三; synchronized在获锁的过程中是不能被中断的, 也就是说死锁状态的线程也是无法被中断的。
那么如何停止一个线程?
不建议直接使用stop方法,最好的方式是使用共享变量的方式来停止线程, 线程周期性的检查这一个变量,然后有序的中止任务。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁 (Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
线程之间的相互作用:线程之间通信
(生产者-消费者问题:可以看成两个线程,生产者一个线程,消费者一个线程,生产者这生产一个东西告诉消费者可以消费了,消费者消费完这个东西就通知生产者可以生产了,绝对不允许生产者连续生产而无消费者消费的情况)
(哲学家就餐问题: 一群哲学家围着一个圆桌吃饭,吃饭需要筷子,但是筷子并不够,并不能每个人分到一双筷子,筷子的分布情况是这样的: 哲学家的左手和右手边分别有一只筷子,一只筷子是无法吃饭,出现的情况时哲学家每个人手里拿着一只筷子等待着别人释放筷子,但是每个哲学家都这么想,都在等待,因此大家都只有一只筷子没办法吃饭,就饿死了,在操作系统中这种现象被称为:死锁 deadlock)
wait 方法:造成当前线程等待,直到另外一个线程调用了notify或者notifyAll方法 ,当前线程必须拥有该对象的 monitor 才可以,意味着当前线程在该对象的synchronized快里或者synchronized方法里面。
wait 方法的调用必须在synchronized快里或者synchronized方法里面调用才可以 不能被重写 wait(time) 方法, 定义等待线程多长时间
synchronized (obj) { while (<condition does not hold>) obj.wait(); // 等待notify通知,但是得到通知了并不会一定会被唤醒,它必须要获取到该对象的锁才能继续往下执行 ... // Perform action appropriate to condition }
notify() 和notifyAll() 也是在synchronized快里或者synchronized方法里面调用才可以 不能被重写
当一个线程执行完毕,释放掉了对象的锁,调用了 notify()方法,就随机的选择了一个正在等待的线程被唤醒,这个线程不是由我们决定的,而是系统自己决定的,
调用了 notifyAll()方法,就通知所有正在等待的线程,然后多个线程通过竞争得到对象的锁。
wait 与 notify 方法都是定义在 Object 类中,而且是 final 的,因此会被所有的 Java类所继承并且无法重 写。这两个方法要求在调用时线程应该已经获得了对象的锁,因此对这两个方法的调用需要放在 synchronized 方法或块当中。当线程执行了 wait方法时,它会释放掉对象的锁。
sleep:另一个会导致线程暂停的方法就是 Thread 类的 sleep 方法,它会导致线程睡眠指定的毫秒数,但线程在睡眠的过程中是不会释放掉对象的锁的 (这是跟wait方法不同的一点)。
wait方法一般都是要加异常处理的,这是因为:当某代码并不持有监视器的使用权时,去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。
等待池(wait blocked pool ):线程使用了wait方法之后,就进入了等待池,
锁池 (lock blocked pool ):当一个线程释放掉锁后,使用notify或者notifyAll方法通知等待池中的线程,等待池中的线程就会进入锁池,等待获取对象的锁。
// 这个例子可以看做是一个生产者消费者问题 public class WaitTest { public static void main(String[] args) { Sample sample = new Sample(); AddThread addThread1 = new AddThread(sample); AddThread addThread2 = new AddThread(sample); PlusThread plusThread1 = new PlusThread(sample); PlusThread plusThread2 = new PlusThread(sample); // 启动了两个增加线程,启动了两个减少线程 // 线程的调试是非常困难的,而且无法使用try catch语句捕获线程的异常 // 建议为每一个线程或者线程池设置名字, addThread1.start(); addThread1.setName("add1"); //为线程设置名字 addThread2.start(); plusThread1.start(); plusThread2.start(); // 这样输出的结果是不正确的,并不是 1,0 交替执行,为什么呢? //因为线程在wait后直接调用后面的代码了,而没有再一次的检查是否符合条件 // 将两个synchronized方法里面的判断语句 if 修改为while就可以了 } } // 增加的线程 class AddThread extends Thread{ private Sample sample; public AddThread( Sample sample){ this.sample = sample; } public void run(){ for(int i = 0; i < 10; i++){ sample.add(); // 要想实现 0, 1 交替输出,输出语句是不能放在这里的,因为上面的方法虽然被阻塞了 // 但是下面的语句线程是可以执行的,因此会出现乱序问题 // System.out.println(sample.getNumber()); } } } // 减少的线程 class PlusThread extends Thread{ private Sample sample; public PlusThread( Sample sample){ this.sample = sample; } public void run(){ for(int i = 0; i < 10; i++){ sample.plus(); // System.out.println(sample.getNumber()); } } } class Sample{ private int number; public int getNumber() { return number; } public synchronized void add(){ // if (0 != number){ while (0 != number){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } number++; System.out.println(number); // 输出语句应该放在同步代码快中 notifyAll(); } public synchronized void plus(){ // if (0 == number){ while (0 == number){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } number --; System.out.println(number); notifyAll(); } }
join() 方法:
当一个线程A在另一个线程B之中调用了join方法,或者在B线程之前调用了join方法,则B 必须等待A执行完之后才能执行。但是要注意的是,A必须在B启动(调用start方法)之前调用join方法,否则不起任何作用,下面的例子说明了此点。
// 线程变成了顺序执行, t1 执行完毕之后--》 t2 执行完毕之后 --》main线程里的最后一句输出语句 public static void main(String[] args){ t1.start(); t1.join(); t2.start(); t2.join(); System.out.println("main Thread"); } // 线程的执行顺序, t1和t2交替执行,等到t1和t2都执行完毕之后才会调用main线程的输出语句 public static void main(String[] args){ t1.start(); t2.start(); t1.join(); t2.join(); System.out.println("main Thread"); }
Thread.interrupt()方法
首先,看看Thread类里的关于中断的有关几个方法的信息:
public static boolean interrupted() : 测试当前线程是否已经中断, 线程的中断状态由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false。
public boolean isInterrupted() 测试线程是否已经中断。线程的中断状态不受该方法的影响。
public void interrupt() 中断线程。调用此方法将会设置该线程的中断状态位,即设置为true,线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会停止一个正在运行的线程(而且stop方法是不被建议使用的)。
注意的是,Java的中断是一种协作机制,也就是说调用线程对象的interrupt方法并不一定就中断了正在运行的线程,每个线程都有一个boolean的中断状态,interrupt方法仅仅只是将该状态置为true,中断之后会抛出一个InterruptedException, 中断的结果,线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身对于中断的异常处理的情况。
可以中断的有哪些?
第一: sleep, wait , join, 都是可以被中断的
第二: I/O操作,
传统的的I/O操作(阻塞式,流的形式)被阻塞时,是不可以被中断的,调用流的close方法也会被阻塞;
Java 1.4中引入的新的I/O —NI/O (基于通道的,非阻塞式的)是可以被中断的,可以调用流的close方法关闭流,NI/O的特性之一就是当文件读取堵塞时可以进行其他操作,而不会一直阻塞,而且一个I/O连接可以在空闲时帮助其他的线程进行I/O进行工作。
第三; synchronized在获锁的过程中是不能被中断的, 也就是说死锁状态的线程也是无法被中断的。
那么如何停止一个线程?
不建议直接使用stop方法,最好的方式是使用共享变量的方式来停止线程, 线程周期性的检查这一个变量,然后有序的中止任务。
悲观锁(Pessimistic Lock), 顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。
乐观锁 (Optimistic Lock), 顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。
相关文章推荐
- JavaSE第一百零四讲:哲学家就餐问题、死锁与使用wait及notify方法实现线程之间的相互通信
- Java 多线程 线程间的通信——wait及notify方法
- java中线程阻塞之sleep、suspend、join、wait、resume、notify方法解析(一)
- 线程之间的同步和通信,synchronized,wait(),notify(),notifyAll()
- Java线程中sleep()、wait()和notify()和notifyAll()、yield()、join()等方法的用法和区别
- Java 多线程之线程间的通信——wait及notify方法
- Java线程中sleep()、wait()和notify()和notifyAll()、yield()、join()等方法的用法和区别
- Java线程中sleep()、wait()和notify()和notifyAll()、yield()、join()等方法的用法和区别
- 线程间通信的实现 wait()和notify()方法
- java多线程详解(6)-线程间的通信wait及notify方法
- 多线程五,线程间通信2,wait、notify,notifyAll方法(14,毕向东老师)
- 多线程五,线程间通信,wait、notify,notifyAll方法(14,毕向东老师)
- Java 线程间的通信——wait及notify方法
- Java 多线程(七) 线程间的通信——wait及notify方法
- 线程中的一些常用方法的用法 join()、yield()、sleep()、wait()、notify()、notifyAll()
- 用java来实现线程之间的wait、notify()通信
- 多线程五,线程间通信3,wait、notify,notifyAll方法,生产者和消费者问题(14,毕向东老师)
- Java【多线程知识总结(10)】线程通信之wait()与notify()的运用--模拟指挥官指挥2个连队交替轰炸战区<另外的写法>
- Java线程中sleep()、wait()和notify()和notifyAll()、yield()、join()等方法的用法和区别
- Java线程中sleep()、wait()和notify()和notifyAll()、suspend和resume()、yield()、join()、interrupt()的用法和区别