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();//唤醒其它线程,但不立即释放同步锁

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

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

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

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

3.1.8
        wait(long),才参数的wait()方法功能是:等待某一时间内是否有线程对其唤醒,超过此时间就自动唤醒。
        此方法释放同步锁,相当于一个释放锁版的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()方法实现。

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类的对象,在一个新的线程中就会相当于重新创建一个键值对。当然了,我们在调用的时候,不用关心          这个键。get方法就是获取键对应的值,set就是为键设置值。
        重要的是:在不同的线程中,ThreadLocal对象重置!如果默认值是一个表达式,那么会重新计算值。

3.3.3
        继承ThreadLocal类,重写initialValue()方法。get()返回值就是此键值对的默认值


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

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

猜你喜欢

转载自blog.csdn.net/yanluandai1985/article/details/81195485