并发编程专题-02共享模型-管程(悲观锁)

2021-09-12  本文已影响0人  攻城老狮

1.共享问题

1.1 Java共享问题演示

以下的结果可能是正数、负数、零。因为 Java 中对静态变量的自增,自减并不是原子操作。单线程情况下,不会出现指令交错的现象。但是在多线程环境下,可能出现指令交错运行。

//两个线程对共享的值进行修改,一个进行++,一个进行--,查看最后的结果是否为0
public class TestSecurity {

    static int count = 0;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 3000; i++) {
                    count++;
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 3000; i++) {
                    count--;
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count); //不一定为0
    }
}

1.2 临界区

一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区

上面程序代码的临界区为对资源的共享部分。

static int count = 0;
//...
// 临界区
{
 counter++;
}
//...
// 临界区
{
 counter--;
}

1.3 竞态条件

多个线程在临界区内执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竞态条件

2. synchronized

2.1 synchronized语法

synchronized(对象) 
{
 临界区
}

另外准备对象,用于加锁使用。当某个线程持有该对象锁后,其他线程会被阻塞,无法访问临界资源。

public class TestSecurity {

    static int count = 0;
    //对象锁
    static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 3000; i++) {
                    //临界区加锁
                    synchronized (lock){
                        count++;
                    }
                }
            }
        };
        Thread t2 = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 3000; i++) {
                    //临界区加锁
                    synchronized (lock){
                        count--;
                    }
                }
            }
        };
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

2.2 方法上的 synchronized

在成员方法上面加synchronized表示对this对象加锁,在静态方法上面加synchronized表示对类加锁

class Test{
     public synchronized void test() {

     }
}
//等价于
class Test{
     public void test() {
         synchronized(this) {

         }
     }
}
class Test{
     public synchronized static void test() {
         
     }
}
//等价于
class Test{
     public static void test() {
         synchronized(Test.class) {

         }
     }
}

3.变量和线程安全分析

3.1 成员变量和静态变量的线程安全问题

3.2 局部变量的线程安全问题

注: private 或 final 提供安全的意义在于,在一定程度上防止了子类重写父类方法,引入线程安全问题。(在重写的方法中,采用多线程的方式对共享资源进行读写——非线程安全)

3.3 常见线程安全类

String,Integer,StringBuffer,Random,Vector,HashTable,java.util.concurrent包下的类。

如:下面代码使用多个方法的组合,非线程安全的

Hashtable table = new Hashtable();
// 线程1,线程2
if( table.get("key") == null) {
 table.put("key", value);
}

4 Monitor 管程

4.1 Java对象头

以32位虚拟机为例

# 普通对象 对象头包括 标记字段和Class字段(标识对象的Class类型)
|--------------------------------------------------------------|
| Object Header (64 bits)                                   |
|------------------------------------|-------------------------|
| Mark Word (32 bits)               | Klass Word (32 bits)    |
|------------------------------------|-------------------------|
# Mark Word 结构
# 01 正常状态;  01 biased_lock:1 偏向锁;   00 轻量级锁;    10 重量级锁;    11 GC
|-------------------------------------------------------|--------------------|
| Mark Word (32 bits)                                                    | State             |
|-------------------------------------------------------|--------------------|
| hashcode:25 | age:4 | biased_lock:0         | 01      | Normal            |
|-------------------------------------------------------|--------------------|
| thread:23 | epoch:2 | age:4 | biased_lock:1 | 01      | Biased            |
|-------------------------------------------------------|--------------------|
| ptr_to_lock_record:30                                 | 00      | Lightweight Locked |
|-------------------------------------------------------|--------------------|
| ptr_to_heavyweight_monitor:30                      | 10      | Heavyweight Locked |
|-------------------------------------------------------|--------------------|
|                                                                | 11       | Marked for GC      |
|-------------------------------------------------------|--------------------|

4.2 Monitor原理

Monitor管程,每个Java对象都可以关联一个Monitor对象,如果使用synchronized给对象上锁(重量级)后,该对象头的Mark Word 中就被设置指向 Monitor 对象的指针

Monitor的结构:

image-20210615183300471.png

注:synchronized必须进入同一个对象的Monitor才有上述功能;不加synchronized的对象不会关联Monitor管程。

4.3 synchronized原理

4.3.1 轻量级锁

使用场景:如果一个对象虽然有多线程要加锁,但加锁的时间是错开的(没有竞争),则可以使用轻量级锁优化。轻量级锁对使用者是透明的,语法还是synchronized

轻量级锁的加锁过程

  1. 创建锁记录对象,每个线程的栈帧中包含一个锁记录的结构
image-20210615184749305.png
  1. 让锁记录中的Object reference指向锁对象,尝试使用CAS,将lock record地址和Object的Mark Word替换
image-20210615184951286.png image-20210615185230003.png
  1. 退出synchronized代码块(解锁时),分两种情况
    • 如果有取值为null的锁记录,表示有重入,让重入计数减一
    • 如果锁记录的取值不为null,此时使用CAS将Mark Word的值恢复给对象头
      • 成功,解锁成功
      • 失败,说明发生竞争,轻量级锁进行了锁膨胀已经升级为重量级锁,进入重量级锁的解锁流程

锁膨胀

如果在尝试加轻量级锁的过程中,发现该对象的Mark Word中已经指向了其他线程的Lock Record,则进入锁膨胀,将轻量级锁变为重量级锁

  1. 当出现竞争线程时,该线程尝试加锁失败。发现Object对象中已经指向了其他线程的Lock Record。
image-20210615190342119.png
  1. 此时当前线程加轻量级锁失败,进入锁膨胀阶段
    • 首先为Object对象申请Monitor管程,让Object指向重量级锁地址
    • 当前线程进入该Monitor的EntryList,等待唤醒
image-20210615190554686.png
  1. 当持有锁的线程退出同步代码块时,使用CAS给Mark Word的值恢复给对象头时失败,这时进入重量级锁的解锁流程,按照Monitor地址找到Monitor对象,设置Owner为null,唤醒EntryList中的阻塞线程

4.3.2 自旋优化

使用场景:重量级锁竞争时,可以使用自旋优化。如果当前线程自旋成功(持有锁的线程已经退出同步代码块,解锁),则可以避免当前线程的阻塞。

注:自旋会占用CPU时间,单核CPU自旋浪费资源,自旋优化适用于多核CPU;Java6后自旋是自适应的,刚刚自旋成功的话,则判定这次自旋的成功率会较高,多自旋几次,相反则少自旋几次

4.3.3 偏向锁

使用场景:轻量级锁在没有竞争时,每次还需要执行CAS操作,Java6中引入偏向锁进一步优化,只有第一次使用CAS将线程ID设置到对象的 Mark Word 中,之后发现这个线程ID是自己就表示没有竞争,不用重新CAS。以后不发生竞争,则该对象就归该线程所有。

注:默认开启了偏向锁,偏向锁默认是延迟的,不会在程序启动时立即生效,避免延迟,添加VM参数:-XX:BiasedLockingStartupDelay=0 用于禁用延迟。hashcode第一次用到才会被赋值,当有hashcode时,偏向锁则消失,两者不可同存。

实例测试

//偏向锁测试
public class TestLock {

    public static void main(String[] args) {
        Dog dog = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(dog);
        new Thread(){
            @Override
            public void run() {
                System.out.println("before");
                System.out.println(classLayout.toPrintable());
                synchronized (dog){
                    System.out.println("in");
                    System.out.println(classLayout.toPrintable());
                }
                System.out.println("after");
                System.out.println(classLayout.toPrintable());
            }
        }.start();
    }
}

class Dog{}
# 结果
before
com.yqj.concurrent2.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000005 (biasable; age: 0)
  8   4        (object header: class)    0x2000c143
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

in
com.yqj.concurrent2.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001b426005 (biased: 0x000000000006d098; epoch: 0; age: 0)
  8   4        (object header: class)    0x2000c143
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after
com.yqj.concurrent2.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001b426005 (biased: 0x000000000006d098; epoch: 0; age: 0)
  8   4        (object header: class)    0x2000c143
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

偏向锁的撤销

  1. 调用对象的hashCode

偏向锁的对象 MarkWord 中存储的是线程 id,如果调用 hashCode 会导致偏向锁被撤销。这是由于空间有限,两者不能共存。而轻量级锁可以将hashCode保存在锁记录Lock Record中。重量级锁可以将hashCode保存在Monitor中。

  1. 其他线程使用对象

当有其他线程使用偏向锁对象时,会将偏向锁升级为轻量级锁。

//测试实例
public class TestLock {

    public static void main(String[] args) throws InterruptedException {
        Dog dog = new Dog();
        ClassLayout classLayout = ClassLayout.parseInstance(dog);
        Thread t1 = new Thread() {
            @Override
            public void run() {
                synchronized (dog) {
                    System.out.println(classLayout.toPrintable());
                }
                //必须使用wait/nodify,因为t1线程不能结束,否则底层线程可能被jvm重用作为t2线程,底层线程的id一样,导致依旧还是偏向锁
                synchronized (TestLock.class){
                    TestLock.class.notify();
                }
            }
        };
        t1.start();

        Thread t2 = new Thread() {
            @Override
            public void run() {
                synchronized (TestLock.class){
                    try {
                        TestLock.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("before");
                System.out.println(classLayout.toPrintable());
                synchronized (dog) {
                    System.out.println("in");
                    System.out.println(classLayout.toPrintable());
                }
                System.out.println("after");
                System.out.println(classLayout.toPrintable());
            }
        };
        t2.start();
    }
}

class Dog{}
# 结果 原本为偏向锁,后由于其他线程也使用该对象,从而升级为轻量级锁
com.yqj.concurrent2.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001af67005 (biased: 0x000000000006bd9c; epoch: 0; age: 0)
  8   4        (object header: class)    0x2000c205
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

before
com.yqj.concurrent2.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001af67005 (biased: 0x000000000006bd9c; epoch: 0; age: 0)
  8   4        (object header: class)    0x2000c205
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

in
com.yqj.concurrent2.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x000000001b83f630 (thin lock: 0x000000001b83f630)
  8   4        (object header: class)    0x2000c205
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total

after
com.yqj.concurrent2.Dog object internals:
OFF  SZ   TYPE DESCRIPTION               VALUE
  0   8        (object header: mark)     0x0000000000000001 (non-biasable; age: 0)
  8   4        (object header: class)    0x2000c205
 12   4        (object alignment gap)    
Instance size: 16 bytes
Space losses: 0 bytes internal + 4 bytes external = 4 bytes total
  1. 调用wait/notify

批量重偏向

使用场景:当对象被多个线程访问,但不存在竞争,这时偏向了t1线程的对象仍然有机会重新偏向t2,重偏向会重置对象的ID。(当撤销偏向锁第20次后,会给之后的这些对象加锁时重新偏向至加锁线程)

批量撤销

使用场景:当撤销偏向锁再超过40次后(批量重偏向后),整个类的所有对象都会变为不可偏向的,新建的对象也是不可偏向的

5 wait/notify

5.1 wait/notify 原理

image-20210615202746313.png

5.2 wait/notify 使用

属于Object对象的方法,必须获得该对象锁时,才可以调用这几个方法。

public class TestWaitNotify{

    private static final Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {

        List<Thread> list = new ArrayList<>();
        //创建5个线程,并启动
        for (int i = 0; i < 5; i++) {
            Thread t = new Thread(() -> {
                synchronized (lock) {
                    System.out.println("run...");
                    try {
                        lock.wait(); //当前线程等待
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + " other...");
                }
            });
            list.add(t);
            t.start();
        }

        TimeUnit.SECONDS.sleep(1);

        synchronized (lock) {
            System.out.println("wake up...");
//            lock.notify();  //唤醒lock对象上的一个线程
            lock.notifyAll(); //唤醒lock对象上的全部等待的线程
        }
    }
}

5.3 与sleep方法的区别

5.4 wait/notify使用的正确方式

synchronized(lock) {
     while(条件不成立) {
         lock.wait();
     }
     // 其他操作
}
//另一个线程
synchronized(lock) {
    lock.notifyAll();
}

6 park/unpark

6. 1 park/unpark原理

每个线程都有自己的一个Parker对象,该对象由三部分组成,包括 _counter, _cond, _mutex。

三种情况:

  1. 先调用 park() 阻塞线程
image-20210616083943696.png
  1. 线程已经处于阻塞状态,调用unpark()恢复线程
image-20210616084132819.png
  1. 先调用unpark(),后调用 park()
image-20210616084240013.png

6.2 park/unpark使用

//测试实例
public class TestLock {

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread() {
            @Override
            public void run() {
                System.out.println("thread start");
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("park");
                LockSupport.park();
                System.out.println("continue");
            }
        };
        t1.start();

        //TimeUnit.SECONDS.sleep(1); //在线程t1 park前 先unpark
        TimeUnit.SECONDS.sleep(3); //在线程t1 park后 再unpark
        System.out.println("unpark");
        LockSupport.unpark(t1);
    }
}

6.3 与 wait/nodify 的比较

7 线程状态

image-20210616090447421.png

几种可以实现状态转换的方法总结:

  1. New -> Runnable
  1. Runnable -> Waiting
  1. Runnable -> Timed_Waiting
  1. Runnable -> Blocked
  1. Runnable -> Terminated

8 活跃性

8.1 死锁

一个线程同时需要获取多把锁,此时便容易发生死锁

死锁测试实例

//测试实例,t1线程获得了lockA,但无法获得lockB。t2线程获得了lockB,但无法获得lockA
public class TestLock {

    private static final Object lockA = new Object();
    private static final Object lockB = new Object();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            synchronized (lockA){
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1已获得lockA");
                synchronized (lockB){
                    System.out.println("t1已获得lockB");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (lockB){
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2已获得lockB");
                synchronized (lockA){
                    System.out.println("t2已获得lockA");
                }
            }
        }, "t2");

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

哲学家就餐问题(死锁)

public class TestLock {

    public static void main(String[] args) {
        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("t1", c1, c2).start();
        new Philosopher("t2", c2, c3).start();
        new Philosopher("t3", c3, c4).start();
        new Philosopher("t4", c4, c5).start();
        new Philosopher("t5", c5, c1).start();
    }

}

class Chopstick {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Philosopher extends Thread {
    String name;
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        this.name = name;
        this.left = left;
        this.right = right;
    }

    private void eat() {
        System.out.println(currentThread().getName() + " eat...");
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            synchronized (left) {
                synchronized (right) {
                    eat();
                }
            }
        }
    }

    @Override
    public String toString() {
        return "Philosopher{" +
                "name='" + name + '\'' +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
}

8.2 活锁

活锁出现在两个线程互相改变对付的结束条件,最后均无法结束的现象

public class TestLock {

    private static int count = 10;

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {

            while (count > 0) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println(count);
            }

        }, "t1");

        Thread t2 = new Thread(() -> {

            while (count < 20) {
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count++;
                System.out.println(count);
            }

        }, "t2");

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

8.3 饥饿

一个线程由于优先级太低,始终得不到CPU调度执行,也不能结束

9 ReentrantLock

相当于synchronized的改进版本,具有如下特点:

//ReentrantLock语法
// 获取锁
reentrantLock.lock();
try {
 // 临界区
} finally {
 // 释放锁
 reentrantLock.unlock();
}

9.1 可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁 如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

//测试实例,由于可重入性,在一个线程执行过程中的两个方法均可以正常获取锁并执行
public class TestLock {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        method1();
    }

    private static void method1() {
        lock.lock();
        try {
            System.out.println("method1");
            method2();
        }finally {
            lock.unlock();
        }
    }

    private static void method2() {
        lock.lock();
        try {
            System.out.println("method2");
        }finally {
            lock.unlock();
        }
    }

}

9.2 可打断

加锁等待时,可以通过异常打断

//测试实例,主线程先加锁,t1线程没有获得锁等待,当主线程发送打断后,t1线程可以被成功打断
public class TestLock {

    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            System.out.println("start");
            try {
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                System.out.println("lock interrupted");
                return;
            }
            try {
                System.out.println("run...");
            }finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        System.out.println("main lock");
        t1.start();
        try {
            Thread.sleep(2000);
            t1.interrupt();
            System.out.println("interrupt t1");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

}

9.3 锁超时

//测试实例,主线程先加锁,t1线程没有获得锁,尝试加锁失败返回
public class TestLock {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            if (!lock.tryLock()){
                System.out.println("lock fail");
                return;
            }
            try {
                System.out.println("run...");
            }finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        System.out.println("main lock");
        t1.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
//测试实例,主线程先加锁,t1线程没有获得锁,等待指定时间后若依旧没有获得锁则加锁失败返回
public class TestLock {

    private static final ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                if (!lock.tryLock(1,TimeUnit.SECONDS)){
                    System.out.println("lock fail");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            try {
                System.out.println("run...");
            }finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        System.out.println("main lock");
        t1.start();
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
public class TestLock {

    public static void main(String[] args) {
        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("t1", c1, c2).start();
        new Philosopher("t2", c2, c3).start();
        new Philosopher("t3", c3, c4).start();
        new Philosopher("t4", c4, c5).start();
        new Philosopher("t5", c5, c1).start();
    }

}
//继承了ReentrantLock类
class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "Chopstick{" +
                "name='" + name + '\'' +
                '}';
    }
}

class Philosopher extends Thread {
    String name;
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        this.name = name;
        this.left = left;
        this.right = right;
    }

    private void eat() {
        System.out.println(currentThread().getName() + " eat...");
        try {
            sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        while (true) {
            //尝试加锁,加锁失败直接放弃,避免死锁
            if (left.tryLock()) {
                try {
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }

    @Override
    public String toString() {
        return "Philosopher{" +
                "name='" + name + '\'' +
                ", left=" + left +
                ", right=" + right +
                '}';
    }
}

9.4 公平锁

ReentrantLock默认是不公平的,不建议使用公平锁,会降低程序的并发度

//设置为公平锁的方式
ReentrantLock lock = new ReentrantLock(true);

9.5 条件变量

//测试实例
public class TestLock {

    private static final ReentrantLock lock = new ReentrantLock();
    private static Condition waitCigaretteQueue = lock.newCondition();
    private static Condition waitBreakfastQueue = lock.newCondition();
    private static volatile boolean hasCigarette = false;
    private static volatile boolean hasBreakfast = false;

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("wait cigarette");
                while (!hasCigarette){
                    try {
                        waitCigaretteQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("has Cigarette");
                }
            }finally {
                lock.unlock();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            lock.lock();
            try {
                System.out.println("wait breakfast");
                while (!hasBreakfast){
                    try {
                        waitBreakfastQueue.await();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println("has breakfast");
                }
            }finally {
                lock.unlock();
            }
        }, "t2");

        t1.start();
        t2.start();
        TimeUnit.SECONDS.sleep(1);
        sendCigarette();
        TimeUnit.SECONDS.sleep(1);
        sendBreakfast();
    }

    private static void sendBreakfast() {
        lock.lock();
        try {
            System.out.println("send breakfast");
            hasBreakfast = true;
            waitBreakfastQueue.signal();
        }finally {
            lock.unlock();
        }
    }

    private static void sendCigarette() {
        lock.lock();
        try {
            System.out.println("send cigarette");
            hasCigarette = true;
            waitCigaretteQueue.signal();
        }finally {
            lock.unlock();
        }
    }
}

问题

  1. synchronized的理解(线程八锁问题
上一篇 下一篇

猜你喜欢

热点阅读