java multithreading communication - yaokun123/php-wiki GitHub Wiki

JAVA多线程之间的通信

一、线程间通信-示例

多个线程在处理同一个资源,但是任务不同。

//资源
class Resource{
    String name;
    String sex;
}

//输入
class Input implement Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            if(x%2==0){
                r.name = "mike";
                r.sex = "man";
            }else{
                r.name = "丽丽";
                r.sex = "女";
            } 
            x++;    
        }
    }
}

//输出
class Output implement Runnable{
    Resource r;
    Output(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){
            System.out.println(r.name+"..."+r.sex);
        }
    }
}

class Demo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input i = new Input(r);
        Output o = new Output(r);

        Thread t1 = new Thread(i);
        Thread t2 = new Thread(o);

        t1.start();
        t2.start();
    }
}

由于读和写不同步,所以会造成数据混乱

修改如下:

//输入
class Input implement Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            synchronized(this.r){
                if(x%2==0){
                    r.name = "mike";
                    r.sex = "man";
                }else{
                    r.name = "丽丽";
                    r.sex = "女";
                } 
                x++;
            }    
        }
    }
}

//输出
class Output implement Runnable{
    Resource r;
    Output(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){
            synchronized(this.r){
                System.out.println(r.name+"..."+r.sex);
            }
        }
    }
}

二、线程间通信-等待唤醒机制-wait()-notify()

涉及的方法

1、wait():是让线程处于冻结状态,释放cpu的执行权和执行资格。被wait的线程会被存储到线程池中。

2、notify():唤醒线程池中一个线程(任意)。

3、notifyAll():唤醒线程池中的所有线程。

注意:这些方法都必须定义在同步中。因为这些方法是用于操作线程状态的方法。必须要明确到底操作的是哪一个锁上的线程。

为什么操作线程的方法定义在Object类中? 因为这些方法是监视器的方法,监视器其实就是锁,而锁就是任意的对象,所以任意的对象可以调用的方法必须定义在Object中。

//资源
class Resource{
    String name;
    String sex;
    boolean flag = false;
}

//输入
class Input implement Runnable{
    Resource r;
    Input(Resource r){
        this.r = r;
    }
    public void run(){
        int x = 0;
        while(true){
            synchronized(this.r){
                if(this.r.flag){
                    try{
                        this.r.wait();
                    }catch(InterruptedException){}
                }

                if(x%2==0){
                    this.r.name = "mike";
                    this.r.sex = "man";
                }else{
                    this.r.name = "丽丽";
                    this.r.sex = "女";
                }
                this.r.flag = true;
                this.r.notify();
                x++;
            }    
        }
    }
}

//输出
class Output implement Runnable{
    Resource r;
    Output(Resource r){
        this.r = r;
    }
    public void run(){
        while(true){
            synchronized(this.r){
                if(!this.r.flag){
                    try{
                        this.r.wait();
                    }catch(InterruptedException){}
                }
                System.out.println(this.r.name+"..."+this.r.sex);
                this.r.flag = false;
                this.r.notify();
            }
        }
    }
}
class Demo{
    public static void main(String[] args){
        Resource r = new Resource();
        Input i = new Input(r);
        Output o = new Output(r);

        Thread t1 = new Thread(i);
        Thread t2 = new Thread(o);

        t1.start();
        t2.start();
    }
}

三、线程间通信-多生产者多消费者问题-wait()-notifyAll()

if判断标记,只有一次,会导致不该运行的线程运行了,出现了数据错误的情况
while判断标记,解决了线程获取执行权后是否要运行(回过头判断)

notify只能唤醒一个线程,如果奔放唤醒了本方就没有意义。而且while判断标记+notify会导致死锁
notifyAll解决了本方线程一定会唤醒对方线程

class Resource{
    private String name;
    private int count = 1;
    private boolean flag = false;

    public synchronized void setName(String name){
        if(this.flag){//多生产者这里不能在用if,必须用while,进程唤醒之后再进行判断标记
            try{this.wait();}catch(InterruptedException e){}
        }
        this.name = name + this.count;
        this.count++;
        System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);

        this.flag = true;
        this.notify();//多生产者这里必须使用notifyAll()唤醒所有线程,不然会导致所有线程都被休眠现象(死锁)
    }

    public synchronized void out(){
        if(!this.flag){//多生产者这里不能在用if,必须用while,进程唤醒之后再进行判断标记
            try{this.wait();}catch(InterruptedException e){}
        }
        System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
        this.flag = false;
        this.notify();
    }
}

class Producer implement runnable{
    private Resource r;
    Producer(Resource r){
        this.r = r;
    }

    public void run(){
        while(true){
           this.r.setName("烤鸭"); 
        }
    }
}

class Consummer implement runnable{
    private Resource r;
    Consummer(Resource r){
        this.r = r;
    }

    public void run(){
        while(true){
           this.r.out(); 
        }
    }
}

class ProducerConsumerDemo{
    public static void main(String[] args){
        Resource r = new Resource();

        Producer p = new Producer(r);
        Consummer c = new Consummer(r);

        //多生产者
        Thread t0 = new Thread(p);
        Thread t1 = new Thread(p);

        //多消费者 
        Thread t2 = new Thread(c);
        Thread t3 = new Thread(c);

        t1.start();
        t2.start();
    }
}

多生产者多消费者使用notify()是造成另一种死锁的原因。

四、线程间通信-多生产者多消费者问题-JDK1.5新特性-Lock

对于同步代码块,对于锁的操作是隐式的。

import java.util.concurrent.locks.*;
class Resource{
    private String name;
    private int count = 1;
    private boolean flag = false;

    Look look = new ReentrantLock();//jdk1.5新特性-创建一个锁对象

    public void setName(String name){
        lock.lock();//使用显式锁
 
        try{
            while(this.flag){//此处就不能再用this了,要用锁上的wait
                try{this.wait();}catch(InterruptedException e){}
            }
            this.name = name + this.count;
            this.count++;
            System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);

            this.flag = true;
            this.notify();//此处就不能再用this了
        }finally{
            lock.unlock();
        }
    }

    public void out(){
        try{
            if(!this.flag){//此处就不能再用this了
                try{this.wait();}catch(InterruptedException e){}
            }
            System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
            this.flag = false;
            this.notify();//此处就不能再用this了
        }finally{
            lock.unlock();
        }   
    }
}

Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式锁操作变为显式锁操作。同时更为灵活。可以在一个锁上加上多个监视器

五、线程间通信-多生产者多消费者问题-JDK1.5新特性-Condition

import java.util.concurrent.locks.*;
class Resource{
    private String name;
    private int count = 1;
    private boolean flag = false;

    Look look = new ReentrantLock();//jdk1.5新特性-创建一个锁对象

    //通过已有的锁获取该锁上的监视器对象
    Condition con1 = lock.newCondition();//两组监视器,一组监视生产者,一组监视消费者
    Condition con2 = lock.newCondition();

    public void setName(String name){
        lock.lock();//使用显式锁
 
        try{
            while(this.flag){
                try{con1.await();}catch(InterruptedException e){}//此处就不能再用this了,要用锁上的wait
            }
            this.name = name + this.count;
            this.count++;
            System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);

            this.flag = true;
            //this.notify();//此处就不能再用this了
            con2.signalAll();/con2.signal();
        }finally{
            lock.unlock();
        }
    }

    public void out(){
        try{
            if(!this.flag){//此处就不能再用this了
                try{con2.await();}catch(InterruptedException e){}
            }
            System.out.println(Thread.currentThread().getName()+"......消费者......"+this.name);
            this.flag = false;
            //this.notify();//此处就不能再用this了
            con1.signalAll();/con1.signal();
        }finally{
            lock.unlock();
        }   
    }
}

Condition接口:出现替代了Object中的wait notify notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合(await signal signalAll)

六、wait和sleep的区别

1、wait可以指定时间也可以不指定。sleep必须指定时间。
2、在同步中,对cpu的执行权和锁的处理不同
    wait:释放执行权,释放锁
    sleep:释放执行权,不释放锁。

七、停止线程

  • 1、stop()
 已过时
  • 2、run方法结束
任务中都会有循环结构,只要控制住循环就可以结束任务。
控制循环通常就用定义标记来完成。

但是如果线程处于冻结状态,无法读取标记,如何结束呢?

可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备cpu的执行资格。
但是强制动作会发生InterruptedException,记得要处理

八、守护线程-setDaemon

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,java虚拟机退出。

该方法必须在启动线程前调用。