Java多线程编程核心技术-----第三章读书笔记

3.1.3
        调用wait()或notify()等方法的对象,必须是同步锁对象,否则抛出IllegalMonitorStateException.
        比如同步方法中只能是 this.lock()。

        wait()使当前执行代码的线程进入等待状态(冷冻暂停),并释放同步锁
        sleep方法不释放同步锁。
        调用wait()方法时必须要持有同步锁,即在同步代码中才能正确调用wait(),否则抛出IllegalMonitorStateException。
        wait()是Object类的方法。

        notify()随机使得一个wait状态的线程恢复执行
        notify()需要在同步代码中执行,此线程执行完才释放同步锁。然后被唤醒的线程才有机会去争抢同步锁。
        当没有wait状态的线程,那么就忽略本次操作。
        wait状态的线程必须要得到notify()或者notifyAll()才能被唤醒,否则一直处于wait状态。

        notifyAll()方法唤醒所有wait状态的线程。

        原:wait使线程停止运行,而notify使停止的线程继续执行。P137
        P139例子用意: lock.wait();//释放了同步锁,另外一个线程 lock.notify();//唤醒其它线程,但不立即释放同步锁

        wait()/notify() 要结合 Synchronized 关键字一起使用,因为他们都需要首先获取该对象的对象锁;

        出现线程等待的原因:
                调用sleep主动放弃CPU时间片
                其它线程争抢到了同步锁
                调用了wait()方法进入wait状态

        线程重新进入可执行状态的情况:
                sleep()方法的睡眠时间到了
                线程抢夺到了同步锁
                线程被notify()唤醒了

        另外就是IO阻塞等待用户输入(输入完毕)
        suspend挂起(使用resume恢复)

3.1.5
        wait状态的线程需要使用try catch(InterruptedException e) 进行对线程的中断状态位进行监控。
        一旦此线程的中断状态位被置为true,那么则进入异常处理语句,并中断此线程,并清除状态位。

3.1.8
        wait(long)方法功能是:等待某一时间内是否有线程对其唤醒,超过此时间就自动唤醒。
        此方法释放同步锁,相当于一个释放锁版的sleep方法,当然也可以在时间范围内提前唤醒它。

3.1.11 
        生产者:如果产品生产出来了,等待消费者消费。否则生产并通知消费者消费。
        消费者:如果产品没有生产,那么等待生产者生产。消费完,则通知生产者生产。
        伪代码:
        生产者 if(有产品){lock.wait} 生产 lock.notify  如果有产品,我就休息。不然进行生产,并通知消费者消费。
        消费者 if(没产品){lock.wait} 消费 lock.notify  如果没有产品,我就等待。不然的话就消费,消费完就通知生产。
        抓住两者什么时候wait,什么是notify。
        生产者wait说明有货了,消费者wait说明没货了。主体代码是body,意外情况在前面用if语句加上。
        生产者生产完notify消费者去消费,消费者消费完notify生产者生产。
        总之,先wait后notify的写法是很好的,符合先判断不好的情况。

        多生产多消费出现假死。
        ABCD线程都刚开始都处于 可执行状态,AB是生产者,CD是消费者。
        A刚开始生产了以后,又抢到了锁,因为有货,那就wait。
                操作                      结果           剩余线程
                A生产                 (有货)        (ABCD还醒)
                A运行,A等待。(有货)        (BCD)
                B运行,B等待。(有货)        (CD)
                C消费,唤醒A。(无货)        (ACD)
                C运行,C等待。(无货)        (AD)
                D运行,D等待。(无货)        (A)           
                A生产,唤醒B。(有货)         (AB)
                A运行,A等待。(有货)         (B)
                B运行,B等待。(有货)         (全睡了)

        从上述的例子来看,C唤醒了D,然后是A唤醒了B,出现连续唤醒同类,导致的假死。

        原:假死出现的主要原因是有可能连续唤醒同类,解决办法是将异类也一同唤醒。P165
        把唤醒一个的notify方法修改为notifyAll方法即可。
    
        原:因为条件发生改变时并没有得到及时响应。
        采用while替代if,消费者A消费完唤醒消费者B,消费者B应该重新判断是否有货。

        多个生产者或者多个消费者时,把if换为while,并把唤醒换为notifyAll。
        为什么要换位while?因为比如消费者A消费完了货再唤醒其它线程。此时条件已经变化了,货物已经被消费。
        所以要让新唤醒的消费者再去重新判断一下条件,如果没货,继续wait。如果不是while而是if的话,那么新唤醒的消费者
        则顺序调用消费语句,很可能出现诸如空指针异常等异常。
        notifyAll为了避免出现连续唤醒同类,所导致假死的状况,保证能够唤醒异类

3.2.1
        A线程的任务中出现 B.join(); 那么A线程需要等待B线程任务执行完毕再继续执行。

3.2.2
        join方法内部采用wait()方法实现。

        线程A调用 otherThread.join()方法,那么线程A进入 WAITING 状态内部使用的是 wait()方法实现,所以会释放同步锁

3.2.3
        如果a线程在b.join的过程中,调用的线程a要等待另外一个线程b执行完毕,所以处于wait等待状态。
        如果调用的线程a的中断状态位被设置为true,那么a会出现异常。但是不会影响到b线程的执行。

3.2.5
        this.join(long),源码是this.wait(long),能够调用wait方法的是同步锁,所以this是同步锁。
        也就是抢到了锁,然后又释放了锁,指定时间后自动苏醒。

3.3.1
        原:ThreadLocal实现了每个线程内部都有自己的共享变量。P191
        ThreadLocal实现原理:ThreadLocal相当于一个Map<Thread,Object> map对象。键的话,就是currentThread() ,

值的话,就是你存进去的那个值。

        SimpleDateFormat是线程不安全的,解决方法是为每个线程创建一个sdf对象,而不是共享。

       

    /**
     * 获取simpleDateFormat对象,线程安全
     *
     * @return 返回当前线程绑定的simpleDateFormat对象
     */
    public static SimpleDateFormat getSimpleDateFormat() {
        //创建格式器引用
        SimpleDateFormat sdf;
        //先拿一次看看,private static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<>();
        sdf = t1.get();
        if (null == sdf) {
            //如果是空,说明当前线程第一次调用此方法
            //构造一个SimpleDateFormat对象
            sdf = new SimpleDateFormat();
            //将新创建的sdf放入线程私有变量
            t1.set(sdf);
        }
        return sdf;
    }

       

3.3.3
        继承ThreadLocal类,重写initialValue()方法。创建此MyThreadLocal类对象,调用get()返回值就是此键值对的初始值


3.4.1
        使用InheritableThreadLocal可以让子线程从父线程的键值对中取得值。
        A线程启动了B线程,那么A线程是B线程的父线程。
        也就是A线程中的值,子线程B也能获取到A线程中的键值对的值,而不是重新计算表达式。

3.4.2
        通过重写InheritableThreadLocal的childValue(parentValue)方法,重新定义MyInheritableThreadLocal的键值对。
        A线程启动了B线程,那么A线程是父线程,B线程是A线程的子线程。
        B线程此刻访问自己的键值对的时候,拿的是父类的键值对的值。
        当然,如果重写了childValue()方法,那么拿的就是自己的值了。

 

-----------------------------------------------------------------------------------------------------------------------------------

-----------------------------------------------------------------------------------------------------------------------------------

        消费者生产者 经典代码。

package com.ssi.domains.Add;

/**
 * Created by jay.zhou on 2018/7/24.
 */
public class Product {
    public String state = "无货";

    public void create() {
        state = "有货";
    }

    public void consume() {
        state = "无货";
    }

    public static void main(String[] args) {
        Product product = new Product();
        Object lock = new Object();
        ThreadA threadA = new ThreadA(product,lock);
        ThreadB threadb = new ThreadB(product,lock);
        threadA.start();
        threadb.start();
    }
}

class ThreadA extends Thread {
    private Product product;
    private Object lock;

    public ThreadA(Product product, Object lock) {
        this.product = product;
        this.lock = lock;
    }

    //生产者线程
    @Override
    public void run() {
        while (true) {
            try {
                synchronized (lock) {
                    if (product.state.equals("有货")) {
                        lock.wait();
                    }
                    product.create();
                    System.out.println("生产者生产出货物");
                    lock.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

class ThreadB extends Thread {
    private Product product;
    private Object lock;

    public ThreadB(Product product, Object lock) {
        this.product = product;
        this.lock = lock;
    }

    //消费者线程
    @Override
    public void run() {
        while (true) {
            try {
                synchronized (lock) {
                    if (product.state.equals("无货")) {
                        lock.wait();
                    }
                    product.consume();
                    System.out.println("消费者消费完货物");
                    lock.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

 

posted @ 2022-07-17 12:15  小大宇  阅读(30)  评论(0编辑  收藏  举报