JUC中的常见类
创始人
2024-05-23 20:54:20
0

一.ReentrantLock

ReentrantLock是juc包中提供的一种锁,用来保证线程安全,其相对于synchronized存在一些不同点

  1. 上锁方式:ReentrantLock是手动进行上锁和解锁的,这就意味着相对于synchronized,reentrantlock更容易发生死锁现象,(当上锁之后,在解锁之前发生异常,这时候解锁无法进行,其他线程想要获取锁,这时候会发生死锁现象),为了避免死锁现象的产生,我们必须将reentrantLock上锁到解锁的代码放入trycatch finally代码块中,保证解锁的执行

ReentrantLock reentrantLock1 = new ReentrantLock();try {reentrantLock1.lock();}catch (Exception e){e.printStackTrace();}finally {reentrantLock1.unlock();}

需要注意的是ReentrantLock()中有一个tryLock()方法,它有一定的等待时间,如果在等待时间内获取不到锁,就放弃索取该锁,去执行其他任务

2.相对于synchronized,其支持公平锁:

ReentrantLock reentrantLock = new ReentrantLock(true);

3.阻塞和唤醒的方式:我们知道,synchronized使用的是Object类中的wait()、notify()和notifyall()的方法进行阻塞和唤醒,而ReentrantLock进行阻塞和解锁的方式如下:

 //创建对象ReentrantLock reentrantLock = new ReentrantLock(true);//需要手动进行上锁和解锁try {reentrantLock.lock();//创建condition对象Condition condition = reentrantLock.newCondition();condition.await();condition.signal();condition.signalAll();

reentrantLock对象利用newCondition()方法,创建阻塞对象(在底层创建一个条件对象),使用其await()、signal()、和signalAll()方法进行阻塞和唤醒,但是synchronized在底层维护一个阻塞队列和就绪队列,但是ReentrantLock中的每一个condition对象都会维护一个阻塞队列,不同线程会根据条件的不同进入不同的阻塞队列,最终维护一个就绪队列和多个阻塞队列

4.ReentrantLock支持读写锁:我们可以利用ReentrantReadWriteLock类创建读锁和写锁,其中读锁是共享锁,写锁是独占锁

 ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();//获取读锁ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();//获取写锁ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();

二.原子类

原子类是利用CAS来来保证线程安全的实现方式,其性能要比加锁操作高很多

我们写以下代码来体会其作用:

public class TestAtomic {public static void main(String[] args) throws InterruptedException {AtomicInteger atomicInteger = new AtomicInteger();Thread t1 =new Thread(()->{for (int i=0;i<50000  ;++i){atomicInteger.getAndIncrement();}});t1.start();Thread t2 =new Thread(()->{for (int i = 0; i < 50000; i++) {atomicInteger.getAndIncrement();}});t2.start();t1.join();t2.join();System.out.println(atomicInteger);}
}

其内部机制:利用CAS+自旋保证原子性

我们没有使用synchronized进行上锁,却没有产生原子性问题,所以它的作用就像它的名字一样:保持原子性,我们去说明其中的几个方法:

三.JUC中的工具类

3.1 Semaphore(信号量)

semaphore用来记录可用资源的个数,在本质上说是一个计数器

可以把信号量想象成是停车场的展示牌: 当前有车位 100 个. 表示有 100 个可用资源.

当有车开进去的时候, 就相当于申请一个可用资源, 可用车位就 -1 (这个称为信号量的 P 操作)

当有车开出来的时候, 就相当于释放一个可用资源, 可用车位就 +1 (这个称为信号量的 V 操作)

如果计数器的值已经为 0 了, 还尝试申请资源, 就会阻塞等待, 直到有其他线程释放资源.

Semaphore 的 PV 操作中的加减计数器操作都是原子的, 可以在多线程环境下直接使用.

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;/*** @author tongchen* @create 2023-02-08 16:06*/
public class SemaphoreTest {public static void main(String[] args) {//创建信号量Semaphore semaphore = new Semaphore(5);//创建任务Runnable runnable=new Runnable() {@Overridepublic void run() {//消耗资源try {semaphore.acquire();System.out.println( Thread.currentThread().getName()+"获取到资源了");//执行任务System.out.println(Thread.currentThread().getName()+"在执行任务");//释放资源TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+"释放资源了");semaphore.release();} catch (InterruptedException e) {throw new RuntimeException(e);}}};//通过for循环不断创建线程for (int i = 0; i <10 ; i++) {Thread thread=new Thread(runnable,i+"");thread.start();}}
}

3.2 CountDownLatch

同时等待 N 个任务执行结束.

好像跑步比赛,10个选手依次就位,哨声响才同时出发;所有选手都通过终点,才能公布成绩

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;/*** @author tongchen* @create 2023-02-08 16:21*/
public class CountDownLatchTest {public static void main(String[] args) throws InterruptedException {//创建10个任务CountDownLatch countDownLatch = new CountDownLatch(10);Runnable runnable=new Runnable() {@Overridepublic void run() {//准备就绪System.out.println(Thread.currentThread().getName()+"准备就绪");//开始运行System.out.println(Thread.currentThread().getName()+"开始运行");try {TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName()+"运行结束");countDownLatch.countDown();} catch (InterruptedException e) {throw new RuntimeException(e);}}};for (int i = 0; i < 10; i++) {Thread thread=new Thread(runnable,"任务"+i);thread.start();}//等待任务全部执行结束countDownLatch.await();System.out.println("任务全部执行结束了......");}
}

3.3线程安全的集合类

当我们在多线程中使用集合类,同样会产生线程安全问题:

import java.util.ArrayList;
import java.util.Arrays;/*** @author tongchen* @create 2023-02-08 16:34*/
public class ArraylistWithMultithreading {public static void main(String[] args) {//创建集合ArrayList arrayList = new ArrayList<>();//创建任务并加入多线程for (int i = 0; i < 10; i++) {int x=i;Thread  thread=new Thread(()->{arrayList.add(x);});thread.start();System.out.println(arrayList);}System.out.println("-----------------------");System.out.println(arrayList);}
}

我们如何解决集合类中的线程安全问题呢?

  1. 手动加锁,使用synchronized包裹代码块或者使用reentrantlock手动加锁释放锁(一定要注意线程问题存在很多,我们在不同线程中进行读和写的操作都要记得加锁)

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.locks.ReentrantLock;/*** @author tongchen* @create 2023-02-08 16:34*/
public class ArraylistWithMultithreading {static Object loker=new Object();public static void main(String[] args) {//创建集合ArrayList arrayList = new ArrayList<>();List list = Collections.synchronizedList(arrayList);ReentrantLock reentrantLock = new ReentrantLock();CopyOnWriteArrayList integers = new CopyOnWriteArrayList<>(arrayList);//创建任务并加入多线程for (int i = 0; i < 11; i++) {int x=i;Thread  thread=new Thread(()->{reentrantLock.lock();arrayList.add(x);reentrantLock.unlock();});thread.start();reentrantLock.lock();System.out.println(arrayList);reentrantLock.unlock();}System.out.println("-----------------------");}
}
  1. 使用vector等线程安全的集合

  1. 使用工具类:使用Conllections类,将集合包裹


import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;/*** @author tongchen* @create 2023-02-08 16:34*/
public class ArraylistWithMultithreading {static Object loker=new Object();public static void main(String[] args) {//创建集合ArrayList arrayList = new ArrayList<>();List list = Collections.synchronizedList(arrayList);CopyOnWriteArrayList integers = new CopyOnWriteArrayList<>(arrayList);//创建任务并加入多线程for (int i = 0; i < 11; i++) {int x=i;Thread  thread=new Thread(()->{list.add(x);});thread.start();System.out.println(list);}System.out.println("-----------------------");System.out.println(list);}
}
  1. 使用CopyOnWriteArraylist

如果是读Arraylist,不需要进行加锁,但是如果是对ArrayList进行写操作时,

此时使用 CopyOnWriteArrayList就是把这个ArrayList'给复制了一份,先修改

副本,修改之后引用再指向副本,保证修改的同时对于读操作是没有任何影响的,读的时候先读旧的版本,不会出现读到一个没修改完的中间状态,适用于多读少写的业务场景

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;/*** @author tongchen* @create 2023-02-08 16:34*/
public class ArraylistWithMultithreading {static Object loker=new Object();public static void main(String[] args) throws InterruptedException {//创建集合ArrayList arrayList = new ArrayList<>();List list = Collections.synchronizedList(arrayList);CopyOnWriteArrayList integers = new CopyOnWriteArrayList<>(arrayList);//创建任务并加入多线程for (int i = 0; i < 5; i++) {int x=i;Thread  thread=new Thread(()->{integers.add(x);});thread.start();System.out.println(integers);}System.out.println("-----------------------");System.out.println(integers);}
}

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
【PdgCntEditor】解... 一、问题背景 大部分的图书对应的PDF,目录中的页码并非PDF中直接索引的页码...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
修复 爱普生 EPSON L4... L4151 L4153 L4156 L4158 L4163 L4165 L4166 L4168 L4...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...