狐言不胡言

导航

线程协作的三种方式

线程之间需要进行通信,通信有数据共享和线程协作两种方式,这篇主要说线程协作的内容。

一:数据共享

1:文件共享;2:网络共享;3:变量共享。

二:线程协作

        先来个场景:落魄程序员摆摊卖起了炒粉,起先有人去买炒粉,发现炒粉卖完了,只能失落的回家了;后来为了不让客户白来一趟,落魄程序员想到了一个办法,线上预定。要是没有炒粉了,客户就不要白跑了,要是炒粉做好了,就通知客户。

2.1 被弃用的suspend和resume

suspend会让当前线程挂起,resume会唤醒当前线程。那么,举个栗子先:

 /** 炒粉对象 */
    public static Object obj;

    public static void main(String[] args) throws InterruptedException {
        Demo_Suspend suspend = new Demo_Suspend();
        suspend.test1();
    }

    //不会死锁
    public void test1() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                    //没有买到炒粉,就挂起等待
                    Thread.currentThread().suspend();
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        //炒粉做好了,就唤醒客户
        thread.resume();
        System.out.println("落魄程序员做好了炒粉,通知客户。");
    }

上述代码运行结果如下:

第二个栗子:

//死锁,加了synchronized关键字
   public void test2() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    synchronized (Demo_Suspend.class) {
                        System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                        //没有买到炒粉,就挂起等待
                        Thread.currentThread().suspend();
                    }
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        synchronized (Demo_Suspend.class) {
            //炒粉做好了,就唤醒客户
            thread.resume();
            System.out.println("落魄程序员做好了炒粉,通知客户。");
        }
    }

运行结果:

       发现线程一直挂起了,区别就是加了synchronized锁,客户买不到就把自己关了起来,想要通知到客户,就要拿到钥匙,可是客户把钥匙也拿起来了,所以就通知不到,造成了死锁。

      注意:那是因为在synchronized中没有释放锁这个语义的,所以锁不能够进行释放,别人也就获取不到,然后就是死锁了

第三个栗子:

//先唤醒,后挂起,会造成死锁
    public void test3() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    try {
                        Thread.sleep(5000);
                        System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                        //没有买到炒粉,就挂起等待
                        Thread.currentThread().suspend();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        //炒粉做好了,就唤醒客户
        thread.resume();
        System.out.println("落魄程序员做好了炒粉,通知客户。");
    }

上述例子呢,落魄程序员2点就把炒粉做好了,去通知客户,可是客户5点了还在睡觉,运行结果如下:

同样的,会造成死锁。

2.2 wait和notify,notifyAll

      wait会让当前线程挂起,而且当线程调用wait之后,会自动释放锁,notify,notifyAll会唤醒线程,wait和notify,notifyAll只能用在synchronized关键字中,而且必须是同一个对象锁,否则会报java.lang.IllegalMonitorStateException异常。

又来栗子了:

 public static Object obj;

    public static void main(String[] args) throws InterruptedException {
        Demo_Wait wait = new Demo_Wait();
        wait.test1();

    }

    //正常,不会死锁
    public void test1() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    synchronized (Demo_Wait.class) {
                        try {
                            System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                            Thread.currentThread().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        synchronized (Demo_Wait.class) {
            Thread.currentThread().notifyAll();
            System.out.println("落魄程序员做好了炒粉,通知客户。");
        }
    }
运行结果如下:

下一个栗子:

//先唤醒后挂起会死锁
    public void test2() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    synchronized (Demo_Wait.class) {
                        try {
                            Thread.sleep(6000);
                            System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                            Thread.currentThread().wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        synchronized (Demo_Wait.class) {
            Thread.currentThread().notifyAll();
            System.out.println("落魄程序员做好了炒粉,通知客户。");
        }
    }

 同样的,落魄程序员2点就把炒粉做好了,去通知客户,可是客户6点了还在睡觉,造成了死锁。运行结果如下:

2.3 park和unpark

park会让线程挂起,unpark唤醒线程。话不多说,栗子来啦:

public static Object obj;

    public static void main(String[] args) throws InterruptedException {
        Demo_park park = new Demo_park();
        park.test1();
    }

    //不会死锁
    public void test1() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                    //没有买到炒粉,就挂起等待
                    LockSupport.park();
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        //炒粉做好了,就唤醒客户
        LockSupport.unpark(thread);
        System.out.println("落魄程序员做好了炒粉,通知客户。");
    }

运行结果如下:

第二个栗子:

 //先唤醒后挂起不会死锁
    public void test2() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    try {
                        Thread.sleep(6000);
                        System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                        //没有买到炒粉,就挂起等待
                        LockSupport.park();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        LockSupport.unpark(thread);
        System.out.println("落魄程序员做好了炒粉,通知客户。");
    }

运行结果:

       发现,先唤醒后挂起并不会死锁,原因是park,unpark是许可的意思,也就是说你只要有了许可证,就可以通过,若unpark多次,只当作一次,后续过来的park会继续等待。

第三个栗子:

//加了sync会死锁
    public void test3() throws InterruptedException {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                while (obj == null) {
                    synchronized (Demo_park.class) {
                        System.out.println("客户来预定炒粉,炒粉卖完了,不开心。。。");
                        LockSupport.park();
                    }
                }
                System.out.println("客户买到了炒粉,开心回家吸粉了!!!");
            }
        });

        thread.start();
        Thread.sleep(2000);
        obj = new Object();
        synchronized (Demo_park.class) {
            LockSupport.unpark(thread);
            System.out.println("落魄程序员做好了炒粉,通知客户。");
        }
    }

运行结果如下:

同样的,park和unpark加了synchronized关键字会造成死锁。

2.4 总结

协作方式 加synchronized关键字 先唤醒后挂起
suspend/resume 死锁 死锁
wait/notify,notifyAll 不会死锁 死锁
park/unpark 死锁 不会死锁

 

posted on 2021-04-16 15:05  狐言不胡言  阅读(239)  评论(0编辑  收藏  举报