【JUC并发编程】用Condition实现精准唤醒/理解八锁现象

Condition

Condition 不像 wait()notify()notifyAll() 这些方法一样,直接使用就行,而是先需要生成一个 Condition 对象,比较通用的就是从 ReentrantLock 的 newCondition() 方法中获取一个 Condition 对象。

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

Condition 无法单独使用,还是需要先获取到锁之后再使用。

示例:用condition精准唤醒

public class Test05 {
    public static void main(String[] args) {
        Data3 data = new Data3();
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.change1();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"A").start();
​
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.change2();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"B").start();
​
        new Thread(() -> {
            for (int i = 0; i < 10; i++) {
                try {
                    data.change3();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        },"C").start();
    }
}
​
class Data3 {
    private int num = 1;
    Lock lock = new ReentrantLock();
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();
​
    void change1() throws InterruptedException {
        try {
            lock.lock();
            while (num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + ": " + num);
            num = 2;
            condition2.signal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
​
    void change2() throws InterruptedException {
        try {
            lock.lock();
            while (num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + ": " + num);
            num = 3;
            condition3.signal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
​
    void change3() throws InterruptedException {
        try {
            lock.lock();
            while (num != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + ": " + num);
            num = 1;
            condition1.signal();
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            lock.unlock();
        }
    }
}

此时线程ABC按顺序进行工作,在方法中实现了对单个线程的精准唤醒。


理解八锁现象

1.

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendMessage();
        }, "A").start();
​
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            phone.call();
        }, "B").start();
    }
}
​
class Phone {
    public synchronized void sendMessage() {
        System.out.println("发信息");
    }
    public synchronized void call() {
        System.out.println("打电话");
    }
}

多次运行进行测试,发现都是先打印发短信,再打印打电话

2.

   public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendMessage();
        }, "A").start();
​
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            phone.call();
        }, "B").start();
    }
}
​
class Phone {
    public synchronized void sendMessage() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发信息");
    }
    public synchronized void call() {
        System.out.println("打电话");
    }
}

此时仍然先打印发信息,再打印打电话。因为synchronized锁的对象是方法的调用者,在上述两方法sendMessage和call方法中使用的是同一个锁,先拿到锁的方法先执行。

3.增加一个普通方法,此时先打印发信息还是Hello?

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();
        new Thread(() -> {
            phone.sendMessage();
        }, "A").start();
​
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            phone.hello();
        }, "B").start();
    }
}
​
class Phone {
    public synchronized void sendMessage() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发信息");
    }
    public synchronized void call() {
        System.out.println("打电话");
    }
​
    public void hello() {
        System.out.println("Hello");
    }
}

此时先打印Hello,因为hello方法不是同步方法,不受synchronized锁的影响。

4.增加一个对象,两个线程分别调用两个对象的同步方法,此时先打印发信息还是打电话?

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMessage();
        }, "A").start();
​
//        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            phone2.call();
        }, "B").start();
    }
}
​
class Phone {
    public synchronized void sendMessage() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发信息");
    }
    public synchronized void call() {
        System.out.println("打电话");
    }
​
    public void hello() {
        System.out.println("Hello");
    }
}

此时先打印打电话,后打印发信息,因为有两个不同的对象,所以有两个调用者,即两把锁,由于sendMessage方法中存在延时,所以先打印打电话。

5.增加两个静态方法,用同一个对象进行测试,此时是先打印发信息还是打电话?

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
//        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMessage();
        }, "A").start();
​
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            phone1.call();
        }, "B").start();
    }
}
​
class Phone {
    public static synchronized void sendMessage() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发信息");
    }
    public static synchronized void call() {
        System.out.println("打电话");
    }
​
    public void hello() {
        System.out.println("Hello");
    }
}

此时先打印打电话,因为static是静态方法,在静态方法中synchronized锁锁的是Phone的Class模版,由于Phone的Class模版全局唯一,所以两线程共用一把锁,发信息线程先获得锁,所以先执行。

6.增加一个对象,此时两个对象分别调用两个静态同步方法,先执行发信息还是打电话?

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMessage();
        }, "A").start();
​
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            phone2.call();
        }, "B").start();
    }
}
​
class Phone {
    public static synchronized void sendMessage() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发信息");
    }
    public static synchronized void call() {
        System.out.println("打电话");
    }
​
    public void hello() {
        System.out.println("Hello");
    }
}

此时仍然是先打印发信息,理由与情况5相同,对象Phone1和Phone2的Class模版相同(Phone类的Class模版全局唯一),所以Phone1和Phone2共用一把锁,先获得锁的先执行。

7.一个静态同步方法,一个普通同步方法,用同一个对象调用,此时是先打印发信息还是打电话?

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
//        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMessage();
        }, "A").start();
​
        TimeUnit.SECONDS.sleep(1);
        new Thread(() -> {
            phone1.call();
        }, "B").start();
    }
}
​
class Phone {
    public static synchronized void sendMessage() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发信息");
    }
    public synchronized void call() {
        System.out.println("打电话");
    }
​
    public void hello() {
        System.out.println("Hello");
    }
}

此时先打印打电话,再打印发信息,因为sendMessage方法中的锁锁的是Class类模版,call方法中的锁锁的是调用者,因此是两把不同的锁。

8.一个静态同步方法,一个普通同步方法,用两个对象进行调用,先打印发信息还是打电话?

public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        Phone phone1 = new Phone();
        Phone phone2 = new Phone();
        new Thread(() -> {
            phone1.sendMessage();
        }, "A").start();
​
        TimeUnit.SECONDS.sleep(1);
​
        new Thread(() -> {
            phone2.call();
        }, "B").start();
    }
}
​
class Phone {
    public static synchronized void sendMessage() {
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("发信息");
    }
    public synchronized void call() {
        System.out.println("打电话");
    }
​
    public void hello() {
        System.out.println("Hello");
    }
}

此时也是先打印打电话,再打印发信息,原因与7中相同。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值