认真阅读完这篇文章熟练掌握多线程常见锁的基本用法

简介: 认真阅读完这篇文章熟练掌握多线程常见锁的基本用法

CountDownLatch

官方解释:一种同步辅助,允许一个或多个线程等待,直到在其他线程中执行的一组操作完成countDownLatch用给定的计数进行初始化。由于对countDown方法的调用,await方法会阻塞直到当前计数达到0,之后所有等待的线程都会被释放,所有后续的await调用都会立即返回。这是一个一次性现象——计数不能重置。如果需要重置计数的版本,可以考虑使用CyclicBarrier。countDownLatch是一个通用的同步工具,可以用于多种目的。用一个计数初始化的CountDownLatch作为一个简单的开/关闩锁,或gate:所有调用await的线程在gate处等待,直到它被调用countDown的线程打开。可以使用初始化为N的CountDownLatch使一个线程等待,直到Nthreads完成某些操作,或者某些操作已经完成N次。CountDownLatch的一个有用属性是,它不要求调用countDown的线程在继续之前等待计数达到0,它只是阻止任何线程继续等待,直到所有线程都可以通过。
案例一、

package duoxiancheng2;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;

/**
 * @author yeqv
 * @program A2
 * @Classname T2
 * @Date 2022/2/8 8:38
 * @Email w16638771062@163.com
 */
public class T2 {
    //CountDownLatch
    CountDownLatch latch = new CountDownLatch(1);

    public static void main(String[] args) {

        T2 t = new T2();
        new Thread(() -> t.a(), "A").start();
        new Thread(() -> t.b(), "B").start();
    }

    void a() {
        System.out.println(Thread.currentThread().getName() + "已启动");
        try {
            TimeUnit.SECONDS.sleep(3);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        latch.countDown();
        System.out.println(Thread.currentThread().getName() + "已结束");


    }

    void b() {
        System.out.println(Thread.currentThread().getName() + "已启动");
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "已结束");

    }
}

结果:线程A,B启动。线程A在执行,线程B在等待
在这里插入图片描述
当线程A完成操作开锁之后,线程B开始执行(这里是某组操作完成时,也就是执行了countDown()方法。不是说线程A执行完。可能执行到中间就已经开锁了)
在这里插入图片描述

ReentrantLock

ReentrantLock 最大的特点公平锁
即哪个线程等待的时间最长优先执行
ReentrantLock常常对比着synchronized来分析,我们先对比着来看然后再一点一点分析。
(1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。

(2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

(3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。

public class T3 {
    Lock lock = new ReentrantLock(true);//设置为true公平锁,效率低但公平

    void m() {
        for (int i = 0; i <= 20; i++) {
            lock.lock();
            System.out.println(Thread.currentThread().getName());
            try{
                TimeUnit.SECONDS.sleep(1);
            }catch(Exception e){
                e.printStackTrace();
            }
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        var t = new T3();
        new Thread(t::m,"T1").start();
        new Thread(t::m,"T2").start();
        new Thread(t::m,"T3").start();
        new Thread(t::m,"T4").start();
    }
}

ReadWriteLock

ReadWriteLock也是一个接口,提供了readLock和writeLock两种锁的操作机制,一个资源可以被多个线程同时读,或者被一个线程写,但是不能同时存在读和写线程。
假设在程序中定义一个共享的数据结构用作缓存,它大部分时间提供读服务(例如:查询和搜索),而写操作占有的时间很少,但是写操作完成之后的更新需要对后续的读服务可见。
特性:
读-读不互斥
读-写互斥
写-写互斥
案例、

public class TestReadWriteLock{
    public static void main(String[] args){
        ReadWriteLockDemo rw = new ReadWriteLockDemo();

        // 一个线程进行写
        new Thread(new Runnable(){
            public void run(){
                rw.set((int)(Math.random()*100));
            }
        },"Write:").start();


        // 100个线程进行读操作
        for(int i=0; i<100; i++){
            new Thread(new Runnable(){
                public void run(){
                rw.get();
                }
            },"Read:").start();
        }
    }
}
 
class ReadWriteLockDemo{
    private int number = 0;
    private ReadWriteLock lock = new ReentrantReadWriteLock();
    // 读
    public void get(){
        lock.readLock().lock(); // 上锁
        try{
            System.out.println(Thread.currentThread().getName()+":"+number);
        }finally{
            lock.readLock().unlock(); // 释放锁
        }
    }
    // 写
    public void set(int number){
        lock.writeLock().lock();
        try{
            System.out.println(Thread.currentThread().getName());
            this.number = number;
        }finally{
            lock.writeLock().unlock();
        }
    }
}
相关文章
|
12天前
|
安全 Java 编译器
线程安全问题和锁
本文详细介绍了线程的状态及其转换,包括新建、就绪、等待、超时等待、阻塞和终止状态,并通过示例说明了各状态的特点。接着,文章深入探讨了线程安全问题,分析了多线程环境下变量修改引发的数据异常,并通过使用 `synchronized` 关键字和 `volatile` 解决内存可见性问题。最后,文章讲解了锁的概念,包括同步代码块、同步方法以及 `Lock` 接口,并讨论了死锁现象及其产生的原因与解决方案。
45 10
线程安全问题和锁
|
8天前
|
存储 缓存 安全
【Java面试题汇总】多线程、JUC、锁篇(2023版)
线程和进程的区别、CAS的ABA问题、AQS、哪些地方使用了CAS、怎么保证线程安全、线程同步方式、synchronized的用法及原理、Lock、volatile、线程的六个状态、ThreadLocal、线程通信方式、创建方式、两种创建线程池的方法、线程池设置合适的线程数、线程安全的集合?ConcurrentHashMap、JUC
【Java面试题汇总】多线程、JUC、锁篇(2023版)
|
26天前
|
数据采集 存储 安全
如何确保Python Queue的线程和进程安全性:使用锁的技巧
本文探讨了在Python爬虫技术中使用锁来保障Queue(队列)的线程和进程安全性。通过分析`queue.Queue`及`multiprocessing.Queue`的基本线程与进程安全特性,文章指出在特定场景下使用锁的重要性。文中还提供了一个综合示例,该示例利用亿牛云爬虫代理服务、多线程技术和锁机制,实现了高效且安全的网页数据采集流程。示例涵盖了代理IP、User-Agent和Cookie的设置,以及如何使用BeautifulSoup解析HTML内容并将其保存为文档。通过这种方式,不仅提高了数据采集效率,还有效避免了并发环境下的数据竞争问题。
如何确保Python Queue的线程和进程安全性:使用锁的技巧
|
26天前
|
Java 开发者
Java多线程教程:使用ReentrantLock实现高级锁功能
Java多线程教程:使用ReentrantLock实现高级锁功能
24 1
|
1月前
|
存储 安全 容器
【多线程面试题二十一】、 分段锁是怎么实现的?
这篇文章解释了分段锁的概念和实现方式,通过将数据分成多个段并在每段数据上使用独立锁,从而降低锁竞争,提高并发访问效率,举例说明了`ConcurrentHashMap`如何使用分段锁技术来实现高并发和线程安全。
【多线程面试题二十一】、 分段锁是怎么实现的?
|
19天前
|
安全 Java API
Java线程池原理与锁机制分析
综上所述,Java线程池和锁机制是并发编程中极其重要的两个部分。线程池主要用于管理线程的生命周期和执行并发任务,而锁机制则用于保障线程安全和防止数据的并发错误。它们深入地结合在一起,成为Java高效并发编程实践中的关键要素。
10 0
|
25天前
|
数据采集 Java Python
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
python 递归锁、信号量、事件、线程队列、进程池和线程池、回调函数、定时器
|
28天前
|
Java 开发者
解锁Java并发编程的秘密武器!揭秘AQS,让你的代码从此告别‘锁’事烦恼,多线程同步不再是梦!
【8月更文挑战第25天】AbstractQueuedSynchronizer(AQS)是Java并发包中的核心组件,作为多种同步工具类(如ReentrantLock和CountDownLatch等)的基础。AQS通过维护一个表示同步状态的`state`变量和一个FIFO线程等待队列,提供了一种高效灵活的同步机制。它支持独占式和共享式两种资源访问模式。内部使用CLH锁队列管理等待线程,当线程尝试获取已持有的锁时,会被放入队列并阻塞,直至锁被释放。AQS的巧妙设计极大地丰富了Java并发编程的能力。
30 0
|
1月前
|
缓存 Java
【多线程面试题二十三】、 说说你对读写锁的了解volatile关键字有什么用?
这篇文章讨论了Java中的`volatile`关键字,解释了它如何保证变量的可见性和禁止指令重排,以及它不能保证复合操作的原子性。
|
1月前
|
Java
【多线程面试题二十二】、 说说你对读写锁的了解
这篇文章讨论了读写锁(ReadWriteLock)的概念和应用场景,强调了读写锁适用于读操作远多于写操作的情况,并介绍了Java中`ReentrantReadWriteLock`实现的读写锁特性,包括公平性选择、可重入和可降级。