5、多线程
5.3 线程间通信
5.3.1 线程间通信涉及的方法
多个线程在处理统一资源,但是任务却不同,这时候就需要线程间通信。
等待/唤醒机制涉及的方法:
1. wait():让线程处于冻结状态,被wait的线程会被存储到线程池中。
2. notify():唤醒线程池中的一个线程(任何一个都有可能)。
3. notifyAll():唤醒线程池中的所有线程。
P.S.
1、这些方法都必须定义在同步中,因为这些方法是用于操作线程状态的方法。
2、必须要明确到底操作的是哪个锁上的线程!
3、wait和sleep区别?
1)wait可以指定时间也可以不指定。sleep必须指定时间。
2)在同步中时,对CPU的执行权和锁的处理不同。
wait:释放执行权,释放锁。
sleep:释放执行权,不释放锁。
为什么操作线程的方法wait、notify、notifyAll定义在了object类中,因为这些方法是监视器的方法,监视器其实就是锁。
锁可以是任意的对象,任意的对象调用的方式一定在object类中。
生产者-消费者问题:
- class Resource{
- private String name ;
- private String sex ;
- private boolean flag = false;
- public synchronized void set(String name,String sex){
- if(flag )
- try{
- this.wait();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- this.name = name;
- this.sex = sex;
- flag = true ;
- this.notify();
- }
-
- public synchronized void out(){
- if(!flag )
- try{
- this.wait();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- System. out.println(name + "..." + sex);
- flag = false ;
- this.notify();
- }
- }
- //输入
- class Input implements Runnable{
- Resource r;
- Input(Resource r){
- this.r = r;
- }
- public void run(){
- int x = 0;
- while(true ){
- if(x == 0){
- r.set( "mike","男" );
- } else{
- r.set( "lili","女" );
- }
- x = (x + 1)%2;
- }
- }
- }
- //输出
- class Output implements Runnable{
- Resource r;
- Output(Resource r){
- this.r = r;
- }
- public void run(){
- while(true ){
- r.out();
- }
- }
- }
- class ResourceDemo {
- public static void main(String[] args){
- //创建资源
- Resource r = new Resource();
- //创建任务
- Input in = new Input(r);
- Output out = new Output(r);
- //创建线程,执行路径
- Thread t1 = new Thread(in);
- Thread t2 = new Thread(out);
- //开启线程
- t1.start();
- t2.start();
- }
- }
复制代码 运行结果: - class Resource{
- private String name ;
- private int count = 1;
- private boolean flag = false;
- public synchronized void set(String name){
- if(flag )
- try{
- wait();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- this.name = name + count;
- count++;
- System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
- flag = true ;
- notify();
- }
- public synchronized void out(){
- if(!flag )
- try{
- wait();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- flag = false ;
- notify();
- System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
- }
- }
- class Producer implements Runnable{
- private Resource r ;
- Producer(Resource r){
- this.r = r;
- }
- public void run(){
- while(true ){
- r.set( "烤鸭");
- }
- }
- }
- class Consumer implements Runnable{
- private Resource r ;
- Consumer(Resource r){
- this.r = r;
- }
- public void run(){
- while(true ){
- r.out();
- }
- }
- }
-
- class ProducerConsumerDemo {
- public static void main(String[] args){
- Resource r = new Resource();
- Producer pro = new Producer(r);
- Consumer con = new Consumer(r);
- Thread t0 = new Thread(pro);
- Thread t1 = new Thread(pro);
- Thread t2 = new Thread(con);
- Thread t3 = new Thread(con);
- t0.start();
- t1.start();
- t2.start();
- t3.start();
- }
- }
复制代码 运行结果:以上代码存在安全问题。
原因分析: 得到以上结果的过程分析如下: 1. 线程Thread-0获取到CPU执行权及锁,生产了烤鸭3298,将flag设置为true。然后,Thread-0又重新获取到CPU执行权,由于flag为true,故执行wait方法,阻塞。Thread-1接着获取到CPU执行权,由于flag为true,故执行wait方法,也阻塞。 2. 线程Thread-3获取到CPU执行权及锁,消费了烤鸭3298,将flag设置为false。然后,线程Thread-0被唤醒,但是并没有获取到锁,而是线程Thread-3接着获取到CPU执行权及锁,然而此时flag为false,所以Thread-3阻塞。下面线程Thread-2接着获取到CPU执行权及锁,然而此时flag为false,所以Thread-2也阻塞。
3. 线程Thread-0获取到CPU执行权及锁,不需要if语句判断,直接生产烤鸭3299,然后又唤醒线程Thread-1获取到CPU执行权及锁,不需要if语句判断,直接生产烤鸭3300。从而造成了烤鸭3299还没有被消费,就直接生产了烤鸭3300的情况。
由于if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况。故修改成while判断标记,线程获取CPU执行权及锁后,将重新判断是否具备运行条件。 notify方法只能唤醒一个线程,如果本方唤醒了本方,没有意义。而且while判断标记+notify会导致死锁。notifyAll解决了本方线程一定会唤醒对方线程的问题。
P.S. while判断标记+notify会导致死锁的示例: 如果将上面的代码中的if判断标记修改成wile判断标记,就会出现死锁的现象,前2步与原来是一致的。第3步如下: 3. 线程Thread-0获取到CPU执行权及锁,通过了while语句判断,直接生产烤鸭3299,将flag设置为true。然后又唤醒线程Thread-1获取到CPU执行权及锁,没有通过while语句判断,阻塞。线程Thread-0又获取到CPU执行权及锁,通不过while语句判断,也阻塞,此时Thread-0、1、2、3都阻塞,故死锁。
代码:
- class Resource{
- private String name ;
- private int count = 1;
- private boolean flag = false;
- public synchronized void set(String name){
- while(flag )
- try{
- this.wait();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- this.name = name + count;
- count++;
- System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
- flag = true ;
- notifyAll();
- }
- public synchronized void out(){
- while(!flag )
- try{
- this.wait();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- flag = false ;
- notifyAll();
- System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
- }
- }
- class Producer implements Runnable{
- private Resource r ;
- Producer(Resource r){
- this.r = r;
- }
- public void run(){
- while(true ){
- r.set( "烤鸭");
- }
- }
- }
- class Consumer implements Runnable{
- private Resource r ;
- Consumer(Resource r){
- this.r = r;
- }
- public void run(){
- while(true ){
- r.out();
- }
- }
- }
- class ProducerConsumerDemo {
- public static void main(String[] args){
- Resource r = new Resource();
- Producer pro = new Producer(r);
- Consumer con = new Consumer(r);
- Thread t0 = new Thread(pro);
- Thread t1 = new Thread(pro);
- Thread t2 = new Thread(con);
- Thread t3 = new Thread(con);
- t0.start();
- t1.start();
- t2.start();
- t3.start();
- }
- }
复制代码 运行结果:
5.3.2 JDK1.5新特性
同步代码块就是对于锁的操作是隐式的。
JDK1.5以后将同步和锁封装成了对象,并将操作锁的隐式方式定义到了该对象中,将隐式动作变成了显示动作。
Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。
Condition接口中的await方法对应于Object中的wait方法。
Condition接口中的signal方法对应于Object中的notify方法。
Condition接口中的signalAll方法对应于Object中的notifyAll方法。
使用一个Lock、一个Condition修改上面的多生产者-多消费者问题。
代码: - import java.util.concurrent.locks.*;
- class Resource{
- private String name ;
- private int count = 1;
- private boolean flag = false;
-
- //创建一个锁对象
- Lock lock = new ReentrantLock();
- //通过已有的锁获取该锁上的监视器对象
- Condition con = lock .newCondition();
- public void set(String name){
- lock.lock();
- try{
- while(flag )
- try{
- con.await();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- this.name = name + count;
- count++;
- System.out.println(Thread.currentThread().getName() + "...生产者..." + this. name);
- flag = true ;
- con.signalAll();
- }finally{
- lock.unlock();
- }
- }
- public void out(){
- lock.lock();
- try{
- while(!flag )
- try{
- con.await();
- } catch(InterruptedException e){
- e.printStackTrace();
- }
- flag = false ;
- con.signalAll();
- System.out.println(Thread.currentThread().getName() + "...消费者..." + this. name);
- }finally{
- lock.unlock();
- }
- }
- }
- class Producer implements Runnable{
- private Resource r ;
- Producer(Resource r){
- this.r = r;
- }
- public void run(){
- while(true ){
- r.set( "烤鸭");
- }
- }
- }
- class Consumer implements Runnable{
- private Resource r ;
- Consumer(Resource r){
- this.r = r;
- }
- public void run(){
- while(true ){
- r.out();
- }
- }
- }
- class ProducerConsumerDemo {
- public static void main(String[] args){
- Resource r = new Resource();
- Producer pro = new Producer(r);
- Consumer con = new Consumer(r);
- Thread t0 = new Thread(pro);
- Thread t1 = new Thread(pro);
- Thread t2 = new Thread(con);
- Thread t3 = new Thread(con);
- t0.start();
- t1.start();
- t2.start();
- t3.start();
- }
- }
复制代码 运行结果: 使用一个Lock、两个Condition修改上面的多生产者-多消费者问题。
~END~
|