Java并发编程学习系列一:线程与锁(二)

简介: Java并发编程学习系列一:线程与锁(二)

生产消费者问题


Sychronized,wait,notify

/**
 * @author hresh
 * @date 2020/2/16 21:19
 * @description
 * 线程之间的通信问题:生产者和消费者问题
 * 传统解决方法,Sychronized,wait,notify三者结合使用
 */
public class A {
    public static void main(String[] args) {
        Data data = new Data();
        new Thread(()->{
            for (int i=0;i<20;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.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}
class Data {
    private int num = 0;
    //判断等待,业务,通知
    public synchronized void increment() throws InterruptedException {
        //注意这里使用的是while判断,而非if判断,防止虚假唤醒
        while (num != 0){
            //等待
            this.wait();
        }
        num++;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,我+1完毕了
        this.notifyAll();
    }
    public synchronized void decrement() throws InterruptedException {
        while (num == 0){
            this.wait();
        }
        num--;
        System.out.println(Thread.currentThread().getName()+"=>"+num);
        //通知其他线程,我-1完毕了
        this.notifyAll();
    }
}
复制代码


关于 wait 方法的判断,必须使用 while 条件,官方文档对此是这样描述的。


1.jpg


Lock,await,signal


public class LockDemo {
    public static void main(String[] args) {
        Data2 data = new Data2();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
        new Thread(()->{
            for (int i=0;i<10;i++){
                try {
                    data.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"D").start();
    }
}
class Data2 {
    private int num = 0;
    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();
    public void increment()  {
        lock.lock();
        try {
            while (num != 0){
                //等待
                condition.await();
            }
            num++;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其他线程,我+1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void decrement() {
        lock.lock();
        try {
            while (num == 0){
                //等待
                condition.await();
            }
            num--;
            System.out.println(Thread.currentThread().getName()+"=>"+num);
            //通知其他线程,我-1完毕了
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
复制代码


从结果中可以发现这种实现方式和 Synchronized 关键字效果一致,每当生产者生产完毕,都有一个消费者可以获取到来消费。此时如果有这么一个需求:消费者B获取生产者A的内容,D获取C的内容,实现精准获取,有序执行。采用 Synchronized 是无法满足该需求的,但是 Lock 锁有方法可以实现。


定义3个线程,A执行完该B执行,B执行完该C执行,之后再从A开始。类似案例:比如说在生产线中:下单-》支付-》交易-》物流


public class NewLockDemo {
    public static void main(String[] args) {
        Data3 data3 = new Data3();
        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printA();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"A").start();
        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printB();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"B").start();
        new Thread(()->{
            for(int i=0;i<10;i++){
                try {
                    data3.printC();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        },"C").start();
    }
}
class Data3{
    private Lock lock = new ReentrantLock();
    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();
    private int num = 1;
    public void printA(){
        lock.lock();
        try {
            while (num != 1){
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName()+"AAAAA");
            num = 2;
            condition2.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printB(){
        lock.lock();
        try {
            while (num != 2){
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName()+"BBBBBBBB");
            num = 3;
            condition3.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void printC(){
        lock.lock();
        try {
            while (num != 3){
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName()+"CCCCCCCC");
            num = 1;
            condition1.signal();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
复制代码


通过声明3个 Condition 对象,每次调用方法,指定某个 Condition 等待,然后释放某个准确的 Condition。


8锁问题

Synchronized 锁实例对象


如下案例,phone 对象调用 sendSms 和 call 方法,先执行哪个方法,就意味着该方法获取了 phone 对象的锁,另外一个方法就必须等待锁被释放后,才可以执行。


/**
 * @author hresh
 * @date 2020/2/16 22:02
 * @description
 * 8锁,关于锁的8个问题
 * 1.标准情况下,先打印发短信还是打电话?答:1、发短信;2、打电话
 * 2.sendSms延时3m,先打印哪个?答:1、发短信;2、打电话
 */
public class Test1 {
    public static void main(String[] args) {
        Phone phone = new Phone();
        new Thread(()->{
            phone.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone.call();
        },"B").start();
    }
}
class Phone{
    // Synchronized 锁的对象是方法的调用者,即new出来的对象
    //以下两个方法共用同一把锁
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
复制代码


当对象中除了同步方法外,还有一个普通方法,执行顺序又将变成什么样子?


//3.一个对象,添加一个普通方法,先打印hello还是发短信?
public class Test2 {
    public static void main(String[] args) {
        Phone2 phone1 = new Phone2();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.hello();
        },"B").start();
    }
}
class Phone2{
    //Sychronized锁的对象是方法的调用者
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public void hello(){
        System.out.println("hello");
    }
}
复制代码


执行结果为:


hello
发短信
复制代码


在第一个案例中,我们说过 Synchronized 锁的对象是 phone 对象,这是针对同步方法而言,但是普通方法的调用并不需要获取锁,所以当同步方法在延时等待时,普通方法就可以正常执行。


当锁对象有两个时,分别调用一个方法,结果又是怎样?


//两个对象,分别调用发短信和打电话,先打印打电话
public class Test2 {
    public static void main(String[] args) {
        //两个对象
        Phone2 phone1 = new Phone2();
        Phone2 phone2 = new Phone2();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
}
class Phone2{
    //Sychronized锁的对象是方法的调用者
    public synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
复制代码


执行结果:


打电话
发短信
复制代码


因为有两个锁对象,所以大家各自都有一把锁,不需要等待锁释放,sendSms 方法因为要延迟等待,所以就是 call 方法先执行。


Synchronized 锁Class对象


被 static 修饰的方法为静态方法,随着类的加载而加载,只加载一次,不需要实例化对象,可以通过类直接进行调用,所以此时锁的对象是 Phone3.class。


//5.一个对象,两个方法都声明为静态的,分别调用发短信和打电话,先打印发短信
public class Test3 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.call();
        },"B").start();
    }
}
class Phone3{
    //Sychronized锁的对象是方法的调用者
    //static 静态方法
    //类一加载就有了!Class模版
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public static synchronized void call(){
        System.out.println("打电话");
    }
}
复制代码


如果我们声明两个对象,分别调用这两个方法,结果又是怎样的呢?


//6.两个对象,同样都是静态的,先打印发短信,因为虽然对象不同,但是锁的是同一个类模版
public class Test3 {
    public static void main(String[] args) {
        Phone3 phone1 = new Phone3();
        Phone3 phone2 = new Phone3();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
复制代码


结果与上面一样,都是先打印发短信。原因在于 Phone.class 只有一份,关于它的锁谁先拿到就先执行,与 new 多少个对象无关。


Synchronized 多锁


当代码中存在多个锁时,也即上面提到的两种锁,一种是实例对象锁,一种是类对象锁。


//7.一个对象,一个静态同步,一个普通同步,先打印打电话
public class Test4 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.call();
        },"B").start();
    }
}
class Phone4{
    //静态同步方法
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        System.out.println("打电话");
    }
}
复制代码


结果中先打印打电话,原因在于这两个方法需要拿到的锁不一样,所以并不存在等待锁释放。


即使声明两个对象,一个调用静态同步方法,一个调用普通同步方法,结果与上述一致。


//8.两个对象,一个静态同步,一个普通同步,先打印打电话
public class Test4 {
    public static void main(String[] args) {
        Phone4 phone1 = new Phone4();
        Phone4 phone2 = new Phone4();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone2.call();
        },"B").start();
    }
}
复制代码


Synchronized 锁代码块


Synchronized 关键字还可以用来修饰同步代码块,在某些情况下,我们可能只需要同步一部分代码,没必要对整个方法进行同步操作,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了,同步代码块的使用示例如下:


//this,当前实例对象锁
synchronized(this){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
//class对象锁
synchronized(AccountingSync.class){
    for(int j=0;j<1000000;j++){
        i++;
    }
}
复制代码


我们可以使用 this 对象(代表当前实例)或者当前类的 class 对象作为锁。以下案例来比较这三种方式的运行结果:


//9.一个对象,一个静态同步,一个普通同步方法,一个同步代码块,先打印打电话,最后发短信
public class Test5 {
    public static void main(String[] args) {
        Phone5 phone1 = new Phone5();
        new Thread(()->{
            phone1.sendSms();
        },"A").start();
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        new Thread(()->{
            phone1.call();
        },"B").start();
        new Thread(()->{
            phone1.test();
        },"C").start();
    }
}
class Phone5{
    //静态同步方法
    public static synchronized void sendSms(){
        try {
            TimeUnit.SECONDS.sleep(4);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("发短信");
    }
    public synchronized void call(){
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("打电话");
    }
    public void test(){
        synchronized (this){
            System.out.println("synchronized code");
        }
    }
}
复制代码


执行结果为:


打电话
synchronized code
发短信




目录
相关文章
|
5天前
|
存储 缓存 Java
Java 并发编程——volatile 关键字解析
本文介绍了Java线程中的`volatile`关键字及其与`synchronized`锁的区别。`volatile`保证了变量的可见性和一定的有序性,但不能保证原子性。它通过内存屏障实现,避免指令重排序,确保线程间数据一致。相比`synchronized`,`volatile`性能更优,适用于简单状态标记和某些特定场景,如单例模式中的双重检查锁定。文中还解释了Java内存模型的基本概念,包括主内存、工作内存及并发编程中的原子性、可见性和有序性。
Java 并发编程——volatile 关键字解析
|
9天前
|
算法 Java 调度
java并发编程中Monitor里的waitSet和EntryList都是做什么的
在Java并发编程中,Monitor内部包含两个重要队列:等待集(Wait Set)和入口列表(Entry List)。Wait Set用于线程的条件等待和协作,线程调用`wait()`后进入此集合,通过`notify()`或`notifyAll()`唤醒。Entry List则管理锁的竞争,未能获取锁的线程在此排队,等待锁释放后重新竞争。理解两者区别有助于设计高效的多线程程序。 - **Wait Set**:线程调用`wait()`后进入,等待条件满足被唤醒,需重新竞争锁。 - **Entry List**:多个线程竞争锁时,未获锁的线程在此排队,等待锁释放后获取锁继续执行。
39 12
|
6天前
|
存储 安全 Java
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
66 2
|
22天前
|
安全 算法 Java
Java多线程编程中的陷阱与最佳实践####
本文探讨了Java多线程编程中常见的陷阱,并介绍了如何通过最佳实践来避免这些问题。我们将从基础概念入手,逐步深入到具体的代码示例,帮助开发者更好地理解和应用多线程技术。无论是初学者还是有经验的开发者,都能从中获得有价值的见解和建议。 ####
|
22天前
|
Java 调度
Java中的多线程编程与并发控制
本文深入探讨了Java编程语言中多线程编程的基础知识和并发控制机制。文章首先介绍了多线程的基本概念,包括线程的定义、生命周期以及在Java中创建和管理线程的方法。接着,详细讲解了Java提供的同步机制,如synchronized关键字、wait()和notify()方法等,以及如何通过这些机制实现线程间的协调与通信。最后,本文还讨论了一些常见的并发问题,例如死锁、竞态条件等,并提供了相应的解决策略。
45 3
|
3天前
|
Java 关系型数据库 MySQL
【JavaEE“多线程进阶”】——各种“锁”大总结
乐/悲观锁,轻/重量级锁,自旋锁,挂起等待锁,普通互斥锁,读写锁,公不公平锁,可不可重入锁,synchronized加锁三阶段过程,锁消除,锁粗化
|
3天前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
7月前
|
存储 安全 Java
24、使用 Java 官方教程学习:① 类变量和类方法详解;② 深入介绍 main() 方法
24、使用 Java 官方教程学习:① 类变量和类方法详解;② 深入介绍 main() 方法
102 1
|
7月前
|
存储 Java
【JAVA学习之路 | 进阶篇】Map接口及其实现类及常用方法
【JAVA学习之路 | 进阶篇】Map接口及其实现类及常用方法
|
7月前
|
Java 测试技术 C++
【JAVA学习之路 | 进阶篇】File类及常用方法
【JAVA学习之路 | 进阶篇】File类及常用方法