JUC必要掌握(Synchronized,Lock,可重入锁ReentrantLock,可重入锁,读写锁,自旋锁,线程间通信,集合的线程安全)

本文已参与「新人创作礼」活动,一起开启掘金创作之路

1.锁(Synchronized和lock)

1.1 Synchronized

(1)Synchronized是Java内置的关键字,是Java内置的锁机制。 (2)Synchronized的作用域: 方法: 实例方法:被锁定的是类的实例对象。 静态方法:类对象,也就是Class对象。 代码块: 实例对象:锁的对象是类的实例对象。 class对象:锁的对象是Class对象。 任意实例对象object:锁的对象是实例对象。 (3)Synchronized是可重入锁,非公平锁,不可以中断,锁的释放由JVM决定。

(4)Synchronized获取锁的线程释放锁只会有两种情况:

         1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;

         2)线程执行发生异常,此时 JVM 会让线程自动释放锁。
复制代码

1.2 Lock

(1)Lock是JUC包的接口。 (2)Lock的内置方法: lock():加锁(锁不可中断)。 unlock():解锁。 tryLock():尝试获取锁。 lockInterruptibly():加锁(锁可以通过Interruptibly进行中断)。 newCondition():新建一个监视器。

Synchronized和Lock锁的区别
1、Synchronized 内置的Java关键字, Lock 是一个Java接口。
2、Synchronized 无法判断获取锁的状态,Lock 可以判断是否获取到了锁。
3、Synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁。
4、Synchronized 线程 1(获得锁,阻塞)、线程2(等待,傻傻的等);Lock锁就不一定会等待下去。
5、Synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以 自己设置)。
6、Synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!
复制代码

2.线程安全问题:

2.1 电影院的售票实例:

package com.example.onlyqi.juc;
 
/**
 * @author only-qi
 * @date 2022/5/30 10:16
 * @description
 */
public class Ticket implements Runnable {
    private int ticket = 100;
 
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
         //每个窗口卖票的操作
         //窗口 永远开启
        while (true) {
   
                if (ticket > 0) {//有票 可以卖
 
                    try {
                        //出票操作
                        //使用sleep模拟一下出票时间
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取当前线程对象的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖票:" + ticket--);
                }
            }
     
    }
}

package com.example.onlyqi.juc;
 
/**
 * @author onlyqi
 * @date 2022/5/30 10:18
 * @description
 */
public class Demo {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
//创建三个窗口对象
        Thread t1 = new Thread(ticket, "窗口1");
        Thread t2 = new Thread(ticket, "窗口2");
        Thread t3 = new Thread(ticket, "窗口3");
//同时卖票
        t1.start();
        t2.start();
        t3.start();
    }
}
复制代码

出现的问题:

3.线程同步解决线程安全问题

3.1 同步代码块:

synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式:

synchronized(同步锁){
需要同步操作的代码
}
复制代码

同步锁:

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.

  1. 锁对象 可以是任意类型。

  2. 多个线程对象 要使用同一把锁。

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着

public class Ticket implements Runnable {
    private int ticket = 100;
    Integer lock = new Integer(1);
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        //每个窗口卖票的操作
        //窗口 永远开启
        while (true) {
            synchronized (lock) {
                if (ticket > 0) {//有票 可以卖
                    try {
                        //出票操作 //使用sleep模拟一下出票时间
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //获取当前线程对象的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖票:" + ticket--);
                }
            }
        }
    }
}
复制代码

3.2 同步方法

同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外 等着。

格式:

public synchronized void method(){
可能会产生线程安全问题的代码
}
复制代码

同步锁是谁?

对于非static方法,同步锁就是this。 对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。

package com.example.onlyqi.juc;
 
/**
 * @author onlyqi
 * @date 2022/5/30 10:16
 * @description
 */
public class Ticket implements Runnable {
    private int ticket = 100;
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
        while(true) {
            sellTicket();
        }
 
    }
    public synchronized void sellTicket(){
        //每个窗口卖票的操作
        //窗口 永远开启
        if (ticket > 0) {//有票 可以卖
            try {
                //出票操作 //使用sleep模拟一下出票时间
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //获取当前线程对象的名字
            String name = Thread.currentThread().getName();
            System.out.println(name + "正在卖票:" + ticket--);
        }
 
    }
}
复制代码

3.3 Lock锁

package com.example.onlyqi.juc;
 
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * @author onlyqi
 * @date 2022/5/30 10:16
 * @description
 */
public class Ticket implements Runnable {
    private int ticket = 100;
    Lock lock = new ReentrantLock();
 
    /*
     * 执行卖票操作
     */
    @Override
    public void run() {
//每个窗口卖票的操作
//窗口 永远开启
        while (true) {
            lock.lock();
            if (ticket > 0) {//有票 可以卖
             //出票操作
               //使用sleep模拟一下出票时间
                try {
                    Thread.sleep(50);
                    //获取当前线程对象的名字
                    String name = Thread.currentThread().getName();
                    System.out.println(name + "正在卖:" + ticket--);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    lock.unlock();
                }
             
            }
        }
    }
}
 
复制代码

4. ReentrantLock(可重入锁)

4.1 ReentrantLock默认是非公平锁

小知识点:

1、公平锁
 
定义:多个线程按照先到先得的策略获取锁。
 
优点:所有线程都有机会获得锁,不会饿死
 
缺点:由于所有线程都会经历阻塞态,因此唤醒阻塞线程的开销会很大。
 
2、非公平锁
 
定义:所有的线程拼运气,谁运气好,谁就获取到锁
 
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高
 
缺点:可能会有线程长时间甚至永远获取不到锁,导致饿死。
复制代码

ReentrantLock类不仅实现了Lock接口里的方法,还新增了一些其他的方法,ReenTrantLock提供的方法如下:

// 创建一个 ReentrantLock ,默认是“非公平锁”
ReentrantLock()
 
// 创建策略是fair的 ReentrantLock。fair为true表示是公平锁,fair为false表示是非公平锁
ReentrantLock(boolean fair)
    
// 查询当前线程保持此锁的次数
int getHoldCount()
 
// 返回目前拥有此锁的线程,如果此锁不被任何线程拥有,则返回 null
protected Thread getOwner()
 
// 返回一个collection,它包含可能正等待获取此锁的线程。
protected Collection<Thread> getQueuedThreads()
 
// 返回正等待获取此锁的线程估计数
int getQueueLength()
 
// 返回一个 collection,它包含可能正在等待与此锁相关给定条件的那些线程。
protected Collection<Thread> getWaitingThreads(Condition condition)
 
// 返回等待与此锁相关的给定条件的线程估计数
int getWaitQueueLength(Condition condition)
    
// 查询给定线程是否正在等待获取此锁
boolean hasQueuedThread(Thread thread)
    
// 查询是否有些线程正在等待获取此锁。
boolean hasQueuedThreads()
    
// 查询是否有些线程正在等待与此锁有关的给定条件。
boolean hasWaiters(Condition condition)
    
// 如果是“公平锁”返回true,否则返回false
boolean isFair()
    
// 查询当前线程是否保持此锁。
boolean isHeldByCurrentThread()
    
// 查询此锁是否由任意线程保持。
boolean isLocked()
    
// 获取锁。
void lock()
    
// 如果当前线程未被中断,则获取锁。
void lockInterruptibly()
    
// 返回用来与此 Lock 实例一起使用的 Condition 实例。
Condition newCondition()
    
// 尝试获取锁,仅当锁没有被其他线程获取时才能获取到锁
boolean tryLock()
    
// 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
boolean tryLock(long timeout, TimeUnit unit)
    
// 试图释放此锁。
void unlock()
复制代码

4.2 ReenTrantLock类常用的方法的使用案例:

(1)void lock()

package com.example.onlyqi.juc;
 
 
 
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author onlyqi
 * @date 2022/5/30 13:51
 * @description
 */
public class LocklMain {
    // 公共写的数据
    private static List<Integer> list = new ArrayList<>();
    private static Lock lock= new ReentrantLock();
    public static void main(String[] args) {
        final LocklMain locklMain = new LocklMain();
        new Thread(new Runnable() {
            @Override
            public void run() {
                insert();
            }
        },"tread01").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                insert();
            }
        },"tread02").start();
    }
    private static void insert()
    {
        lock.lock();
        try{
            System.out.println(Thread.currentThread().getName() + " 开始添加元素");
            for (int i =0; i < 5; ++i)
            {
                list.add(i);
                System.out.println(Thread.currentThread().getName() + " 添加了元素 " + String.valueOf(i));
            }
            System.out.println(Thread.currentThread().getName() + " 线程添加元素完毕");
        }
        finally {
            System.out.println(Thread.currentThread().getName() + " 释放了锁");
            lock.unlock();
        }
    }
}
复制代码

例子中在main方法里初始化一个final实例和一个公共写的list,起两个线程,每个线程里都调用这个final实例的insert方法向list里add元素。由于实例方法insert()是需要同步的方法,之前可用synchronized关键字直接声明insert方法,这里在insert方法里通过调用lock.lock()和lock.unlock()对insert()方法里需要同步的部分进行加锁,从结果看达到了同步的目的:Thread01先调用lock.lock()上锁,向list add元素,add完成后调用lock.unlock()方法释放锁,接着Thread02再调用lock.lock()上锁,向list add元素,添加完后再释放锁。

注意private Lock lock=newReentrantLock();这个语句,不应该在insert方法里声明,而是全局声明(可以使用单例模式创建一个全局唯一的锁对象),不然两个线程调用insert()方法时,都会new一个lock出来,两个lock各自锁各自调用的insert()方法,到不到同步的目的。

(2) boolean tryLock()

package com.example.onlyqi.juc;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author onlyqi
 * @date 2022/5/30 14:03
 * @description
 */
 
public class TryLockMain {
    // 公共写的数据
    private static List<Integer> list = new ArrayList<>();
    private static Lock lock= new ReentrantLock();
    public static void main(String[] args) {
        final TryLockMain tryLocklMain = new TryLockMain();
        new Thread(new Runnable() {
            @Override
            public void run() {
                insert();
            }
        },"tread01").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                insert();
            }
        },"tread02").start();
    }
    private static void insert()
    {
        if (lock.tryLock())
        {
            System.out.println(Thread.currentThread().getName() + " 获得锁成功");
            try{
                System.out.println(Thread.currentThread().getName() + " 开始添加元素");
                for (int i =0; i < 5; ++i)
                {
                    list.add(i);
                    System.out.println(Thread.currentThread().getName() + " 添加了元素 " + String.valueOf(i));
                }
                System.out.println(Thread.currentThread().getName() + " 线程添加元素完毕");
            }
            finally {
                System.out.println(Thread.currentThread().getName() + " 释放了锁");
                lock.unlock();
            }
        }
        else {
            System.out.println(Thread.currentThread().getName() + " 获取锁失败");
        }
    }
}
复制代码

运行结果:

跟上面例子很相似,不同的是在需要同步的insert()方法里用tryLock()而不是lock(),同样起两个工作线程,调用final实例对象tryLocklMain的insert()方法向list里add元素。Thread01先获得lock锁,进行for循环打印,此时Thread02也调用tryLock()方法尝试获取lock锁,由于Thread01此时持有该锁,Thread02获取锁失败,无需等待直接返回false,走else语句打印“获取锁失败”。

4.3 可重入锁

ReentrantLock和sychronized是可重入锁

可重入锁:指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提得是同一个对象或者class),这样的锁就叫做可重入锁。

package com.example.onlyqi.juc;
 
/**
 * @author onlyqi
 * @date 2022/5/30 14:28
 * @description
 */
public class ReentrantTest implements Runnable {
 
    public synchronized void get() {
        System.out.println(Thread.currentThread().getName());
        set();
    }
 
    public synchronized void set() {
        System.out.println(Thread.currentThread().getName());
    }
 
    public void run() {
        get();
    }
 
    public static void main(String[] args) {
        ReentrantTest rt = new ReentrantTest();
        for(int i=0;i<100;i++){
            new Thread(rt).start();
        }
    }
}
复制代码

整个过程没有发生死锁的情况,输出结果如下

不可重入锁:不可重入锁,与可重入锁相反,不可递归调用,递归调用就发生死锁。

4.4 读写锁

独占锁:指该锁一次只能被一个线程所持有。对reentrantLock和synchronized都是独占锁
 
共享锁:指该锁可以被多个线程所持有
复制代码

读写锁

ReentrantReadWriteLock 实现了 ReadWriteLock 接口。
 
对ReentrantReadWriteLock其读锁是共享,其写锁是独占
 
写的时候只能被一个线程持有,读的时候能被多个线程所持有
 
读写场景时:读的时候不应该用独占锁,影响并发性,读的时候不会造成数据不一致问题,因此可以多个人共享读
 
● 读-读:能共存
● 读-写:不能共存
● 写-写:不能共存

public class ReadWirteSync {
 
    /**
     * 资源类
     */
    static class MyCache {
        
        private volatile Map<String, Object> map = new HashMap<>();
        // private Lock lock = null;
 
        /**
         * 定义写操作
         * 满足:原子 + 独占
         *
         * @param key
         * @param value
         */
        public void put(String key, Object value) {
            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
        }
 
        public void get(String key) {
            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
        }
 
 
    }
 
    public static void main(String[] args) {
 
 
        MyCache myCache = new MyCache();
        // 线程操作资源类,5个线程写
        for (int i = 0; i < 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt + "");
            }, String.valueOf(i)).start();
        }
        // 线程操作资源类, 5个线程读
        for (int i = 0; i < 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, String.valueOf(i)).start();
        }
    }
 
}
复制代码

运行的结果:

在写入的时候,写入完成时被别的线程打断了。还没写完,就读取了

解决方法:

上面多线程代码是没有加锁的,在线程进行写入操作时,被其他线程频繁打断,从而不具备原子性

使用synchronized锁,又太重量级,以为读的时候可以并发读。

所以,使用读写锁

private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
 
 
 
 
// 创建一个写锁
rwLock.writeLock().lock();
 
// 写锁 释放
rwLock.writeLock().unlock();
 
 
 
//读操作的时候,转换为读锁
 
// 创建一个读锁
rwLock.readLock().lock();
 
// 读锁 释放
rwLock.readLock().unlock();
复制代码

上代码:

package com.example.onlyqi.juc;
 
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantReadWriteLock;
 
/**
 * @author onlyqi
 * @date 2022/5/30 14:57
 * @description
 */
public class ReadWirteSync {
 
 
 
    /**
     * 资源类
     */
    static class MyCache {
 
 
        private ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
        private volatile Map<String, Object> map = new HashMap<>();
        // private Lock lock = null;
 
        /**
         * 定义写操作
         * 满足:原子 + 独占
         *
         * @param key
         * @param value
         */
        public void put(String key, Object value) {
            rwLock.writeLock().lock();
            System.out.println(Thread.currentThread().getName() + "\t 正在写入:" + key);
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            map.put(key, value);
            System.out.println(Thread.currentThread().getName() + "\t 写入完成");
            rwLock.writeLock().unlock();
        }
 
        public void get(String key) {
            rwLock.readLock().lock();
            System.out.println(Thread.currentThread().getName() + "\t 正在读取:");
            try {
                // 模拟网络拥堵,延迟0.3秒
                TimeUnit.MILLISECONDS.sleep(300);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            Object value = map.get(key);
            System.out.println(Thread.currentThread().getName() + "\t 读取完成:" + value);
            rwLock.readLock().unlock();
        }
 
 
    }
 
    public static void main(String[] args) {
 
 
        MyCache myCache = new MyCache();
        // 线程操作资源类,5个线程写
        for (int i = 1; i < 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.put(tempInt + "", tempInt + "");
            }, "写线程"+i+":").start();
        }
        // 线程操作资源类, 5个线程读
        for (int i = 1; i < 5; i++) {
            // lambda表达式内部必须是final
            final int tempInt = i;
            new Thread(() -> {
                myCache.get(tempInt + "");
            }, "读线程"+i+":").start();
        }
    }
 
}
 
 
 
 
 
 
 
 
 
 
 
 
复制代码

运行结果:

4.5 Java自旋锁

1.自旋锁:自旋锁是指当一个线程尝试获取某个锁时,如果该锁已被其他线程占用,就一直循环检测锁是否被释放,而不是进入线程挂起或睡眠状态。

2.为什么要使用自旋锁

CAS全称呼Compare-And-Swap,它是一条CPU并发原语,俗称比较并交换!
​


多个线程对同一个变量一直使用CAS操作,那么会有大量修改操作,从而产生大量的缓存一致性流量,因为每一次CAS操作都会发出广播通知其他处理器,从而影响程序的性能。
复制代码

3. 线程自旋与线程阻塞 阻塞的缺点显而易见,线程一旦进入阻塞(Block),再被唤醒的代价比较高,性能较差。自旋的优点是线程还是Runnable的,只是在执行空代码。当然一直自旋也会白白消耗计算资源,所以常见的做法是先自旋一段时间,还没拿到锁就进入阻塞。JVM在处理synchrized实现时就是采用了这种折中的方案,并提供了调节自旋的参数。

4.首先来对比一下互斥锁和自旋锁。 互斥锁:从等待到解锁过程,线程会从sleep状态变为running状态,过程中有线程上下文的切换,抢占CPU等开销。 自旋锁:从等待到解锁过程,线程一直处于running状态,没有上下文的切换。 5.虽然自旋锁效率比互斥锁高,但它会存在下面两个问题 自旋锁一直占用CPU,在未获得锁的情况下,一直运行,如果不能在很短的时间内获得锁,会导致CPU效率降低。 试图递归地获得自旋锁会引起死锁。递归程序决不能在持有自旋锁时调用它自己,也决不能在递归调用时试图获得相同的自旋锁。

由此可见,我们要慎重的使用自旋锁,自旋锁适合于锁使用者保持锁时间比较短并且锁竞争不激烈的情况。正是由于自旋锁使用者一般保持锁时间非常短,因此选择自旋而不是睡眠是非常必要的,自旋锁的效率远高于互斥锁。

public class SpinLocakDemo {
 
 
    /**
     * 手写一个自旋锁
     * 循环比较获取直到成功为止,没有类似于wait的阻塞
     * 通过CAS操作完成自旋锁,A线程先进来调用myLock方法自己持有锁5秒,B随后进来发现当前有线程持有锁,不是null,所以只能通过自旋等待,直到A释放锁后B随后抢到
     */
    public static class SpinLockDemo {
 
        // 现在的泛型装的是Thread,原子引用线程
        AtomicReference<Thread> atomicReference = new AtomicReference<>();
 
        public void myLock() {
            // 获取当前进来的线程
            Thread thread = Thread.currentThread();
            System.out.println(Thread.currentThread().getName() + "\t come in ");
 
            // 开始自旋,期望值是null,更新值是当前线程,如果是null,则更新为当前线程,否者自旋
            while (!atomicReference.compareAndSet(null, thread)) {
 
            }
        }
 
        /**
         * 解锁
         */
        public void myUnLock() {
 
            // 获取当前进来的线程
            Thread thread = Thread.currentThread();
 
            // 自己用完了后,把atomicReference变成null
            atomicReference.compareAndSet(thread, null);
 
            System.out.println(Thread.currentThread().getName() + "\t invoked myUnlock()");
        }
 
    }
 
 
    public static void main(String[] args) {
 
        SpinLockDemo spinLockDemo = new SpinLockDemo();
 
        // 启动t1线程,开始操作
        new Thread(() -> {
 
            // 开始占有锁
            spinLockDemo.myLock();
 
 
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
 
            // 开始释放锁
            spinLockDemo.myUnLock();
 
        }, "t1").start();
 
 
        // 让main线程暂停1秒,使得t1线程,先执行
        try {
            TimeUnit.SECONDS.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
 
        // 1秒后,启动t2线程,开始占用这个锁
        new Thread(() -> {
 
            // 开始占有锁
            spinLockDemo.myLock();
            // 开始释放锁
            spinLockDemo.myUnLock();
 
        }, "t2").start();
 
    }
}
复制代码

5. 线程间通信

5.1 线程间通信必知点

关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式,

Lock锁的 newContition()方法返回 Condition 对象,Condition 类 也可以实现等待/通知模式。

用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以 进行选择性通知, Condition 比较常用的两个方法:

• await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重 新获得锁并继续执行。

• signal()用于唤醒一个等待的线程。

注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关 的 Lock 锁,调用 await()后线程会释放这个锁,在 singal()调用后会从当前 Condition 对象的等待队列中,唤醒 一个线程,唤醒的线程尝试获得锁, 一旦 获得锁成功就继续执行。

5.2 线程间通信demo

场景---两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1,要求 用线程间通信

5.2.1 synchronized 方案
package com.example.onlyqi.juc;
 
/**
 * @author onlyqi
 * @date 2022/5/30 16:11
 * @description
 */
/**
 * volatile 关键字实现线程交替加减
 */
public class TestVolatile {
    /**
     * 交替加减
     * @param args
     */
    public static void main(String[] args){
        DemoClass demoClass = new DemoClass();
        new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                demoClass.increment();
            }
        }, "线程 A").start();
        new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                demoClass.decrement();
            }
        }, "线程 B").start();
    }
}
 
class DemoClass{
    //加减对象
    private int number = 0;
    /**
     * 加 1
     */
    public synchronized void increment() {
        try {
            while (number != 0){
                this.wait();
            }
            number++;
            System.out.println("--------" + Thread.currentThread().getName() + "加一成功----------,值为:" + number);
            notifyAll();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 减一
     */
    public synchronized void decrement(){
        try {
            while (number == 0){
                this.wait();
            }
            number--;
            System.out.println("--------" + Thread.currentThread().getName() + "减一成功----------,值为:" + number);
            notifyAll();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}
复制代码

运行结果:

5.2.2 Lock方案
package com.example.onlyqi.juc;
 
/**
 * @author onlyqi
 * @date 2022/5/30 16:11
 * @description
 */
 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * volatile 关键字实现线程交替加减
 */
public class TestVolatile {
    /**
     * 交替加减
     * @param args
     */
    public static void main(String[] args){
        DemoClass demoClass = new DemoClass();
        new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                demoClass.increment();
            }
        }, "线程 A").start();
        new Thread(() ->{
            for (int i = 0; i < 5; i++) {
                demoClass.decrement();
            }
        }, "线程 B").start();
    }
}
 
class DemoClass{
    //加减对象
    private int number = 0;
    //声明锁
    private Lock lock = new ReentrantLock();
    //声明钥匙
    private Condition condition = lock.newCondition();
    /**
     * 加 1
     */
    public void increment() {
        try {
            lock.lock();
            while (number != 0){
                condition.await();
            }
            number++;
            System.out.println("--------" + Thread.currentThread().getName() + "加一成功----------,值为:" + number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    /**
     * 减一
     */
    public void decrement(){
        try {
            lock.lock();
            while (number == 0){
                condition.await();
            }
            number--;
            System.out.println("--------" + Thread.currentThread().getName() + "减一成功----------,值为:" + number);
            condition.signalAll();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
复制代码

5.3 线程间定制化通信

==问题: A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照 此顺序循环 2 轮==

package com.example.onlyqi.juc;
 
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
 
/**
 * @author onlyqi
 * @date 2022/5/30 16:21
 * @description
 */
public class DemoClass {
    //通信对象:0--打印 A 1---打印 B 2----打印 C
    private int number = 0;
    //声明锁
    private Lock lock = new ReentrantLock();
    //声明钥匙 A
    private Condition conditionA = lock.newCondition();
    //声明钥匙 B
    private Condition conditionB = lock.newCondition();
    //声明钥匙 C
    private Condition conditionC = lock.newCondition();
    /**
     * A 打印 5 次
     */
    public void printA(int j){
        try {
            lock.lock();
            while (number != 0){
                conditionA.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 A,第" + j + "轮开始");
                    //输出 5 次 A
            for (int i = 0; i < 5; i++) {
                System.out.println("A");
            }
            //开始打印 B
            number = 1;
            //唤醒 B
            conditionB.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    /**
     * B 打印 10 次
     */
    public void printB(int j){
        try {
            lock.lock();
            while (number != 1){
                conditionB.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 B,第" + j + "轮开始");
                    //输出 10 次 B
            for (int i = 0; i < 10; i++) {
                System.out.println("B");
            }
            //开始打印 C
            number = 2;
            //唤醒 C
            conditionC.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    /**
     * C 打印 15 次
     */
    public void printC(int j){
        try {
            lock.lock();
            while (number != 2){
                conditionC.await();
            }
            System.out.println(Thread.currentThread().getName() + "输出 C,第" + j + "轮开始");
                    //输出 15 次 C
            for (int i = 0; i < 15; i++) {
                System.out.println("C");
            }
            System.out.println("-----------------------------------------");
            //开始打印 A
            number = 0;
            //唤醒 A
            conditionA.signal();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}
 
/**
 * volatile 关键字实现线程交替加减
 */
class TestVolatile {
    /**
     * 交替加减
     * @param args
     */
    public static void main(String[] args){
        DemoClass demoClass = new DemoClass();
        new Thread(() ->{
            for (int i = 1; i <= 2; i++) {
                demoClass.printA(i);
            }
        }, "A 线程").start();
        new Thread(() ->{
            for (int i = 1; i <= 2; i++) {
                demoClass.printB(i);
            }
        }, "B 线程").start();
        new Thread(() ->{
            for (int i = 1; i <= 2; i++) {
                demoClass.printC(i);
            }
        }, "C 线程").start();
    }
}
 
复制代码

运行结果:

6. 集合的线程安全

请看我之前写的一篇文章,传送门:

java集合:线程安全的实现方式与分析 传送门:

JUC必要掌握,学习第一天
JUC必要掌握(Callable&Future、JUC 三大辅助类、 阻塞队列),学习第三天

来源:https://juejin.cn/post/7117208351053709349
posted @ 2022-07-17 00:06  程序员小明1024  阅读(61)  评论(0编辑  收藏  举报