JUC并发编程 第二章 共享模型之管程

大纲

实际测试

使用synchronized关键字解决问题:

@Slf4j
public class Test16 {
    static  int count = 0;
    static Object lock  = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock){
                    count++;
                }

            }
        },"老王");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (lock){
                    count--;
                }
            }
        },"小王");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",count);
    }
}

理解一下:

用图来表示:

用面向对象的方法去改造代码:

@Slf4j
public class Test16 {


    public static void main(String[] args) throws InterruptedException {
        Room room = new Room();
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                room.increment();

            }
        },"老王");
        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
               room.decrement();
            }
        },"小王");
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        log.debug("{}",room.getCount());
    }
}
class Room{
    private  int count = 0;
    public void increment(){
        synchronized (this){
            count++;
        }

    }
    public void decrement(){
        synchronized (this){
            count--;
        }
    }
    public int getCount(){
        synchronized (this){
            return count;
        }
    }
}

线程8锁问题:

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(()->{
            log.debug("begin");
            number.a();
        }).start();
        new Thread(()->{
            log.debug("begin");
            number.b();
        }).start();
    }
}

@Slf4j
class  Number{
    public synchronized void a(){
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }
}

那么这里锁住的都是number对象 这时会出现12或者21的情况;

情况2:

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number number = new Number();
        new Thread(()->{
            log.debug("begin");
            try {
                number.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            log.debug("begin");
            number.b();
        }).start();
    }
}

@Slf4j
class  Number{
    public synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }
}

在a的方法中加入sleep;那么会出现两种情况,,但是因为还是锁同一个this对象,所以还是互斥,需要等待;

情况3:

@Slf4j
public class Test8Locks {
public static void main(String[] args) {
Number number = new Number();
new Thread(()->{
log.debug("begin");
try {
number.a();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
log.debug("begin");
number.b();
}).start();
new Thread(()->{
log.debug("begin");
number.c();
}).start();
}
}

@Slf4j
class Number{
public synchronized void a() throws InterruptedException {
Thread.sleep(1000);
log.debug("1");
}
public synchronized void b(){
log.debug("2");
}
public void c(){
log.debug("3");
}
}

情况4:

@Slf4j
public class Test8Locks {
    public static void main(String[] args) {
        Number number = new Number();
        Number number2 = new Number();
        new Thread(()->{
            log.debug("begin");
            try {
                number.a();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            log.debug("begin");
            number2.b();
        }).start();
//        new Thread(()->{
//            log.debug("begin");
//            number.c();
//        }).start();
    }
}

@Slf4j
class  Number{
    public synchronized void a() throws InterruptedException {
        Thread.sleep(1000);
        log.debug("1");
    }
    public synchronized void b(){
        log.debug("2");
    }
}

由于锁住的不是同一个对象,所以永远都是21

4.4 变量的线程安全分析

@Slf4j
public class Test18 {
    public static void main(String[] args) {
        ThreadUnsafe threadUnsafe = new ThreadUnsafe();
        for (int i = 0; i < 2; i++) {
            new Thread(()->{
                threadUnsafe.method1(200);
            },"thread"+(i+1)).start();
        }
    }
}
class ThreadUnsafe{
    ArrayList<String> list = new ArrayList<>();
    public void method1(int loopNumber){
        for (int i = 0; i < loopNumber; i++) {
            //{临界区,会产生竞态条件}
            method2();
            method3();
        }
    }

    private void method3() {
        list.add("1");
    }

    private void method2() {
        list.remove(0);
    }
}

变为局部变量:

不是安全的
全是安全的

练习题:

TicketWindow

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
//售票窗口
public class TicketWindow {
    private int count;

    public TicketWindow(int count){
        this.count = count;
    }
    //获取余票数量:
    public int getCount(){
        return count;
    }
    //售票:
    public synchronized  int sell(int amount){
        if(this.count >amount){
            this.count -= amount;
            return amount;
        }else {
            return 0;
        }
    }
}

售卖类:

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.Vector;

@Slf4j
public class ExerciseSell {
    public static void main(String[] args) throws InterruptedException {
        //模拟多人买票
        TicketWindow window = new TicketWindow(1000);
        //所有线程的集合
        List<Thread> threadList = new ArrayList<>();

        //卖出的票数统计:
        List<Integer> amountlist = new Vector<>();
        for (int i = 0; i <5000 ; i++) {
            Thread thread = new Thread(() -> {
                //买票
                int sellcount = window.sell(randomAmount());
                try {
                    Thread.sleep(randomAmount());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                amountlist.add(sellcount);
            });
            threadList.add(thread);
            thread.start();
        }

        for (Thread thread : threadList) {
            thread.join();
        }
        //同级卖出的票数和剩余的票数:
        log.debug("余票:{}",window.getCount());
        log.debug("卖出的票数:{}",amountlist.stream().mapToInt(i ->i).sum());


    }

    //Random 为线程安全
    static Random random = new Random();

    //随机1 - 5
    public static int randomAmount(){
        return random.nextInt(5)+1;
    }
}


4.6 Monitor 概念

Java 对象头

以 32 位虚拟机为例

* 原理之 Monitor()

* 原理之 synchronized

小故事

故事角色

老王 – JVM

小南 – 线程

小女 – 线程

房间 – 对象

房间门上 – 防盗锁 – Monitor

房间门上 – 小南书包 – 轻量级锁

房间门上 – 刻上小南大名 – 偏向锁

批量重刻名 – 一个类的偏向锁撤销到达 20 阈值

不能刻名字 – 批量撤销该类对象的偏向锁,设置该类不可偏向

小南要使用房间保证计算不被其它人干扰(原子性),最初,他用的是防盗锁,当上下文切换时,锁住门。这样,

即使他离开了,别人也进不了门,他的工作就是安全的。

但是,很多情况下没人跟他来竞争房间的使用权。小女是要用房间,但使用的时间上是错开的,小南白天用,小女

晚上用。每次上锁太麻烦了,有没有更简单的办法呢?

小南和小女商量了一下,约定不锁门了,而是谁用房间,谁把自己的书包挂在门口,但他们的书包样式都一样,因

此每次进门前得翻翻书包,看课本是谁的,如果是自己的,那么就可以进门,这样省的上锁解锁了。万一书包不是

自己的,那么就在门外等,并通知对方下次用锁门的方式。

后来,小女回老家了,很长一段时间都不会用这个房间。小南每次还是挂书包,翻书包,虽然比锁门省事了,但仍

然觉得麻烦。

于是,小南干脆在门上刻上了自己的名字:【小南专属房间,其它人勿用】,下次来用房间时,只要名字还在,那

么说明没人打扰,还是可以安全地使用房间。如果这期间有其它人要用这个房间,那么由使用者将小南刻的名字擦

掉,升级为挂书包的方式。

同学们都放假回老家了,小南就膨胀了,在 20 个房间刻上了自己的名字,想进哪个进哪个。后来他自己放假回老

家了,这时小女回来了(她也要用这些房间),结果就是得一个个地擦掉小南刻的名字,升级为挂书包的方式。老

王觉得这成本有点高,提出了一种批量重刻名的方法,他让小女不用挂书包了,可以直接在门上刻上自己的名字

后来,刻名的现象越来越频繁,老王受不了了:算了,这些房间都不能刻名了,只能挂书包

* 原理之 synchronized 进阶

轻量级锁:

偏向状态

有延迟,需要等一会
调节参数,不延迟直接加偏向锁
不使用偏向锁那么加锁就是直接轻量级锁

批量重偏向

如果对象虽然被多个线程访问,但没有竞争,这时偏向了线程 T1 的对象仍有机会重新偏向 T2,重偏向会重置对象

的 Thread ID

当撤销偏向锁阈值超过 20 次后,jvm 会这样觉得,我是不是偏向错了呢,于是会在给这些对象加锁时重新偏向至

加锁线程

输出结果:

[t1] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t1] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - ===============> 
[t2] - 0 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 0 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 0 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 1 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 1 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 1 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 2 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 2 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 2 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 3 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 3 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 3 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 4 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 4 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 4 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 5 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 5 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 5 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 6 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 6 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 6 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 7 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 

[t2] - 7 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 7 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 8 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 8 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 8 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 9 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 9 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 9 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 10 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 10 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 10 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 11 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 11 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 11 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 12 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 12 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 12 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 13 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 13 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 13 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 14 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 14 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 14 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 15 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 15 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 15 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 16 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 16 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 16 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 17 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 17 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 17 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 18 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 18 00000000 00000000 00000000 00000000 00100000 01011000 11110111 00000000 
[t2] - 18 00000000 00000000 00000000 00000000 00000000 00000000 00000000 00000001 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 19 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 20 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 21 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 22 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 23 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 24 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 

[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 25 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 26 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 27 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 28 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11100000 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101 
[t2] - 29 00000000 00000000 00000000 00000000 00011111 11110011 11110001 00000101

一批对象的偏向锁锁住的线程id是相同的时候,对他们进行修改的次数超过20 ,那么自21以后,原来那一批对象的偏向锁会自动的偏向之前修改的线程id

批量撤销

4.7 wait notify

没有获得锁,所以不能wait
@Slf4j
public class Test22 {
    static  final  Object lock = new Object();
    public static void main(String[] args) throws InterruptedException {
        new Thread(()->{

            synchronized (lock){
                log.debug("执行wait");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他的代码");
            }
        },"t1").start();

        new Thread(()->{

            synchronized (lock){
                log.debug("执行wait");
                try {
                    lock.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                log.debug("其他的代码");
            }
        },"t2").start();
        Thread.sleep(2000);
        log.debug("唤醒lock上其他线程");
        synchronized (lock){
           // lock.notify(); //唤醒lock上一个线程
            lock.notifyAll();//唤醒lock上所有的线程
        }
    }
}

可以自己选择时间wait

wait和sleep的区别,wait会释放锁,sleep则不会

具体代码

思考:要是还有其他线程呢,会不会叫错了:

换成了notifAll() ,但是都唤醒了,还有没有更好的方案呢
使用while
@Slf4j
public class Test1111 {
    //线程1等待线程2的下载结果
    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        new Thread(()->{
            //等待结果
            log.debug("等待结果");
            Vector<String> list1 = (Vector<String>)guardedObject.get();
            log.debug("结果",list1.size());
        },"t1").start();

        new Thread(()->{
            log.debug("执行代码");
            Vector<String> strings = new Vector<>();
            strings.add("yxx");
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardedObject.complete(strings);
        },"t2").start();
    }

}
class GuardedObject{
    //结果
    private Object response;

    //获取结果:
    public Object get(){
        synchronized (this){
            while( response  == null){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    return  response;
    }

    //产生结果:
    public void complete(Object response){
        //给结果成员变量赋值
        synchronized (this){
            this.response =response;
            this.notifyAll();
        }
    }
}

和join的区别,join必须等待一个线程结束了才能执行,但是这个模式不需要等待整个线程结束,而是只等待一部分代码

进一步优化:

class GuardedObject{
    //结果
    private Object response;

    //获取结果:
    public Object get(long timeout){
        long begin = System.currentTimeMillis();
        //经理时间:
        long passedTime = 0;
        synchronized (this){
            while( response  == null){
                long waitTime =timeout - passedTime;
                if(passedTime >= timeout){
                    break;
                    //经历的时间大于最大等待时间,退出循环;
                }
                try {
                    this.wait(waitTime);//虚假唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //求得经历时间
                passedTime = System.currentTimeMillis()-begin;
            }
        }
    return  response;
    }

    //产生结果:
    public void complete(Object response){
        //给结果成员变量赋值
        synchronized (this){
            this.response =response;
            this.notifyAll();
        }
    }
}

改进后的测试代码:

@Slf4j
public class Test1111 {
    //线程1等待线程2的下载结果
    public static void main(String[] args) {
        GuardedObject guardedObject = new GuardedObject();
        new Thread(() -> {
            log.debug("begin");
            Object resp = guardedObject.get(2000);
            log.debug("结果是:{}", resp);
        }, "t1").start();
        new Thread(() -> {
            log.debug("begin");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            guardedObject.complete(null);
        }, "t2").start();

    }

    static class GuardedObject {
        //结果
        private Object response;

        //获取结果:
        public Object get(long timeout) {
            long begin = System.currentTimeMillis();
            //经理时间:
            long passedTime = 0;
            synchronized (this) {
                while (response == null) {
                    long waitTime = timeout - passedTime;
                    if (passedTime >= timeout) {
                        break;
                        //经历的时间大于最大等待时间,退出循环;
                    }
                    try {
                        this.wait(waitTime);//虚假唤醒
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //求得经历时间
                    passedTime = System.currentTimeMillis() - begin;
                }
            }
            return response;
        }

        //产生结果:
        public void complete(Object response) {
            //给结果成员变量赋值
            synchronized (this) {
                this.response = response;
                this.notifyAll();
            }
        }
    }
}
经典加一层
@Slf4j(topic = "c.Test20")
public class Test1111 {
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 3; i++) {
            new People().start();
        }
        Thread.sleep(1000);
        for (Integer id : Mailboxes.getIds()) {
            new Postman(id, "内容" + id).start();
        }
    }
}

@Slf4j(topic = "c.People")
class People extends Thread{
    @Override
    public void run() {
        // 收信
        GuardedObject guardedObject = Mailboxes.createGuardedObject();
        log.debug("开始收信 id:{}", guardedObject.getId());
        Object mail = guardedObject.get(5000);
        log.debug("收到信 id:{}, 内容:{}", guardedObject.getId(), mail);
    }
}

@Slf4j(topic = "c.Postman")
class Postman extends Thread {
    private int id;
    private String mail;

    public Postman(int id, String mail) {
        this.id = id;
        this.mail = mail;
    }

    @Override
    public void run() {
        GuardedObject guardedObject = Mailboxes.getGuardedObject(id);
        log.debug("送信 id:{}, 内容:{}", id, mail);
        guardedObject.complete(mail);
    }
}

class Mailboxes {
    private static Map<Integer, GuardedObject> boxes = new Hashtable<>();

    private static int id = 1;
    // 产生唯一 id
    private static synchronized int generateId() {
        return id++;
    }

    public static GuardedObject getGuardedObject(int id) {
        return boxes.remove(id);
    }

    public static GuardedObject createGuardedObject() {
        GuardedObject go = new GuardedObject(generateId());
        boxes.put(go.getId(), go);
        return go;
    }

    public static Set<Integer> getIds() {
        return boxes.keySet();
    }
}

// 增加超时效果
class GuardedObject {

    // 标识 Guarded Object
    private int id;

    public GuardedObject(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    // 结果
    private Object response;

    // 获取结果
    // timeout 表示要等待多久 2000
    public Object get(long timeout) {
        synchronized (this) {
            // 开始时间 15:00:00
            long begin = System.currentTimeMillis();
            // 经历的时间
            long passedTime = 0;
            while (response == null) {
                // 这一轮循环应该等待的时间
                long waitTime = timeout - passedTime;
                // 经历的时间超过了最大等待时间时,退出循环
                if (timeout - passedTime <= 0) {
                    break;
                }
                try {
                    this.wait(waitTime); // 虚假唤醒 15:00:01
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 求得经历时间
                passedTime = System.currentTimeMillis() - begin; // 15:00:02  1s
            }
            return response;
        }
    }

    // 产生结果
    public void complete(Object response) {
        synchronized (this) {
            // 给结果成员变量赋值
            this.response = response;
            this.notifyAll();
        }
    }
}
package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;

@Slf4j(topic = "c.Test21")
public class Test24 {

    public static void main(String[] args) {
        MessageQueue queue = new MessageQueue(2);

        for (int i = 0; i < 3; i++) {
            int id = i;
            new Thread(() -> {
                queue.put(new Message(id , "值"+id));
            }, "生产者" + i).start();
        }

        new Thread(() -> {
            while(true) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                Message message = queue.take();
            }
        }, "消费者").start();
    }

}

// 消息队列类 , java 线程之间通信
@Slf4j(topic = "c.MessageQueue")
class MessageQueue {
    // 消息的队列集合
    private LinkedList<Message> list = new LinkedList<>();
    // 队列容量
    private int capcity;

    public MessageQueue(int capcity) {
        this.capcity = capcity;
    }

    // 获取消息
    public Message take() {
        // 检查队列是否为空
        synchronized (list) {
            while(list.isEmpty()) {
                try {
                    log.debug("队列为空, 消费者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 从队列头部获取消息并返回
            Message message = list.removeFirst();
            log.debug("已消费消息 {}", message);
            list.notifyAll();
            return message;
        }
    }

    // 存入消息
    public void put(Message message) {
        synchronized (list) {
            // 检查对象是否已满
            while(list.size() == capcity) {
                try {
                    log.debug("队列已满, 生产者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 将消息加入队列尾部
            list.addLast(message);
            log.debug("已生产消息 {}", message);
            list.notifyAll();
        }
    }
}

final class Message {
    private int id;
    private Object value;

    public Message(int id, Object value) {
        this.id = id;
        this.value = value;
    }

    public int getId() {
        return id;
    }

    public Object getValue() {
        return value;
    }

    @Override
    public String toString() {
        return "Message{" +
                "id=" + id +
                ", value=" + value +
                '}';
    }
}

Park & unpark

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j
public class Test25 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("start.....");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            log.debug("park");
            LockSupport.park();
            log.debug("resume....");
        }, "t1");
        t1.start();
        Thread.sleep(2000);
        log.debug("unpark....");
        LockSupport.unpark(t1);
    }
}

特点

与 Object 的 wait & notify 相比

1.wait,notify 和 notifyAll 必须配合 Object Monitor 一起使用,而 park,unpark 不必

2.park & unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll

是唤醒所有等待线程,就不那么【精确】

3.park & unpark 可以先 unpark,而 wait & notify 不能先 notify

互换时间,先调用unpark,后调用park

原理:

每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex 打个比喻

线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中

的备用干粮(0 为耗尽,1 为充足)

调用 park 就是要看需不需要停下来歇息

如果备用干粮耗尽,那么钻进帐篷歇息

如果备用干粮充足,那么不需停留,继续前进

调用 unpark,就好比令干粮充足

如果这时线程还在帐篷,就唤醒让他继续前进

如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留继续前进

因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

4.10 重新理解线程状态转换

假设有线程 Thread t

情况 1 NEW > RUNNABLE

当调用 t.start() 方法时,由 NEW –> RUNNABLE

情况 2 RUNNABLE <> WAITING

t 线程用 synchronized(obj) 获取了对象锁后

调用 obj.wait() 方法时,t 线程从 RUNNABLE –> WAITING

调用 obj.notify() , obj.notifyAll() , t.interrupt() 时

竞争锁成功,t 线程从 WAITING –> RUNNABLE

竞争锁失败,t 线程从 WAITING –> BLOCKED

public class TestWaitNotify {
 final static Object obj = new Object();
 public static void main(String[] args) {
new Thread(() -> {
 synchronized (obj) {
 log.debug("执行....");
 try {
 obj.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 log.debug("其它代码...."); // 断点
 }
 },"t1").start();
 new Thread(() -> {
 synchronized (obj) {
 log.debug("执行....");
 try {
 obj.wait();
 } catch (InterruptedException e) {
 e.printStackTrace();
 }
 log.debug("其它代码...."); // 断点
 }
 },"t2").start();
 
 sleep(0.5);
 log.debug("唤醒 obj 上其它线程");
 synchronized (obj) {
 obj.notifyAll(); // 唤醒obj上所有等待线程 断点
 }
 }
}

情况 3 RUNNABLE <> WAITING

当前线程调用 t.join() 方法时,当前线程从 RUNNABLE –> WAITING

注意是当前线程t 线程对象的监视器上等待

t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程从 WAITING –> RUNNABLE

情况 4 RUNNABLE <> WAITING

当前线程调用 LockSupport.park() 方法会让当前线程从 RUNNABLE –> WAITING

调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,会让目标线程从 WAITING –>

RUNNABLE

情况 5 RUNNABLE <–> TIMED_WAITING

t 线程用 synchronized(obj) 获取了对象锁后

调用 obj.wait(long n) 方法时,t 线程从 RUNNABLE –> TIMED_WAITING

t 线程等待时间超过了 n 毫秒,或调用 obj.notify() , obj.notifyAll() , t.interrupt() 时

竞争锁成功,t 线程从 TIMED_WAITING –> RUNNABLE

竞争锁失败,t 线程从 TIMED_WAITING –> BLOCKED

情况 6 RUNNABLE <> TIMED_WAITING

当前线程调用 t.join(long n) 方法时,当前线程从 RUNNABLE –> TIMED_WAITING

注意是当前线程t 线程对象的监视器上等待

当前线程等待时间超过了 n 毫秒,或t 线程运行结束,或调用了当前线程的 interrupt() 时,当前线程

TIMED_WAITING –> RUNNABLE

情况 7 RUNNABLE <> TIMED_WAITING

当前线程调用 Thread.sleep(long n) ,当前线程从 RUNNABLE –> TIMED_WAITING

当前线程等待时间超过了 n 毫秒,当前线程从 TIMED_WAITING –> RUNNABLE

情况 8 RUNNABLE <> TIMED_WAITING

当前线程调用 LockSupport.parkNanos(long nanos) 或 LockSupport.parkUntil(long millis) 时,当前线

从 RUNNABLE –> TIMED_WAITING

调用 LockSupport.unpark(目标线程) 或调用了线程 的 interrupt() ,或是等待超时,会让目标线程从

TIMED_WAITING–> RUNNABLE

情况 9 RUNNABLE <> BLOCKED

t 线程用 synchronized(obj) 获取了对象锁时如果竞争失败,从 RUNNABLE –> BLOCKED

持 obj 锁线程的同步代码块执行完毕,会唤醒该对象上所有 BLOCKED 的线程重新竞争,如果其中 t 线程竞争

成功,从 BLOCKED –> RUNNABLE ,其它失败的线程仍然 BLOCKED

情况 10 RUNNABLE <> TERMINATED

当前线程所有代码运行完毕,进入 TERMINATED

4.11 多把锁

多把不相干的锁

一间大屋子有两个功能:睡觉、学习,互不相干。

现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低

解决方法是准备多个房间(多个对象锁)

例如

class BigRoom {
 public void sleep() {
 synchronized (this) {
 log.debug("sleeping 2 小时");
 Sleeper.sleep(2);
 }
 }
 public void study() {
 synchronized (this) {
 log.debug("study 1 小时");
 Sleeper.sleep(1);
 }
 }
}



改进

4.12 活跃性

死锁

有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁

t1 线程 获得 A对象 锁,接下来想获取 B对象 的锁 t2 线程 获得 B对象 锁,接下来想获取 A对象 的锁 例:

Object A = new Object();
Object B = new Object();
Thread t1 = new Thread(() -> {
 synchronized (A) {
 log.debug("lock A");
 sleep(1);
 synchronized (B) {
 log.debug("lock B");
 log.debug("操作...");
 }
 }
}, "t1");
Thread t2 = new Thread(() -> {
 synchronized (B) {
 log.debug("lock B");
 sleep(0.5);
 synchronized (A) {
 log.debug("lock A");
 log.debug("操作...");
 }
 }
}, "t2");
t1.start();
t2.start();

结果

12:22:06.962 [t2] c.TestDeadLock – lock B

12:22:06.962 [t1] c.TestDeadLock – lock A

定位死锁

检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:

cmd > jps

Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8

12320 Jps

22816 KotlinCompileDaemon

33200 TestDeadLock // JVM 进程

11508 Main

28468 Launcher

cmd > jstack 33200

Picked up JAVA_TOOL_OPTIONS: -Dfile.encoding=UTF-8

2018-12-29 05:51:40

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.91-b14 mixed mode):

“DestroyJavaVM” #13 prio=5 os_prio=0 tid=0x0000000003525000 nid=0x2f60 waiting on condition

[0x0000000000000000]

java.lang.Thread.State: RUNNABLE

“Thread-1” #12 prio=5 os_prio=0 tid=0x000000001eb69000 nid=0xd40 waiting for monitor entry

[0x000000001f54f000]

java.lang.Thread.State: BLOCKED (on object monitor)

at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)

– waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)

– locked <0x000000076b5bf1d0> (a java.lang.Object)

at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)

at java.lang.Thread.run(Thread.java:745)

“Thread-0” #11 prio=5 os_prio=0 tid=0x000000001eb68800 nid=0x1b28 waiting for monitor entry

[0x000000001f44f000]

java.lang.Thread.State: BLOCKED (on object monitor)

at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)

– waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)

– locked <0x000000076b5bf1c0> (a java.lang.Object)

at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)

at java.lang.Thread.run(Thread.java:745)

// 略去部分输出

Found one Java-level deadlock:

=============================

“Thread-1”:

waiting to lock monitor 0x000000000361d378 (object 0x000000076b5bf1c0, a java.lang.Object),

which is held by “Thread-0”

“Thread-0”:

waiting to lock monitor 0x000000000361e768 (object 0x000000076b5bf1d0, a java.lang.Object),

which is held by “Thread-1”

Java stack information for the threads listed above:

===================================================

“Thread-1”:

at thread.TestDeadLock.lambda$main$1(TestDeadLock.java:28)

– waiting to lock <0x000000076b5bf1c0> (a java.lang.Object)

– locked <0x000000076b5bf1d0> (a java.lang.Object)

at thread.TestDeadLock$$Lambda$2/883049899.run(Unknown Source)

at java.lang.Thread.run(Thread.java:745)

“Thread-0”:

at thread.TestDeadLock.lambda$main$0(TestDeadLock.java:15)

– waiting to lock <0x000000076b5bf1d0> (a java.lang.Object)

– locked <0x000000076b5bf1c0> (a java.lang.Object)

at thread.TestDeadLock$$Lambda$1/495053715.run(Unknown Source)

at java.lang.Thread.run(Thread.java:745)

Found 1 deadlock.

避免死锁要注意加锁顺序

另外如果由于某个线程进入了死循环,导致其它线程一直等待,对于这种情况 linux 下可以通过 top 先定位到

CPU 占用高的 Java 进程,再利用 top -Hp 进程id 来定位是哪个线程,最后再用 jstack 排查

哲学家就餐问题

有五位哲学家,围坐在圆桌旁。

他们只做两件事,思考和吃饭,思考一会吃口饭,吃完饭后接着思考。

吃饭时要用两根筷子吃,桌上共有 5 根筷子,每位哲学家左右手边各有一根筷子。

如果筷子被身边的人拿着,自己就得等待

筷子类

class Chopstick {
 String name;
 public Chopstick(String name) {
 this.name = name;
 }
 @Override
 public String toString() {
 return "筷子{" + name + '}';
 }
}

哲学家类

class Philosopher extends Thread {
 Chopstick left;
 Chopstick right;
public Philosopher(String name, Chopstick left, Chopstick right) {
 super(name);
 this.left = left;
 this.right = right;
 }
 private void eat() {
 log.debug("eating...");
 Sleeper.sleep(1);
 }
 
 @Override
 public void run() {
 while (true) {
 // 获得左手筷子
 synchronized (left) {
 // 获得右手筷子
 synchronized (right) {
 // 吃饭
 eat();
 }
 // 放下右手筷子
 }
 // 放下左手筷子
 }
 }
}

就餐

Chopstick c1 = new Chopstick("1");
Chopstick c2 = new Chopstick("2");
Chopstick c3 = new Chopstick("3");
Chopstick c4 = new Chopstick("4");
Chopstick c5 = new Chopstick("5");
new Philosopher("苏格拉底", c1, c2).start();
new Philosopher("柏拉图", c2, c3).start();
new Philosopher("亚里士多德", c3, c4).start();
new Philosopher("赫拉克利特", c4, c5).start();
new Philosopher("阿基米德", c5, c1).start()

执行不多会,就执行不下去了

12:33:15.575 [苏格拉底] c.Philosopher - eating... 
12:33:15.575 [亚里士多德] c.Philosopher - eating... 
12:33:16.580 [阿基米德] c.Philosopher - eating... 
12:33:17.580 [阿基米德] c.Philosopher - eating... 
// 卡在这里, 不向下运行

使用 jconsole 检测死锁,发现

名称: 阿基米德
状态: cn.itcast.Chopstick@1540e19d (筷子1) 上的BLOCKED, 拥有者: 苏格拉底
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@6d6f6e28 (筷子5)
-------------------------------------------------------------------------
名称: 苏格拉底
状态: cn.itcast.Chopstick@677327b6 (筷子2) 上的BLOCKED, 拥有者: 柏拉图
总阻止数: 2, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@1540e19d (筷子1)
-------------------------------------------------------------------------
名称: 柏拉图
状态: cn.itcast.Chopstick@14ae5a5 (筷子3) 上的BLOCKED, 拥有者: 亚里士多德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@677327b6 (筷子2)
-------------------------------------------------------------------------
名称: 亚里士多德
状态: cn.itcast.Chopstick@7f31245a (筷子4) 上的BLOCKED, 拥有者: 赫拉克利特
总阻止数: 1, 总等待数: 1
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@14ae5a5 (筷子3)
-------------------------------------------------------------------------
名称: 赫拉克利特
状态: cn.itcast.Chopstick@6d6f6e28 (筷子5) 上的BLOCKED, 拥有者: 阿基米德
总阻止数: 2, 总等待数: 0
堆栈跟踪:
cn.itcast.Philosopher.run(TestDinner.java:48)
 - 已锁定 cn.itcast.Chopstick@7f31245a (筷子4)

这种线程没有按预期结束,执行不下去的情况,归类为【活跃性】问题,除了死锁以外,还有活锁和饥饿者两种情

活锁

活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

public class TestLiveLock {
 static volatile int count = 10;
static final Object lock = new Object();
 public static void main(String[] args) {
 new Thread(() -> {
 // 期望减到 0 退出循环
 while (count > 0) {
 sleep(0.2);
 count--;
 log.debug("count: {}", count);
 }
 }, "t1").start();
 new Thread(() -> {
 // 期望超过 20 退出循环
 while (count < 20) {
 sleep(0.2);
 count++;
 log.debug("count: {}", count);
 }
 }, "t2").start();
 }
}

饥饿

很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不

易演示,讲读写锁时会涉及饥饿问题

下面我讲一下我遇到的一个线程饥饿的例子,先来看看使用顺序加锁的方式解决之前的死锁问题

顺序加锁的解决方案

4.13 ReentrantLock

相对于 synchronized 它具备如下特点

可中断

可以设置超时时间

可以设置为公平锁

支持多个条件变量

与 synchronized 一样,都支持可重入

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁

如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class Test26 {
    private static  ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        method1();
    }

    private static void method1() {
        lock.lock();
        try {
            log.debug("m1");
            method2();
        }finally {
            lock.unlock();
        }
    }

    private static void method2() {
        lock.lock();
        try {
            log.debug("m2");
            method3();
        }finally {
            lock.unlock();
        }

    }

    private static void method3() {
        lock.lock();
        try {
            log.debug("m3");
        }finally {
            lock.unlock();
        }
    }
}

可打断

代码

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class Test27 {
    private static  ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("启动");
            try {
                lock.lockInterruptibly();
            }catch (InterruptedException e){
               e.printStackTrace();
                log.debug("等待锁的过程中被打断了");
                return; 
            }
            try {
                log.debug("获得了锁");
            }finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得了锁");
        t1.start();
        try {
            Thread.sleep(1000);
            t1.interrupt();
            log.debug("执行打断");
        }finally {
            lock.unlock();
        }


    }
}
package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class Test28 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            try {
                if (!lock.tryLock(2, TimeUnit.SECONDS));
            }catch (InterruptedException e){
                e.printStackTrace();
                log.debug("获取不到锁");
                return;
            }
            try {
                log.debug("获得到锁");
            }finally {
                lock.unlock();
            }
        },"t1");



        lock.lock();
        log.debug("获得到锁");
        t1.start();
        Thread.sleep(1000);
        log.debug("释放锁");
        lock.unlock();
    }
}
锁超时
立刻失败

公平锁

ReentrantLock 默认是不公平的

强行插入,有机会在中间输出

公平锁一般没有必要,会降低并发度,后面分析原理时会讲解

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

synchronized 是那些不满足条件的线程都在一间休息室等消息

而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤

使用要点:

await 前需要获得锁

await 执行后,会释放锁,进入 conditionObject 等待

await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁

竞争 lock 锁成功后,从 await 后继续执行

同步模式之顺序控制

1. 固定运行顺序

比如,必须先 2 后 1 打印

1.1 wait notify 版

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Test30 {
    static  Object object = new Object();
    //标记t2是否执行过
    static boolean t2runned = false;
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (object){
                //如果t2没有执行过
                while(!t2runned){
                    try {
                        //t1先等一会
                        object.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
                System.out.println(1);
            }
        },"t1");
        Thread t2 = new Thread(() -> {
            System.out.println(2);
            synchronized (object){
                //修改运行标记
                t2runned = true;
                //通知obj上等待的线程(可能有多个,因此需要用notifyall)
                object.notifyAll();
            }
        }, "t2");


        t1.start();
        t2.start();
    }
}

park & unpark

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j
public class Test31 {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            LockSupport.park();
            log.debug("1");
        }, "t1");
        t1.start();
        new Thread(() ->{
            log.debug("2");
            LockSupport.unpark(t1);
        },"t2").start();
    }
}
package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class Test32 {
    public static void main(String[] args) {
        waitNotify waitNotify = new waitNotify(1, 5);
        new Thread(()->{
            try {
                waitNotify.print("a", 1, 2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                waitNotify.print("b", 2, 3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
        new Thread(()->{
            try {
                waitNotify.print("c", 3, 1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }).start();
    }
}

/**
 * 输出内容,等待标记  下一个标记
 * a .... 1  .......... 2
 * b......2  .......... 3
 * c ......3 ............1
 */

class waitNotify{

    //打印
    public void print(String str,int waitFlag,int nextFlag) throws InterruptedException {
        for (int i = 0; i < loopNumber; i++) {
            synchronized (this){
                while (flag != waitFlag){
                    this.wait();
                }
                System.out.print(str);
                flag = nextFlag;
                this.notifyAll();
            }
        }
    }

    //等待标记
    private int flag;
    //循环次数
    private int loopNumber;
    public waitNotify(int flag,int loopNumber){
        this.flag = flag;
        this.loopNumber = loopNumber;
    }

}

Reentlock实现;

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

@Slf4j
public class Test33 {
    public static void main(String[] args) throws InterruptedException {
        AwaitSignal awaitSignal = new AwaitSignal(5);
        Condition a = awaitSignal.newCondition();
        Condition b = awaitSignal.newCondition();
        Condition c = awaitSignal.newCondition();
        new Thread(()->{
            awaitSignal.print("a", a, b);
        }).start();
        new Thread(()->{
            awaitSignal.print("b", b, c);
        }).start();
        new Thread(()->{
            awaitSignal.print("c", c, a);
        }).start();
        Thread.sleep(1000);
        awaitSignal.lock();
        try {
            log.debug("开始");
            a.signal();
        }finally {
            awaitSignal.unlock();
        }
    }
}
class AwaitSignal extends ReentrantLock{
     private int loopNumber;

    public AwaitSignal(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    public void print(String str,Condition condition,Condition next){
        for (int i = 0; i < loopNumber; i++) {
            lock();
            try {
                condition.await();
                System.out.print(str);
                next.signal();//唤醒下一间休息室的贤臣
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                unlock();
            }
        }
    }

}

park解决

package com.example.juc.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@Slf4j
public class Test34 {
    static Thread t1;
    static Thread t2;
    static Thread t3;
    public static void main(String[] args) {

        ParkUnpark parkUnpark = new ParkUnpark(5);
         t1 = new Thread(() -> {
            parkUnpark.print("a", t2);
        });
        t2 = new Thread(() -> {
            parkUnpark.print("b", t3);
        });
        t3 = new Thread(() -> {
            parkUnpark.print("c", t1);
        });
        t1.start();
        t2.start();
        t3.start();
        LockSupport.unpark(t1);
    }
}

class ParkUnpark{
    public void print(String str,Thread next){
        for (int i = 0; i < loopNumber; i++) {
            LockSupport.park();
            System.out.print(str);
            LockSupport.unpark(next);
        }
    }


    public ParkUnpark(int loopNumber) {
        this.loopNumber = loopNumber;
    }

    private  int loopNumber;
}

本章小结

本章我们需要重点掌握的是

分析多线程访问共享资源时,哪些代码片段属于临界区

使用 synchronized 互斥解决临界区的线程安全问题

掌握 synchronized 锁对象语法

掌握 synchronzied 加载成员方法和静态方法语法

掌握 wait/notify 同步方法

使用 lock 互斥解决临界区的线程安全问题

掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量

学会分析变量的线程安全性、掌握常见线程安全类的使用

了解线程活跃性问题:死锁、活锁、饥饿

应用方面

互斥:使用 synchronized 或 Lock 达到共享资源互斥效果

同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果

原理方面

monitor、synchronized 、wait/notify 原理

synchronized 进阶原理

park & unpark 原理

模式方面

同步模式之保护性暂停

异步模式之生产者消费者

同步模式之顺序控制

暂无评论

发送评论 编辑评论


				
|´・ω・)ノ
ヾ(≧∇≦*)ゝ
(☆ω☆)
(╯‵□′)╯︵┴─┴
 ̄﹃ ̄
(/ω\)
∠( ᐛ 」∠)_
(๑•̀ㅁ•́ฅ)
→_→
୧(๑•̀⌄•́๑)૭
٩(ˊᗜˋ*)و
(ノ°ο°)ノ
(´இ皿இ`)
⌇●﹏●⌇
(ฅ´ω`ฅ)
(╯°A°)╯︵○○○
φ( ̄∇ ̄o)
ヾ(´・ ・`。)ノ"
( ง ᵒ̌皿ᵒ̌)ง⁼³₌₃
(ó﹏ò。)
Σ(っ °Д °;)っ
( ,,´・ω・)ノ"(´っω・`。)
╮(╯▽╰)╭
o(*////▽////*)q
>﹏<
( ๑´•ω•) "(ㆆᴗㆆ)
😂
😀
😅
😊
🙂
🙃
😌
😍
😘
😜
😝
😏
😒
🙄
😳
😡
😔
😫
😱
😭
💩
👻
🙌
🖕
👍
👫
👬
👭
🌚
🌝
🙈
💊
😶
🙏
🍦
🍉
😣
Source: github.com/k4yt3x/flowerhd
颜文字
Emoji
小恐龙
花!
上一篇
下一篇