JUC学习(二)

2.4、生产者消费者问题

sleep和wait的区别

1、类不同

​ sleep是Thread类中的方法,wait是Object中的方法。

2、会不会释放资源

sleep不会释放锁???wait会释放锁。

3、使用范围不同

​ sleep是Thread的一个静态方法,在哪里都能够调用。建议通过TimeUnit枚举类来调用。

TimeUnit.SECONDS.sleep(3);//休眠三秒

​ wait和notify是一组,要一起使用。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-yCnb3iCT-1583658683893)(C:\Users\Ant\AppData\Roaming\Typora\typora-user-images\1583636043409.png)]

(该截图内容来源于:https://www.cnblogs.com/moongeek/p/7631447.html)

  • 需求:有两个线程,有一个初始值为0,实现两个线程交替执行,对变量+1,-1,交替10次。
public class Demo06 {

    public static void main(String[] args) {
        // 创建资源类
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();
    }

}

// 线程之间的通信: 判断  执行  通知
class Data {
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        if (number != 0) {//判断
            this.wait();
        }
        // 执行
        System.out.println(Thread.currentThread().getName() + "\t" + number++);

        // 通知
        this.notifyAll();//唤醒所有的线程
    }

    public synchronized void decrement() throws InterruptedException {
        if (number == 0) {//判断
            this.wait();
        }
        // 执行
        System.out.println(Thread.currentThread().getName() + "\t" + number--);
        // 通知
        this.notifyAll();
    }
}

​ 当有4个线程时,if判断就有问题了,notify唤醒线程后,线程会接着上次的执行继续向下执行,如果条件判断用if,则执行过wait方法的线程被唤醒后,if条件就执行完了,就失去了线程判断的意义,所以条件判断应该用while。

public class Demo06 {

    public static void main(String[] args) {
        // 创建资源类
        Data data = new Data();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "C").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                try {
                    data.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "D").start();
    }

}

// 线程之间的通信: 判断  执行  通知
class Data {
    private int number = 0;

    public synchronized void increment() throws InterruptedException {
        while (number != 0) {//判断
            this.wait();
        }
        // 执行
        System.out.println(Thread.currentThread().getName() + "\t" + number++);

        // 通知
        this.notifyAll();//唤醒所有的线程
    }

    public synchronized void decrement() throws InterruptedException {
        while (number == 0) {//判断
            this.wait();
        }
        // 执行
        System.out.println(Thread.currentThread().getName() + "\t" + number--);
        // 通知
        this.notifyAll();
    }
}

以上的唤醒方式都不能精准的唤醒指定线程。

Condition

  • 需求:A、B、C三个线程各执行10次方法,A线程执行方法p5,接着B线程执行方法p10,最后C线程执行方法p15,依次循环。
public class Demo07 {

    public static void main(String[] args) {
        // 创建资源类
        Data02 data = new Data02();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.p5();
            }
        }, "A").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.p10();
            }
        }, "B").start();

        new Thread(()->{
            for (int i = 0; i < 10; i++) {
                data.p15();
            }
        }, "C").start();
    }

}

// 线程之间的通信: 判断  执行  通知
class Data02 {

    private int status = 1;// A1 B2 C3

    private Lock lock = new ReentrantLock();// 可重入锁

    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();

    public void p5() {
        lock.lock();
        try {
            // 判断
            while (status != 1) {
                condition1.await();
            }
            // 执行
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 通知
            status = 2;
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void p10() {
        lock.lock();
        try {
            // 判断
            while ( status != 2) {
                condition2.await();
            }
            // 执行
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 通知
            status = 3;
            condition3.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void p15() {
        lock.lock();
        try {
            // 判断
            while (status != 3) {
                condition3.await();
            }
            // 执行
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t" + i);
            }
            // 通知
            status = 1;
            condition1.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
        }
    }
}

​ Lock锁中,等待和唤醒要用Lock.newCondition()返回的Condition方法中的await和signal。如果需要精准控制每个线程的唤醒,则需要给每个线程都创建一个Condition。

2.5、8锁现象

问题1:同一资源对象有两个被synchronized修饰的方法,在不同的线程中,哪个先执行?

public class LockDemo01 {

    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendMsg();
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone.sendEmail();
        }, "B").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(() -> {
            phone.sendWinxin();
        }, "C").start();
    }
}

class Phone {

    public synchronized void sendMsg() {
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t sendEmail");
    }

    public synchronized void sendWinxin() {
        System.out.println(Thread.currentThread().getName() + "\t sendWinxin");
    }
}

结果:sendMsg先执行。

​ 结论:被synchronized修饰的方式,锁的对象是方法的调用者,而每个线程中方法的调用对象是同一个,所以先调用的方法先执行。

问题2:在第一题的基础上,sendMsg方法中先休眠3秒,则哪个方法先执行?

public class LockDemo02 {

    public static void main(String[] args) throws InterruptedException {
        Phone phone = new Phone();

        new Thread(() -> {
            phone.sendMsg();
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone.sendEmail();
        }, "B").start();
    }
}

class Phone02 {

    public synchronized void sendMsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t sendEmail");
    }
}

结果:sendMsg先执行。

​ 结论:锁的对象是同一个,哪个先调用就先执行哪个。

问题3:同一个资源对象中,一个普通方法,一个synchronized修饰的方法,哪个先执行?

public class LockDemo03 {

    public static void main(String[] args) throws InterruptedException {
        Phone03 phone = new Phone03();

        new Thread(() -> {
            try {
                phone.sendMsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone.hello();
        }, "B").start();
    }
}

class Phone03 {

    public synchronized void sendMsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public void hello() {
        System.out.println(Thread.currentThread().getName() + "\t hell0");
    }
}

结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行hello。

​ 结论:没有synchronized修饰的方式不是同步方法,不受锁的影响。

问题4:两个资源对象,分别调用不同的synchronized方法,哪个先执行?

public class LockDemo04 {

    public static void main(String[] args) throws InterruptedException {
        Phone04 phone1 = new Phone04();
        Phone04 phone2 = new Phone04();

        new Thread(() -> {
            try {
                phone1.sendMsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone2.sendEmail();
        }, "B").start();
    }
}

class Phone04 {

    public synchronized void sendMsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t sendEmail");
    }
}

结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行sendEmail方法。

​ 结论:synchronized修饰的方式,锁的对象是调用者,两个对象调用方法就是两个锁。

问题5:一个资源对象中,有两个静态的synchronized方法,哪个先执行?

public class LockDemo05 {

    public static void main(String[] args) throws InterruptedException {
        Phone05 phone = new Phone05();

        new Thread(() -> {
            try {
                phone.sendMsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone.sendEmail();
        }, "B").start();
    }
}

class Phone05 {

    public static synchronized void sendMsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public static synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t sendEmail");
    }
}

结果:sendMsg先执行。

​ 结论:用static修饰的方法,锁的对象是Class模板对象,这个是全局唯一的,所以先调用先执行。

问题6:两个资源对象,两个静态方法,哪个先执行?

public class LockDemo06 {

    public static void main(String[] args) throws InterruptedException {
        Phone06 phone1 = new Phone06();
        Phone06 phone2 = new Phone06();

        new Thread(() -> {
            try {
                phone1.sendMsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone2.sendEmail();
        }, "B").start();
    }
}

class Phone06 {

    public static synchronized void sendMsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public static synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t sendEmail");
    }
}

结果:sendMsg先执行

​ 结论:用static修饰的方法,锁的对象是Class模板对象,这个是全局唯一的,所以先调用先执行。

问题7:一个普通同步方法,一个静态同步方法,同一个资源对象,哪个先执行?

public class LockDemo07 {

    public static void main(String[] args) throws InterruptedException {
        Phone07 phone = new Phone07();

        new Thread(() -> {
            try {
                phone.sendMsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone.sendEmail();
        }, "B").start();
    }
}

class Phone07 {

    public static synchronized void sendMsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t sendEmail");
    }
}

结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行sendEmail方法。

​ 结论:静态的同步方法锁的对象是Class对象,普通同步方法锁的对象是调用者,这里是两个锁。

问题8:一个普通同步方法,一个静态同步方法,两个资源对象,哪个先执行?

public class LockDemo08 {

    public static void main(String[] args) throws InterruptedException {
        Phone08 phone1 = new Phone08();
        Phone08 phone2 = new Phone08();

        new Thread(() -> {
            try {
                phone1.sendMsg();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "A").start();

        TimeUnit.SECONDS.sleep(2);

        new Thread(()->{
            phone2.sendEmail();
        }, "B").start();
    }
}

class Phone08 {

    public static synchronized void sendMsg() throws InterruptedException {
        TimeUnit.SECONDS.sleep(3);
        System.out.println(Thread.currentThread().getName() + "\t sendMsg");
    }

    public synchronized void sendEmail() {
        System.out.println(Thread.currentThread().getName() + "\t sendEmail");
    }
}

结果:sendMsg中将休眠三秒的代码屏蔽,则先执行sendMsg方法;如果不屏蔽,则先执行sendEmail方法。

​ 结论:静态的同步方法锁的对象是Class对象,普通同步方法锁的对象是调用者,这里是两个锁。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值