美文网首页
Java多线程 ----(3)线程间通讯

Java多线程 ----(3)线程间通讯

作者: 艾剪疏 | 来源:发表于2018-08-24 11:36 被阅读24次

    1 同步生效的条件
    2 wait();notify();notifyAll();三个方法
    3 多线程之间的交互通信易存在的问题(难点)
    4 线程停止
    5 守护线程
    6 join方法
    7 线程优先级

    1 同步生效的条件

    先看一段铺垫代码,这个代码很简单,但有2个地方需要注意。

    • 多线程意味着输入和输出都需要被同步(存在多个线程);
    • 注意多线程要使用相同的锁,这个锁是输入程序和输出程序共同使用的Resource对象;
    public class ThreadCommunication {
        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{
        String name;
        String sex;
        boolean flag = false;
    }
    
    public class InputResource implements Runnable {
    
        private Resource r;
    
        InputResource(Resource r) {
            this.r = r;
        }
    
        @Override
        public void run() {
            int x = 0;
            while (true) {
                synchronized (r) {
                    if (x == 0) {
                        r.name = "james";
                        r.sex = "man";
                    } else {
                        r.name = "丽丽";
                        r.sex = "女";
                    }
                    x = (x + 1) % 2;
                }
            }
        }
    }
    
    public class OutPutResource implements Runnable{
    
        private Resource r;
        OutPutResource(Resource r){
            this.r = r;
        }
        public void run() {
            while(true){
                synchronized(r){
                    System.out.println(r.name+"::::::::::::::"+r.sex);
                }
            }
        }
    }
    

    注意这两点之后,程序正确,输出正常。


    image.png

    2 wait();notify();notifyAll();三个方法

    上面虽然执行正常,但并不是希望的效果,因为应该是一次输入对应一次输出。所以这时就需要使用wait和
    notify来对线程进行睡眠和唤醒操作。

    代码如下:

    public class InputResource_1 implements Runnable {
    
        private Resource r;
    
        InputResource_1(Resource r) {
            this.r = r;
        }
    
        @Override
        public void run() {
            int x = 0;
            while (true) {
                synchronized (r) {
                    if (r.flag) {
                        try {
                            r.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    //首先执行false,赋值操作
                    if (x == 0) {
                        r.name = "james";
                        r.sex = "man";
                    } else {
                        r.name = "丽丽";
                        r.sex = "女";
                    }
                    x = (x + 1) % 2;
                    r.flag = true;//flag置为true之后,output被notify,input被wait
                    r.notify();
                }
            }
        }
    }
    
    public class OutPutResource_1 implements Runnable {
    
        private Resource r;
    
        OutPutResource_1(Resource r) {
            this.r = r;
        }
    
        @Override
        public void run() {
            while (true) {
                synchronized (r) {
                    if (!r.flag) {
                        try {
                            r.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(r.name + "::::::::::::::" + r.sex);
                    r.flag = false;
                    r.notify();
                }
            }
        }
    }
    
    image.png

    这里的只wait和notify等待和唤醒必须是同一个锁。

    3 多线程之间的交互通信易存在的问题(难点)

    例子的要求是:生产者生产一个,消费者就消费一个,生产和消费一一对等。
    代码如下:

    public class ProducerAndConsumer {
    
        public static void main(String[] args){
            ResourcesGS r = new ResourcesGS();
            Producer p = new Producer(r);
            Consumer c = new Consumer(r);
            Thread t1 = new Thread(c);
            Thread t2 = new Thread(c);
            Thread t3 = new Thread(p);
            Thread t4 = new Thread(p);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    public class ResourcesGS {
    
        private String name;
        private int count = 1;
        private boolean flag = false;
    
        public synchronized void set(String name){
            if(flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            this.name = name +"---"+ count++;
            System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
            flag = true;
            this.notify();
        }
    
        public synchronized void out(){
            if(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
            flag = false;
            this.notify();
        }
    }
    public class Producer implements Runnable{
    
        private ResourcesGS r;
        Producer(ResourcesGS r){
            this.r = r;
        }
        @Override
        public void run() {
            while(true){
                r.set("producer");
            }
        }
    }
    public class Consumer implements Runnable{
    
        private ResourcesGS r;
        Consumer(ResourcesGS r){
            this.r = r;
        }
        @Override
        public void run() {
            while(true){
                r.out();
            }
        }
    }
    
    

    大部分代码都是和谐状态,但是的确存在错误现象:生产一次,消费多次,生产多次,消费一次。


    image.png

    首先从代码来分析,主要是ResourcesGS中的代码

    public class ResourcesGS {
    
        private String name;
        private int count = 1;
        private boolean flag = false;
    
        public synchronized void set(String name){
            if(flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
    
            this.name = name +"---"+ count++;
            System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
            flag = true;
            this.notify();
        }
    
        public synchronized void out(){
            if(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
            flag = false;
            this.notify();
        }
    }
    

    t1,t2是生产者,t3,t4是消费者。
    假设:

    • t1抢到执行权,首先“生产1”,然后置标记为false,notify剩下的三个线程t2,t3,t4全部被唤醒,接着wait()等待;
    • 如果这时t2抢到执行权,标记为true,wait()等待。那么就意味这等待队列中现在是t1,t2的排序。
    • t3抢到执行权,执行代码"消费1",然后置标记为true,notify了t1,因为t4本来没睡,所以t4抢到执行权,标记为true,直接wait。第一次的生产消费是和谐状态。
    • 这时t1抢到执行权,“生产2”,然后置标记为false,t1执行notify,这时根据队列中等待线程的排列顺序,t2被唤醒(出错点),这时t2不用判断标记,直接继续向下执行,“生产3”。这里就连续生产了2次。

    这时就会导致生产两次,消费一次的现象。

    那么怎么解决呢?
    问题的核心是:t2没有判断标记,直接继续执行了"生产"的代码。只需要让t2一直判断标记即可。就是将if判断改为while判断。这时,即使t2醒了也需要在重新判断标记,所以可以修复改bug。
    但是修改后运行结果如下:


    image.png

    只走了前三个线程,程序就停止了。

    假设t2这时醒了,然后进行while语句的判断,这时标记为true,然后将会被wait(),直接使用object类中的notifyAll()方法唤醒所有的线程即可。这时,即使t2也睡了,但是t3和t4全部被唤醒,可以继续向下执行,程序就会继续执行了。
    代码如下:

    class ResourcesGS{
        private String name;
        private int count = 1;
        private boolean flag = false;
    
        public synchronized void set(String name){
            while(flag){
                try {
                    this.wait();//t1 t2
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name +"---"+ count++;
            System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
            flag = true;
            this.notifyAll();
        }
    
        public synchronized void out(){
            while(!flag){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
            flag = false;
            this.notifyAll();
        }
    }
    

    最后的结果如下,一切都正常了!


    image.png

    4 线程停止

    stop方法已经过时,现在常见的停止线程的方法如下:

    (1)让运行的线程结束

    代码如下:通过控制标记,结束run方法。

    public class StopThread implements Runnable{
    
        private boolean flag = true;
    
        @Override
        public void run() {
            while (flag){
                System.out.println(Thread.currentThread().getName()+"...run");
            }
        }
    
        public void changeFlag(){
            flag = false;
        }
    
        public static void main(String[] args) {
            StopThread stopThread = new StopThread();
    
            Thread t1 = new Thread(stopThread);
            Thread t2 = new Thread(stopThread);
    
            t1.start();
            t2.start();
    
            int num = 0;
            while(true){
                if(num++ == 60){
                    stopThread.changeFlag();
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"..."+num);
            }
        }
    }
    

    (2)interrupt方法

    该方法不是停止线程,而是中断线程。主要是会将 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)等方法的状态清除。强制让线程恢复到运行状态中,这样就可以继续操作标记让线程结束。

    public class StopThread implements Runnable{
    
        private boolean flag = true;
    
        @Override
        public synchronized void run() {
            while (flag){
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println(Thread.currentThread().getName()+"...run");
                    flag = false;
                }
            }
        }
    
        public void changeFlag(){
            flag = false;
        }
    
        public static void main(String[] args) {
            StopThread stopThread = new StopThread();
    
            Thread t1 = new Thread(stopThread);
            Thread t2 = new Thread(stopThread);
    
            t1.start();
            t2.start();
    
            int num = 0;
            while(true){
                if(num++ == 100){
                    t1.interrupt();
                    t2.interrupt();
                    break;
                }
                System.out.println(Thread.currentThread().getName()+"..."+num);
            }
        }
    }
    

    运行结果如下:抛出异常,通过置标记,停止线程。


    image.png

    5 守护线程

    Java中提供守护线程,特点是:守护线程属于后台线程,而主线程是前台线程,守护线程会在主线程运行结束之后自动停止。

    将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 
    该方法必须在启动线程前调用。 
    public final void setDaemon(boolean on)
    

    刚才的代码,如果将t1和t2都设置为守护线程,那么就不需要人为去设置停止,t1,t2会在主线程运行结束后自动结束。

     t1.setDaemon(true);
     t2.setDaemon(true);
    

    6 join方法

    当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完毕,A才会继续执行。一般join用来临时加入线程执行。

    例如,若这样执行,main、Thread0、Thread1会随机无序的交替执行。

    public class StopThread implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 280; i++) {
                System.out.println(Thread.currentThread().getName()+"...run");
            }
        }
    
        public static void main(String[] args) throws InterruptedException {
            StopThread stopThread = new StopThread();
    
            Thread t1 = new Thread(stopThread);
            Thread t2 = new Thread(stopThread);
    
            t1.start();
            t2.start();
    
            for (int i = 0; i < 280; i++) {
                System.out.println(Thread.currentThread().getName()+"...main");
            }
        }
    }
    
    
    image.png

    若在 t1.start()后面加上 t1.join()。

    t1.start()
    t1.join()
    t2.start()
    

    执行结果如下:


    image.png

    如上所述,main、Thread1(t2)会等Thread0(t1)执行完毕后在执行。

    7 线程优先级

    1 toString()

    返回该线程的字符串表示形式,包括线程名称、优先级和线程组。


    image.png

    2 更改线程优先级

    如上图,所有线程的默认优先级是5,但是可以根据需要,设置线程的优先级,优先级高的cpu在执行时就会优先考虑,执行的机会就更大。

    提供1,5,10三个档次的优先级。

    image.png
    getPriority() 
    setPriority(int newPriority) 
    

    多线程的基础知识总结完毕。
    END

    相关文章

      网友评论

          本文标题:Java多线程 ----(3)线程间通讯

          本文链接:https://www.haomeiwen.com/subject/vtomiftx.html