自学内容网 自学内容网

ReentrantLock(可重入锁) &Semaphore(信号量) & CountDownLatch

ReentrantLock(可重入锁) &Semaphore(信号量)&CountDownLatch

ReentrantLock(可重入锁)

上古时期的Java中,synchronized不够强壮,功能也不够强大,也没有各种优化

ReentrantLock就是用来实现可重入锁的选择(历史遗留)

后来synchronized变得厉害了,ReentrantLock用的少了,但是仍有一席之地

synchronized也是可重入锁

传统的锁的风格,这个对象提供了两个方法: lock unlock 这个写法,就容易引起,加了锁之后,忘记解锁了

在unlock之前,触发了return或者异常,就可能引起unlock执行不到了

正确使用ReentrantLock就需要把unlock操作放到finally中

既然有了synchronized,为啥还要有ReentrantLock?

1.ReentrantLock提供了tryLock操作

lock直接进行加锁,如果加锁不成,就要阻塞

tryLock尝试进行加锁,如果加锁不成,不阻塞,直接返回false

此处通过tryLock提供了更多的“可操作空间”

2.ReentrantLock提供了公平锁的实现

ReentrantLock提供了公平锁的实现,通过队列记录加锁线程的先后顺序

synchronized是非公平锁

遵循先来后到,就是公平

3.搭配的等待通知机制不同

对于synchronized,搭配wait/notify

对于ReentrantLock,搭配 Condition类,功能比 wait/notify略强一点

实际上绝大部分开发中,使用synchronized就够用了

Semaphore(信号量)

信号量是由迪杰斯特拉提出来的

信号量也是操作系统内部给咱们提供的一个机制

操作系统对应的API被JVM封装了下,就可以通过Java代码来调用这里的相关操作了

所谓的锁,本质上也是一种特殊的信号量,

锁,可以认为就是计数值为1的信号量

释放状态,就是1

加锁状态,就是0

对于这种非0即1的信号量,称为“二元信号量”

信号量是更广义的锁

public class ThreadDemo37 {
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(1);
        semaphore.acquire();
        System.out.println("申请1");
        semaphore.acquire();
        System.out.println("申请2");
        semaphore.acquire();
        System.out.println("申请3");
    }
}

在这里插入图片描述

import java.util.concurrent.Semaphore;


public class ThreadDemo38 {
    public static  int count=0;
    public static void main(String[] args) throws InterruptedException {
        Semaphore semaphore=new Semaphore(1);
        Thread t1=new Thread(()->{
           for(int i=0;i<500;i++){
               try {
                   semaphore.acquire();
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
               count++;
               semaphore.release();

           }
        });
        Thread t2=new Thread(()->{
            for(int i=0;i<500;i++){
                try {
                    semaphore.acquire();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                count++;
                semaphore.release();

            }
        });
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(count);
    }
}

在这里插入图片描述

如何确保线程安全呢?

1.synchronized

2.ReentrantLock

3.CAS(原子类)

4.Semaphore

semaphore也可以用来实现生产者消费者模型

定义两个信号量

一个用来表示,队列中有多少个可以被消费的元素,sem1

另一个用来表示队列中有多少个可以放置新元素的空间 ,sem2

生产一个元素,sem1.V() , sem2.P()

消费一个元素,sem1.P() , sem2.V()

CountDownLatch

针对特定场景解决问题的小工具!

比如,多线程执行一个任务,把大的任务拆分成几个部分,由每个线程分别执行

下载一个文件,可能很大,但是可以拆成多个部分,每个线程负责下载一部分,下载完成之和,最终把下载的结果拼在一起。

像多线程下载这样的场景,最终执行完成之后,要把所有内容拼在一起

这个拼必然要等到所有线程执行完成

使用CountDownLatch就可以很方便的感知到这个事情!(比你调用很多次join要更简单方便一些)

如果使用join方式,就只能使用每个线程执行一个任务

借助CountDownLatch就可以让一个线程能执行很多个任务

import java.util.Random;
import java.util.concurrent.CountDownLatch;


public class ThreadDemo39 {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch latch=new CountDownLatch(10);
        for(int i=0;i<10;i++){
            int id=i;
            Thread t=new Thread(()->{
                System.out.println("任务:"+id+",开始下载");
                Random random = new Random();
                // [0, 5)
                int time = (random.nextInt(5) + 1) * 1000;
                try {
                    Thread.sleep(time);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                System.out.println("任务:"+id+",结束下载");
                latch.countDown();

            }) ;
            t.start();
        }
        latch.await();
        System.out.println("所有下载完毕");
    }
}

在这里插入图片描述


原文地址:https://blog.csdn.net/t1750982356/article/details/144065865

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!