复习:
1.实现多线程的方法到底有1种还是2种还是4种?
2.怎样才是正确的线程启动方式?
3.如何正确停止线程?(难点)
4.线程的一生-6个状态(生命周期)
学习目标:
1.为什么线程通信的方法wait(),notify()和notifyAll()被定义在Object类?而sleep定义在Thread类里?
2.用3种方式实现生产者模式
3.JavaSE8和Java1.8和JDK 8 是什么关系,是同一个东西吗?
4.Join 和sleep和wait期间线程的状态分别是什么?为什么?
wait,notify,notifyAll
阻塞阶段->唤醒阶段->
在执行上述几种方法时,首先,我们是必须得先得到monitor即获得synchronized锁。才能执行上述几种方法。
其次,我们只能释放其中一个锁。
最后,这些方法是任何对象都可以调用的。
在持有多把锁的时候,要注意,该如何释放,释放谁,释放的时间等等。
被唤醒的4种情况:
注意:wait只会释放,其自己的锁。
package ThreadCommonMethod; /** * @program:多线程和IO * @descripton:展示wait和notify的使用方法 * @author:ZhengCheng * @create:2021/9/17-13:05 **/ public class Wait { static Object object = new Object(); static class Thread1 extends Thread{ @Override public void run() { synchronized (object){ System.out.println(Thread.currentThread().getName()+"Begin"); 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"); } } } public static void main(String[] args) throws InterruptedException { Thread1 thread1 = new Thread1(); Thread2 thread2 = new Thread2(); thread1.start(); Thread.sleep(200); thread2.start(); } }
演示NotifyAll
package ThreadCommonMethod; /** * @program:多线程和IO * @descripton: * @author:ZhengCheng * @create:2021/9/17-13:30 **/ public class NotifyAll { static Object ob = new Object(); static class Thread1 implements Runnable{ @Override public void run() { synchronized (ob){ System.out.println(Thread.currentThread().getName()+"开始了"); try { ob.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread().getName()+"被唤醒了"); } } static class Thread3 extends Thread{ @Override public void run() { synchronized (ob){ ob.notifyAll(); System.out.println(Thread.currentThread().getName()+"唤醒了所有线程"); } } } public static void main(String[] args) throws InterruptedException { Thread1 thread1 = new Thread1(); Thread t1 = new Thread(thread1); Thread t2 = new Thread(thread1); Thread3 thread3 = new Thread3(); t1.start(); t2.start(); Thread.sleep(1000); thread3.run(); } }
学习到了Notify、wait。我们可以尝试实现生产者和消费者模式。
那么首先,什么是生产者和消费者模式呢?
手写生产者消费者模式。尝试用自己的方式来完成。
答案:
package 消费者生产者模式; import java.util.ArrayList; import java.util.Date; import java.util.List; /** * @program:多线程和IO * @descripton:用wait和notify,不用阻塞队列。 * @author:ZhengCheng * @create:2021/9/18-9:46 **/ public class DemoAnswer { public static void main(String[] args) { EventStorage s = new EventStorage(); Produ produ = new Produ(s); Consumer consumer = new Consumer(s); Thread thread1 = new Thread(produ); Thread thread2 = new Thread(consumer); thread1.start(); thread2.start(); } static class Produ implements Runnable{ private EventStorage storage ; public Produ(EventStorage storage) { this.storage = storage; } @Override public void run() { for (int i = 0; i < 100; i++) { storage.put(); } } } static class Consumer implements Runnable{ private EventStorage storage ; public Consumer(EventStorage storage) { this.storage = storage; } @Override public void run() { for (int i = 0; i < 100; i++) { storage.take(); } } } static class EventStorage{ private int Max_size; private List<Date> storage; public EventStorage() { Max_size = 10; this.storage = new ArrayList<>(); } public synchronized void put(){ while (storage.size()==Max_size){ 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(); } } Date d = storage.remove(0); System.out.println("消费者消费了"+d); notify(); } } }
自己的写法日后再看问题在哪
题目:使用多线程,交替打印0-99
package ThreadKN.交替打印100; /** * @program:多线程和IO * @descripton:两个线程交替打印 * @author:ZhengCheng * @create:2021/9/17-22:14 **/ public class demo { static int a =0; static Object lock = new Object(); public static void main(String[] args) { d1 d1 = new d1(); d2 d2 = new d2(); Thread thread1 = new Thread(d1); Thread thread2 = new Thread(d2); thread1.start(); thread2.start(); } static class d1 implements Runnable{ public d1() { } @Override public void run() { print(); } } static void print(){ while (a<100){ synchronized (lock){ System.out.println(a+++Thread.currentThread().getName()); lock.notify(); if (a<100){ try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } static class d2 implements Runnable{ public d2() { } @Override public void run() { print(); } } }
深入理解,为什么要先notify然后再lock。这个锁对象究竟是如何操作的?明天好好想想。
该锁是如何释放的?又是如何被另一个线程竞争到的?
常见问题:
两个线程交替打印0~100的奇偶数。
手写生产者消费者(线程之间的通信)
为什么wait()需要在同步代码块中,而为什么sleep不需要?
为什么线程里的wait、Notify、NotifyAll是在Object类,而sleep是定义在Thread类里的。
那如果真的调用了Thread.wait怎么样?
会比较混乱。因为首先Thread类是继承与Object,可以使用wait方法,但是我们知道,使用一个线程作为锁对象,会在操作上陷入逻辑上的混乱。
用suspend和resume来阻塞线程可以吗?为什么?
什么是可重入锁?
举例子:在class A中有两个同步方法。如下:
package SynchronizedLock; /** * @program:多线程和IO * @descripton: * @author:ZhengCheng * @create:2021/9/18-13:54 **/ public class LockA { public synchronized void a(){ b(); } public synchronized void b(){ System.out.println("。"); } }
我们都知道,在同步方法中,我们要执行该方法,都要获取锁,而同步方法的锁就是LockA.class,那么在执行方法a时,某一线程获得了锁对象,进入方法体,要执行b这个同步方法,此时,该线程又能够得到这把锁,进入方法b中。这样重复得到方式的锁成为可重入锁。
线程执行synchronized同步代码块时再次重入该锁过程中抛异常,是否会释放锁 - rhyme - 博客园
关于不同的休眠方式,对于wait,线程一旦wait就会自动释放掉moniter,而sleep不会。我们常用Thread.sleep来实现睡眠操作,但是我们以后可以尝试使用TimeUnit.SECONDS.sleep();
追其源码,其实现方式是相同的。
对于TimeUnit. .sleep,我们更方便通过对中间时间单位的更换,设计我们所需要暂停的时间。因为其底层,设计了毫秒值帮我们转化的方法。其次,在TimeUnit中,会有一个判断,如果毫秒值小于0,他是不会运行的,不会抛出异常,如果是Thread.sleep,那么是会抛出一个异常,从而中止我们的程序。