美文网首页Java-多线程
Java并发编程 理解Thread和Object类中线程相关方法

Java并发编程 理解Thread和Object类中线程相关方法

作者: 香沙小熊 | 来源:发表于2020-12-23 18:36 被阅读0次
    1. 为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里?而sleep定义在Thread类里?
    2. 用3种方式实现生产者模式
    3. Join和sleep和wait期间线程的状态分别是什么?为什么?

    1. wait,notify,notifyAll方法

    作用、用法:阻塞阶段、唤醒阶段、遇到中断

    1.1阻塞阶段
    • 另一个线程调用该对象的notify()方法且刚好被唤醒的是本线程。
    • 另一个线程调用该对象的notifyAll()方法
    • 过来wait(long timeout)规定的超时时间,如果传入0就是永久等待。
    • 线程自身调用了interrupt()。

    wait,notify,notifyAll作用、方法

    • 阻塞阶段
    • 唤醒阶段
    • 遇到中断

    wait、notify

    /**
     * 描述:     展示wait和notify的基本用法 1. 研究代码执行顺序 2. 证明wait释放锁
     */
    public class Wait {
    
        public static Object object = new Object();
    
        public static void main(String[] args) throws InterruptedException {
            Thread1 thread1 = new Thread1();
            Thread2 thread2 = new Thread2();
            thread1.start();
            Thread.sleep(200);
            thread2.start();
        }
    
        static class Thread1 extends Thread {
    
            @Override
            public void run() {
                synchronized (object) {
                    System.out.println(Thread.currentThread().getName() + "开始执行了");
                    try {
                        object.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁。");
                }
            }
        }
    
        static class Thread2 extends Thread {
    
            @Override
            public void run() {
                synchronized (object) {
                    object.notify();
                    System.out.println("线程" + Thread.currentThread().getName() + "调用了notify()");
                }
            }
        }
    }
    
    Thread-0开始执行了
    线程Thread-1调用了notify()
    线程Thread-0获取到了锁。
    
    notifyAll
    /**
     * 描述:     3个线程,线程1和线程2首先被阻塞,线程3唤醒它们。notify, notifyAll。 start先执行不代表线程先启动。
     */
    public class WaitNotifyAll implements Runnable {
    
        private static final Object resourceA = new Object();
    
    
        public static void main(String[] args) throws InterruptedException {
            Runnable r = new WaitNotifyAll();
            Thread threadA = new Thread(r);
            Thread threadB = new Thread(r);
            Thread threadC = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (resourceA) {
                        resourceA.notifyAll();
    //                    resourceA.notify();
                        System.out.println("ThreadC notified.");
                    }
                }
            });
            threadA.start();
            threadB.start();
            Thread.sleep(200);
            threadC.start();
        }
        @Override
        public void run() {
            synchronized (resourceA) {
                System.out.println(Thread.currentThread().getName()+" got resourceA lock.");
                try {
                    System.out.println(Thread.currentThread().getName()+" waits to start.");
                    resourceA.wait();
                    System.out.println(Thread.currentThread().getName()+"'s waiting to end.");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    Thread-0 got resourceA lock.
    Thread-0 waits to start.
    Thread-1 got resourceA lock.
    Thread-1 waits to start.
    ThreadC notified.
    Thread-1's waiting to end.
    Thread-0's waiting to end.
    
    如果将resourceA.notifyAll() 改为resourceA.notify() 输出结果
    Thread-0 got resourceA lock.
    Thread-0 waits to start.
    Thread-1 got resourceA lock.
    Thread-1 waits to start.
    ThreadC notified.
    Thread-0's waiting to end.
    
    wait只释放当前的那把锁
    public class WaitNotifyReleaseOwnMonitor {
    
        private static volatile Object resourceA = new Object();
        private static volatile Object resourceB = new Object();
    
        public static void main(String[] args) {
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (resourceA) {
                        System.out.println("ThreadA got resourceA lock.");
                        synchronized (resourceB) {
                            System.out.println("ThreadA got resourceB lock.");
                            try {
                                System.out.println("ThreadA releases resourceA lock.");
                                resourceA.wait();
    
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
            });
    
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    synchronized (resourceA) {
                        System.out.println("ThreadB got resourceA lock.");
                        System.out.println("ThreadB tries to resourceB lock.");
    
                        synchronized (resourceB) {
                            System.out.println("ThreadB got resourceB lock.");
                        }
                    }
                }
            });
    
            thread1.start();
            thread2.start();
        }
    }
    
    ThreadA got resourceA lock.
    ThreadA got resourceB lock.
    ThreadA releases resourceA lock.
    ThreadB got resourceA lock.
    ThreadB tries to resourceB lock.
    

    2.生产者消费者设计模式

    产生数据的模块,就形象地称为生产者;而处理数据的模块,就称为消费者。
    单单抽象出生产者和消费者,还够不上是生产者/消费者模式。该模式还需要有一个缓冲区处于生产者和消费者之间,作为一个中介。生产者把数据放入缓冲区,而消费者从缓冲区取出数据。

    为什么要使用生产者和消费者模式

    • 解耦:
      假设生产者和消费者分别是两个类。如果让生产者直接调用消费者的某个方法,那么生产者对于消费者就会产生依赖(也就是耦合)。将来如果消费者的代码发生变化,可能会影响到生产者。而如果两者都依赖于某个缓冲区,两者之间不直接依赖,耦合也就相应降低了。
    • 支持并发:
      生产者直接调用消费者的某个方法,还有另一个弊端。由于函数调用是同步的(或者叫阻塞的),在消费者的方法没有返回之前,生产者只好一直等在那边。万一消费者处理数据很慢,生产者就会白白糟蹋大好时光。
      使用了生产者/消费者模式之后,生产者和消费者可以是两个独立的并发主体(常见并发类型有进程和线程两种)。生产者把制造出来的数据往缓冲区一丢,就可以再去生产下一个数据。基本上不用依赖消费者的处理速度。其实当初这个模式,主要就是用来处理并发问题的。
    • 支持忙闲不均:
      缓冲区还有另一个好处。如果制造数据的速度时快时慢,缓冲区的好处就体现出来了。当数据制造快的时候,消费者来不及处理,未处理的数据可以暂时存在缓冲区中。等生产者的制造速度慢下来,消费者再慢慢处理掉。
      环形缓冲区(减少了内存分配的开销)
      双缓冲区(减少了同步/互斥的开销)
    /**
     * 描述:     用wait/notify来实现生产者消费者模式
     */
    public class ProducerConsumerModel {
        public static void main(String[] args) {
            EventStorage eventStorage = new EventStorage();
            Producer producer = new Producer(eventStorage);
            Consumer consumer = new Consumer(eventStorage);
            new Thread(producer).start();
            new Thread(consumer).start();
        }
    }
    
    class Producer implements Runnable {
    
        private final EventStorage storage;
    
        public Producer(
                EventStorage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                storage.put();
            }
        }
    }
    
    class Consumer implements Runnable {
    
        private final EventStorage storage;
    
        public Consumer(
                EventStorage storage) {
            this.storage = storage;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 100; i++) {
                storage.take();
            }
        }
    }
    
    class EventStorage {
    
        private final int maxSize;
        private final LinkedList<Date> storage;
    
        public EventStorage() {
            maxSize = 10;
            storage = new LinkedList<>();
        }
    
        public synchronized void put() {
            while (storage.size() == maxSize) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            storage.add(new Date());
            System.out.println("仓库里有了" + storage.size() + "个产品。");
            notify();
        }
    
        public synchronized void take() {
            while (storage.size() == 0) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("拿到了" + storage.poll() + ",现在仓库还剩下" + storage.size());
            notify();
        }
    }
    
    
    仓库里有了1个产品。
    仓库里有了2个产品。
    仓库里有了3个产品。
    仓库里有了4个产品。
    仓库里有了5个产品。
    仓库里有了6个产品。
    仓库里有了7个产品。
    仓库里有了8个产品。
    仓库里有了9个产品。
    仓库里有了10个产品。
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下9
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下8
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下7
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下6
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下5
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下4
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下3
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下2
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下1
    拿到了Wed Dec 23 11:44:11 CST 2020,现在仓库还剩下0
    仓库里有了1个产品。
    仓库里有了2个产品。
    ...省略
    
    为什么wait()方法要放在同步块中

    防止出现Lost Wake-Up Problem (防止一直wait中)
    https://blog.csdn.net/zl1zl2zl3/article/details/89236983
    https://www.cnblogs.com/xiohao/p/7118102.html

    为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类里
    • 这些方法存在于同步中;
    • 使用这些方法必须标识同步所属的锁;
    • 锁可以是任意对象,所以任意对象调用方法一定定义在Object类中。
    • 一个线程可以加多把锁

    3. sleep方法不释放锁

    • 包括synchronized和lock
    • 和wait不同
    /**
     * 展示线程sleep的时候不释放synchronized的monitor,等sleep时间到了以后,正常结束后才释放锁
     */
    public class SleepDontReleaseMonitor implements Runnable {
    
        public static void main(String[] args) {
            SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
            new Thread(sleepDontReleaseMonitor).start();
            new Thread(sleepDontReleaseMonitor).start();
        }
    
        @Override
        public void run() {
            syn();
        }
    
        private synchronized void syn() {
            System.out.println("线程" + Thread.currentThread().getName() + "获取到了monitor。");
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程" + Thread.currentThread().getName() + "退出了同步代码块");
        }
    }
    
    线程Thread-1获取到了monitor。
    线程Thread-1退出了同步代码块
    线程Thread-0获取到了monitor。
    线程Thread-0退出了同步代码块
    
    public class SleepDontReleaseLock implements Runnable {
    
        private static final Lock lock = new ReentrantLock();
    
        public static void main(String[] args) {
            SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
            new Thread(sleepDontReleaseLock).start();
            new Thread(sleepDontReleaseLock).start();
        }
    
        @Override
        public void run() {
            lock.lock();
            System.out.println("线程" + Thread.currentThread().getName() + "获取到了锁");
            try {
                Thread.sleep(5000);
                System.out.println("线程" + Thread.currentThread().getName() + "已经苏醒");
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }
    }
    
    线程Thread-0获取到了锁
    线程Thread-0已经苏醒
    线程Thread-1获取到了锁
    线程Thread-1已经苏醒
    
    3.1 sleep方法响应中断
    1. 抛出InterruptedException
    2. 清除中断状态
    public class SleepInterrupted implements Runnable {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new SleepInterrupted());
            thread.start();
            Thread.sleep(6500);
            thread.interrupt();
        }
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println(new Date());
                try {
                    TimeUnit.HOURS.sleep(3);
                    TimeUnit.MINUTES.sleep(25);
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    System.out.println("我被中断了!");
                    e.printStackTrace();
                }
            }
        }
    }
    
    Wed Dec 23 16:24:15 CST 2020
    我被中断了!
    Wed Dec 23 16:24:22 CST 2020
    java.lang.InterruptedException: sleep interrupted
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:340)
        at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
        at com.kpioneer.thread.threadcoreknowledge.threadobjectclasscommonmethods.SleepInterrupted.run(SleepInterrupted.java:24)
        at java.lang.Thread.run(Thread.java:748)
    

    总结:sleep方法可以让线程进入到Waiting状态,并且不占用CPU资源,但是不释放锁,直到规定时间后再执行,休眠期间如果被中断,会抛出异常并清除中断状态。

    wait/notify 和 sleep 方法的异同
    相同点:
    • 它们都可以让线程阻塞。
    • 它们都可以响应 interrupt 中断:在等待的过程中如果收到中断信号,都可以进行响应,并抛出 InterruptedException 异常。
    不同点:
    • wait 方法必须在 synchronized 保护的代码中使用,而 sleep 方法并没有这个要求。
    • 在同步代码中执行 sleep 方法时,并不会释放 monitor 锁,但执行 wait 方法时会主动释放 monitor 锁。
    • sleep 方法中会要求必须定义一个时间,时间到期后会主动恢复,而对于没有参数的 wait 方法而言,意味着永久等待,直到被中断或被唤醒才能恢复,它并不会主动恢复。
    • wait/notify 是 Object 类的方法,而 sleep 是 Thread 类的方法。

    4. join方法

    4.1 join方法作用、用法

    作用:因为新的线程加入了我们,所以我们需要等他执行完再出发
    用法:main等待thread1执行完毕,注意谁等谁

    4.2 join普通用法
    public class Join {
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "执行完毕");
                }
            });
            Thread thread2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "执行完毕");
                }
            });
    
            thread.start();
            thread2.start();
            System.out.println("开始等待子线程运行完毕");
            thread.join();
            thread2.join();
            System.out.println("所有子线程执行完毕");
        }
    }
    
    开始等待子线程运行完毕
    Thread-1执行完毕
    Thread-0执行完毕
    所有子线程执行完毕
    
    4.3 join遇到中断
    public class JoinInterrupt {
        public static void main(String[] args) {
            Thread mainThread = Thread.currentThread();
            Thread thread1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        mainThread.interrupt();
                        Thread.sleep(5000);
                        System.out.println("Thread1 finished.");
                    } catch (InterruptedException e) {
                        System.out.println("子线程中断");
                    }
                }
            });
            thread1.start();
            System.out.println("等待子线程运行完毕");
            try {
                thread1.join();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "主线程中断了");
            }
            System.out.println("子线程已运行完毕");
        }
    
    }
    
    等待子线程运行完毕
    main主线程中断了
    子线程已运行完毕
    Thread1 finished.
    

    注意: 发现 子线程已运行完毕又打印了Thread1 finished. 说明子线程 并没有运行完毕

    优化:在异常中指定子线程中断thread1.interrupt()

            try {
                thread1.join();
            } catch (InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "主线程中断了");
                thread1.interrupt();
            }
    

    树池

    等待子线程运行完毕
    main主线程中断了
    子线程已运行完毕
    子线程中断
    

    Thread1 finished. 没有打印符合结果

    4.4 在join期间,线程到底是什么状态
    /**
     * 描述:     先join再mainThread.getState()
     * 通过debugger看线程join前后状态的对比
     */
    public class JoinThreadState {
        public static void main(String[] args) throws InterruptedException {
            Thread mainThread = Thread.currentThread();
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(3000);
                        System.out.println(mainThread.getState());
                        System.out.println("Thread-0运行结束");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
            System.out.println("等待子线程运行完毕");
            thread.join();
            System.out.println("子线程运行完毕");
    
        }
    }
    
    等待子线程运行完毕
    WAITING
    Thread-0运行结束
    子线程运行完毕
    
    
    4.5 join等价实现
    /**
     * 描述:     通过讲解join原理,分析出join的代替写法
     */
    public class JoinPrinciple {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "执行完毕");
                }
            });
    
            thread.start();
            System.out.println("开始等待子线程运行完毕");
    //        thread.join();
            synchronized (thread) {
                thread.wait();
            }
            System.out.println("所有子线程执行完毕");
        }
    }
    
    开始等待子线程运行完毕
    Thread-0执行完毕
    所有子线程执行完毕
    

    5 yield方法详解

    作用:释放我的CPU时间片
    定位:JVM不保证遵循
    yield 线程可能再次被调度

    特别感谢

    悟空

    相关文章

      网友评论

        本文标题:Java并发编程 理解Thread和Object类中线程相关方法

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