目录

前言

一、atomic包

AtomicInteger类

AtomicReference类

AtomicStampedReference类

二、locks包

接口

Condition

Lock

ReadWriteLock

实现类

ReentrantLock类

ReentrantReadWriteLock类

三、CountDownLatch

四、Semaphore(信号量)

总结


前言

JUC是java.util.concurrent包的简称,在Java5.0添加,目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题!


一、atomic包

方便程序员在多线程环境下,无锁的进行原子操作。Atomic包里的类基本都是使用Unsafe实现的包装类,核心操作是CAS原子操作。

AtomicInteger类

举个例子就是我们平时的  i++,不是原子的操作,在多线程环境下会发生错误,但是使用atomic提供的原子类,就可以很好的避免这个问题发生。

代码演示:

public class Test {static  AtomicInteger atomicInteger  = new AtomicInteger(0);static int count = 100000000;public static void main(String[] args) throws InterruptedException {Thread t = new Thread(()->{for(int i = 0; i < count; i++){//相当于i++atomicInteger.getAndIncrement();}});t.start();Thread t2 = new Thread(() -> {for(int i = 0; i < count; i++){//相当于i--atomicInteger.getAndDecrement();}});t2.start();t.join();t2.join();System.out.println(atomicInteger);}
}

运行结果

可以看到在一亿这个数量级上都是0,足以说明了原子性得到了保持。

AtomicReference类

CAS(关于CAS之前的博客有详细的介绍) 保证的是对一个变量执行操作的原子性,如果对多个变量操作时,CAS 目前无法直接保证操作的原子性的。所以将多个变量封装成对象,通过AtomicReference来保证原子性。

AtomicReference类内部对变量的表示:

 private volatile V value;

AtomicStampedReference类

由于使用CAS会遇到ABA问题(关于ABA问题),所以使用AtomicStampedReference类实现了用版本号作比较的CAS机制。

AtomicStampedReference类内部对变量的表示

  private static class Pair<T> {final T reference;final int stamp;private Pair(T reference, int stamp) {this.reference = reference;this.stamp = stamp;}static <T> Pair<T> of(T reference, int stamp) {return new Pair<T>(reference, stamp);}}  private volatile Pair<V> pair;/*** Creates a new {@code AtomicStampedReference} with the given* initial values.** @param initialRef the initial reference   //变量的引用* @param initialStamp the initial stamp     //这个就是版本号*/public AtomicStampedReference(V initialRef, int initialStamp) {pair = Pair.of(initialRef, initialStamp);}

二、locks包

我们选出几个比较重要接口和实现类讲一下:

接口

上面可以看到一共有三个接口分别是 Condition、Lock、ReadWriteLock。

Condition

condition主要是为了在JUC框架中提供和Java传统的监视器风格的wait,notify和notifyAll方法类似的功能。condition一般和lock一起使用,就像synchronized与wait、notify使用一样。

代码展示:

public class Test {static Lock lock = new ReentrantLock();static Condition condition = lock.newCondition();public static void main(String[] args) throws InterruptedException {Thread t = new Thread(() -> {lock.lock();try {condition.await();System.out.println("子线程醒了");} catch (InterruptedException e) {e.printStackTrace();} finally {lock.unlock();}});t.start();System.out.println("两秒之后叫醒子线程");Thread.sleep(2000);lock.lock();condition.signal();lock.unlock();}
}

运行结果

Lock

在 Lock 接口中,获取锁的方法有 4 个:lock()、tryLock()、tryLock(long,TimeUnit)、lockInterruptibly(),为什么需要这么多方法?这些方法都有什么区别?接下来我们一起来看。

lock()

lock 方法是 Lock 接口中最基础的获取锁的方法,当有可用锁时会直接得到锁并立即返回,当没有可用锁时会一直等待,直到获取到锁为止,它的基础用法如下:

Lock lock = new ReentrantLock();
// 获取锁
lock.lock();
try {// 执行业务代码...
} finally {//释放锁lock.unlock();
}

lockInterruptibly()

lockInterruptibly 方法和 lock 方法类似,当有可用锁时会直接得到锁并立即返回,如果没有可用锁会一直等待直到获取锁,但和 lock 方法不同,lockInterruptibly 方法在等待获取时,如果遇到线程中断会放弃获取锁。它的基础用法如下:

Lock lock = new ReentrantLock();
try {// 获取锁lock.lockInterruptibly();try {// 执行业务方法...} finally {// 释放锁lock.unlock();}
} catch (InterruptedException e) {e.printStackTrace();
}

使用 t.interrupt() 方法可以中断线程执行。

tryLock()

与前面的两个方法不同,使用无参的 tryLock 方法会尝试获取锁,并立即返回获取锁的结果(true 或 false),如果有可用锁返回 true,并得到此锁,如果没有可用锁会立即返回 false。它的基础用法如下:

Lock lock = new ReentrantLock();
// 获取锁
boolean result = lock.tryLock();
if (result) {try {// 获取锁成功,执行业务代码...} finally {// 释放锁lock.unlock();}
} else {// 执行获取锁失败的业务代码...
}

tryLock(long,TimeUnit)

有参数的 tryLock(long,TimeUnit) 方法需要设置两个参数,第一个参数是 long 类型的超时时间,第二个参数是对参数一的时间类型描述(比如第一参数是 3,那么它究竟是 3 秒还是 3 分钟,是第二个参数说了算的)。在这段时间内如果获取到可用的锁了就返回 true,如果在定义的时间内,没有得到锁就会返回 false它的基础用法如下:

Lock lock = new ReentrantLock();
try {// 获取锁(最多等待 3s,如果获取不到锁就返回 false)boolean result = lock.tryLock(3, TimeUnit.SECONDS);if (result) {try {// 获取锁成功,执行业务代码...} finally {// 释放锁lock.unlock();}} else {// 执行获取锁失败的业务代码...}
} catch (InterruptedException e) {e.printStackTrace();
}

synchronized和Lock的区别

1.synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

2.synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

3.通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

ReadWriteLock

首先ReentrantLock(后面介绍)某些时候有局限,如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。

因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。

两个线程都是读:


public class Main {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();Lock readLock = readWriteLock.readLock();Lock writeLock = readWriteLock.writeLock();// “写”的角色,请求写锁// "只读“角色,请求读锁readLock.lock();        // 读锁已经有了// writeLock.lock();       // 写锁锁上Thread t = new Thread() {@Overridepublic void run() {readLock.lock();//      writeLock.lock();System.out.println("子线程也可以加锁成功");}};t.start();}
}

运行结果

一个线程读一个线程写 

public class Main {public static void main(String[] args) {ReadWriteLock readWriteLock = new ReentrantReadWriteLock();Lock readLock = readWriteLock.readLock();Lock writeLock = readWriteLock.writeLock();// “写”的角色,请求写锁// "只读“角色,请求读锁readLock.lock();        // 读锁已经有了// writeLock.lock();       // 写锁锁上Thread t = new Thread() {@Overridepublic void run() {//    readLock.lock();writeLock.lock();System.out.println("子线程也可以加锁成功");}};t.start();}
}

此时由于读锁还没有解锁,所以写锁会阻塞在哪里,不会有输出。

实现类

ReentrantLock类

这个类前面我们已经用到了。首先根据字面意思表示可重入锁。

什么是可重入锁:先看一下代码

public class Main {public static void main(String[] args) {// 我们是主线程Lock lock = new ReentrantLock();    // 名字已经说明,这把锁是可重入锁lock.lock();        // 锁已经被 main 线程锁了lock.lock();System.out.println("说明允许可重入");}
}

也就是是否允许持有锁的线程成功请求到同一把锁,这里得是同一个线程。

对与synchrnized来说,也是可重入锁:代码如下:

public class Main2 {public static void main(String[] args) {Object lock = new Object();synchronized (lock) {   // main 线程已经对 lock 加锁了synchronized (lock) {   // main 线程再次对 lock 请求锁(处于已经锁上状态)System.out.println("这里打印了就说明了 sync 锁是可重入锁");}}}
}

ReentrantLock默认是不公平锁,但是可以修改。synchronized是不公平锁。

public class Main {public static void main(String[] args) {Lock lock = new ReentrantLock(true);    // fair = true:使用公平锁模式Lock lock1 = new ReentrantLock(false);    // fair = false:使用不公平锁模式Lock lock2 = new ReentrantLock();               // 默认情况下是不公平的}
}

Synchronized与ReentrantLock 

1.两者都是可重入锁

可重入锁:重入锁,也叫做递归锁,可重入锁指的是在一个线程中可以多次获取同一把锁,比如: 一个线程在执行一个带锁的方法,该方法中又调用了另一个需要相同锁的方法,则该线程可以直接执行调用的方法,而无需重新获得锁, 两者都是同一个线程每进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

2.synchronized 依赖于 JVM 而 ReentrantLock 依赖于 API

  • synchronized 是依赖于  JVM 实现的,虚拟机团队在 JDK1.6 为 synchronized 关键字进行了很多优化,但是这些优化都是在虚拟机层面实现的。
  • ReentrantLock 是 JDK 层面实现的(也就是 API 层面,需要 lock() 和 unlock() 方法配合 try/finally 语句块来完成)

3.ReentrantLock 比 synchronized 增加了一些高级功能

相比synchronized,ReentrantLock增加了一些高级功能。主要来说主要有三点:①等待可中断;②可实现公平锁;③可实现选择性通知(锁可以绑定多个条件)

  • 等待可中断.通过lock.lockInterruptibly()来实现这个机制。也就是说正在等待的线程可以选择放弃等待,改为处理其他事情。
  • ReentrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。 ReentrantLock默认情况是非公平的,可以通过 ReentrantLock类的ReentrantLock(boolean fair)构造方法来制定是否是公平的。
  • ReentrantLock类线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。 在使用notify()/notifyAll()方法进行通知时,被通知的线程是由 JVM 选择的,用ReentrantLock类结合Condition实例可以实现“选择性通知”

4.使用选择

  • 除非需要使用 ReentrantLock 的高级功能,否则优先使用 synchronized。
  • synchronized 是 JVM 实现的一种锁机制,JVM 原生地支持它,而 ReentrantLock 不是所有的 JDK 版本都支持。并且使用 synchronized 不用担心没有释放锁而导致死锁问题,因为 JVM 会确保锁的释放。

ReentrantReadWriteLock类

首先ReentrantLock某些时候有局限,如果使用ReentrantLock,可能本身是为了防止线程A在写数据、线程B在读数据造成的数据不一致,但这样,如果线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,但是还是加锁了,降低了程序的性能。

因为这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提升了读写的性能。其实都是前面说的~~~。

三、CountDownLatch

CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一(当然这里一个线程也可以返回多个)。当计数器的值为0时,表示所有的线程都已经完成一些任务,然后在CountDownLatch上等待的线程就可以恢复执行接下来的任务。

代码演示:

public class Main {// count: 计数器为 3 个,只有 3 个全部报到了,门闩才会打开static CountDownLatch countDownLatch = new CountDownLatch(3);static class MyThread extends Thread {@Overridepublic void run() {countDownLatch.countDown();countDownLatch.countDown();countDownLatch.countDown();}}public static void main(String[] args) throws InterruptedException {MyThread t = new MyThread();t.start();countDownLatch.await();System.out.println("门闩被打开了");}
}

四、Semaphore(信号量)

Semaphore也叫信号量,在JDK1.5被引入,可以用来控制同时访问特定资源的线程数量,通过协调各个线程,以保证合理的使用资源。

Semaphore内部维护了一组虚拟的许可,许可的数量可以通过构造函数的参数指定。

  • 访问特定资源前,必须使用acquire方法获得许可,如果许可数量为0,该线程则一直阻塞,直到有可用许可。
  • 访问资源后,使用release释放许可。

Semaphore和ReentrantLock类似,获取许可有公平策略和非公平许可策略,默认情况下使用非公平策略。

Semaphore可以用来做流量分流,特别是对公共资源有限的场景,比如数据库连接。
假设有这个的需求,读取几万个文件的数据到数据库中,由于文件读取是IO密集型任务,可以启动几十个线程并发读取,但是数据库连接数只有10个,这时就必须控制最多只有10个线程能够拿到数据库连接进行操作。这个时候,就可以使用Semaphore做流量控制。

代码展示:

public class SemaphoreTest {private static final int COUNT = 40;private static Executor executor = Executors.newFixedThreadPool(COUNT);private static Semaphore semaphore = new Semaphore(10);public static void main(String[] args) {for (int i=0; i< COUNT; i++) {executor.execute(new ThreadTest.Task());}}static class Task implements Runnable {@Overridepublic void run() {try {//读取文件操作semaphore.acquire();// 存数据过程semaphore.release();} catch (InterruptedException e) {e.printStackTrace();} finally {}}}
}

总结

加油哦~~

java---JUC并发包详解相关推荐

  1. Java JUC并发编程详解

    Java JUC并发编程详解 1. JUC概述 1.1 JUC简介 1.2 进程与线程 1.2 并发与并行 1.3 用户线程和守护线程 2. Lock接口 2.1 Synchronized 2.2 什 ...

  2. Java JUC学习 - ConcurrentLinkedDeque 详解

    Java JUC学习 - ConcurrentLinkedDeque 详解 0x00 前言 如何实现并发程序,对于Java以及其他高级语言来说都是一件并不容易的事情.在大一上学期的时候,我们学习了链表 ...

  3. 你真的弄明白了吗?Java并发之AQS详解

    你真的弄明白了吗?Java并发之AQS详解 带着问题阅读 1.什么是AQS,它有什么作用,核心思想是什么 2.AQS中的独占锁和共享锁原理是什么,AQS提供的锁机制是公平锁还是非公平锁 3.AQS在J ...

  4. 【java】java ReentrantLock 源码详解

    文章目录 1.概述 2.问题 3.ReentrantLock源码分析 3.1 类的继承关系 3.2 类的内部类 3.2.1 Sync类 3.2.2 NonfairSync类 3.2.3 FairSyn ...

  5. Java基础:volatile详解

    Java基础:volatile详解 1.volatile保证可见性 1.1.什么是JMM模型? 1.2.volatile保证可见性的代码验证 1.2.1.无可见性代码验证 1.2.1.volatile ...

  6. Java 并发之 AQS 详解(上)

    Java 并发之 AQS 详解 前言 Java SDK 为什么要设计 Lock 死锁问题 synchronized 的局限性 显式锁 Lock Lock 使用范式 Lock 是怎样起到锁的作用呢? 队 ...

  7. Java线程池ThreadPool详解

    Java线程池ThreadPool详解 1. 线程池概述 1.1 线程池简介 1.2 线程池特点 1.3 线程池解决问题 2. 线程池原理分析 2.1 线程池总体设计 2.6 线程池流转状态 2.2 ...

  8. Java单元测试之JUnit4详解

    2019独角兽企业重金招聘Python工程师标准>>> Java单元测试之JUnit4详解 与JUnit3不同,JUnit4通过注解的方式来识别测试方法.目前支持的主要注解有: @B ...

  9. java -jar 和 -cp详解

    java -jar 和 -cp详解 命令行执行程序 假如我们有一个程序,把它打包成Test.jar,如何运行才能成功输出Hello World package com.test; public cla ...

  10. java访问修饰符详解——学java,零基础不怕,不只要理论,更要实践+项目,a href=http://www.bjweixin.com太原维信科技提供 /a...

    java访问修饰符详解--学java,零基础不怕,不只要理论,更要实践+项目 <a href=http://www.bjweixin.com>太原维信科技提供 </a> pub ...

最新文章

  1. 解决ScrollView嵌套ListView高度的问题
  2. 阿里研发效能数据知多少
  3. 重构 - 美股行情系统APP推送改造
  4. 华为厉害了:已启动6G网络技术研究
  5. shell grep cut 【整理】
  6. C程序设计语言现代方法05:选择语句
  7. 树莓派 USB摄像头 实现网络监控
  8. 2021-07-10蓝桥杯单片机学习知识点总结
  9. [slove]Unable to find required classes (javax.activation.DataHandler and javax.m
  10. 山东大学软件工程期末复习知识点总结
  11. 怎样在左边增加中国农历?
  12. 广西大学计算机学院导师张振荣,广西大学硕士研究生导师简介-张振荣
  13. CentOS7.9 通过 kubeadm1.23.5 安装 K8S
  14. sql server2000 安装时出现“另一个安装程序实例已在运行”的解决办法
  15. VueRouter导入
  16. AI公司CEO冒充中科大少年班校友!清华、斯坦福名校经历全造假
  17. 资源分享 | 仅需一个微软账号即可每天白嫖两小时Microsoft学习实验虚拟机云电脑...
  18. java 小鸡_小鸡快跳java
  19. unicode转中文的代码html,unicode的html页面编码转换成中文
  20. curve25519 - 数学的魅力

热门文章

  1. 教你如何几行python代码实现图片转手绘
  2. 纪念一下我那块分区表坏了的60G硬盘
  3. [WP/ctfshow/XXE]ctfshow_XXE_web373-378
  4. 一种基于计算机网络的流媒体,基于网络计算机的流媒体播放器的研究与实现.pdf...
  5. 【人民币识别】人民币序列号识别【含GUI Matlab源码 908期】
  6. 伯努利数(Bernoulli number)
  7. 树上倍增法求最近公共祖先LCA
  8. Sql Server 数据库 表增加列字段
  9. 色值的计算、转换、获取
  10. python输入年月日输出_python下输出指定年月日的方法之一