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
发短信




目录
相关文章
|
10天前
|
设计模式 消息中间件 安全
【JUC】(3)常见的设计模式概念分析与多把锁使用场景!!理解线程状态转换条件!带你深入JUC!!文章全程笔记干货!!
JUC专栏第三篇,带你继续深入JUC! 本篇文章涵盖内容:保护性暂停、生产者与消费者、Park&unPark、线程转换条件、多把锁情况分析、可重入锁、顺序控制 笔记共享!!文章全程干货!
67 2
|
10天前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
56 1
|
10天前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
49 1
|
1月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
77 0
|
缓存 监控 Java
Java中的并发编程:理解并应用线程池
在Java的并发编程中,线程池是提高应用程序性能的关键工具。本文将深入探讨如何有效利用线程池来管理资源、提升效率和简化代码结构。我们将从基础概念出发,逐步介绍线程池的配置、使用场景以及最佳实践,帮助开发者更好地掌握并发编程的核心技巧。
|
Java 数据库
Java中的并发编程:深入理解线程池
在Java的并发编程领域,线程池是提升性能和资源管理的关键工具。本文将通过具体实例和数据,探讨线程池的内部机制、优势以及如何在实际应用中有效利用线程池,同时提出一个开放性问题,引发读者对于未来线程池优化方向的思考。
96 0
|
安全 Java 开发者
Java中的并发编程:深入理解线程池
在Java的并发编程中,线程池是管理资源和任务执行的核心。本文将揭示线程池的内部机制,探讨如何高效利用这一工具来优化程序的性能与响应速度。通过具体案例分析,我们将学习如何根据不同的应用场景选择合适的线程池类型及其参数配置,以及如何避免常见的并发陷阱。
142 1
|
Java 开发者
Java 并发编程之深入理解线程池
在Java并发编程的世界中,线程池扮演着至关重要的角色。本文将深入探讨线程池的内部机制、使用场景以及如何合理配置线程池参数以优化性能。我们将通过实际案例和统计数据,分析线程池对于提升应用性能的具体影响,并讨论在不同应用场景下选择合适线程池策略的重要性。文章旨在为Java开发者提供关于线程池的全面理解和实践指导,帮助其在多线程编程中做出更明智的决策。
|
监控 Java
Java并发编程:深入理解线程池
在Java并发编程领域,线程池是提升应用性能和资源管理效率的关键工具。本文将深入探讨线程池的工作原理、核心参数配置以及使用场景,通过具体案例展示如何有效利用线程池优化多线程应用的性能。