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




目录
相关文章
|
3天前
|
存储 缓存 安全
【并发编程】线程池以及场景题
【并发编程】线程池以及场景题
15 0
|
3天前
|
存储 监控 安全
【并发编程】线程安全(下)
【并发编程】线程安全
7 0
|
3天前
|
存储 安全 Java
【并发编程】线程安全(上)
【并发编程】线程安全
9 0
|
3天前
|
Java 程序员 调度
【并发编程】线程基础知识
【并发编程】线程基础知识
6 0
|
1天前
|
Java 编译器
Java 并发编程中的锁优化策略
【5月更文挑战第17天】在 Java 并发编程中,锁是一种常见的同步机制,用于保护共享资源的访问。然而,不当使用锁可能导致性能问题和死锁风险。本文将探讨 Java 中的锁优化策略,包括锁粗化、锁消除、锁降级以及读写锁等技术,以提高并发程序的性能和可靠性。
|
1天前
|
Java 编译器
Java并发编程中的锁优化策略
【5月更文挑战第17天】在Java并发编程中,锁是一种常见的同步机制,用于保护共享资源。然而,使用不当的锁可能导致性能下降和死锁等问题。本文将探讨Java中锁的优化策略,包括锁粗化、锁消除、锁排序等方法,以提高程序的性能和可靠性。
|
1天前
|
存储 关系型数据库 MySQL
《MySQL 入门教程》第 05 篇 账户和权限,Java高并发编程详解深入理解pdf
《MySQL 入门教程》第 05 篇 账户和权限,Java高并发编程详解深入理解pdf
|
1天前
|
NoSQL 算法 Java
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
【redis源码学习】持久化机制,java程序员面试算法宝典pdf
|
1天前
|
NoSQL Dubbo Java
StringBoot编程式事务与声明式事务java工程师面试突击第一季
StringBoot编程式事务与声明式事务java工程师面试突击第一季
|
2天前
|
数据处理 Python
Python并发编程:实现高效的多线程与多进程
Python作为一种高级编程语言,提供了强大的并发编程能力,通过多线程和多进程技术,可以实现程序的并发执行,提升系统的性能和响应速度。本文将介绍Python中多线程和多进程的基本概念,以及如何利用它们实现高效的并发编程,解决实际开发中的并发性问题。