JUC并发常用工具学习
今天主要来和大家分享一下JUC相关的一些简单知识,线程池文章就不介绍了,前面的文章有介绍,本文主要介绍Lock和认识synchronized和并发的一些工具类的使用。
Lock
传统的锁有synchronized关键字,我们可以直接在方法和代码块中使用它。
在Java中有ReentrantLock、ReentrantReadWriteLock

常用的ReentrantLock,默认采用的是非公平锁,也有公平锁的实现方式,简单点说,
公平锁需要根据申请锁的顺序来获取锁,按照先来先服务的原则。
非公平锁可以不按照申请锁的顺序,后申请的线程也可以按先申请锁的线程先获取锁。
public ReentrantLock() {
sync = new NonfairSync();
}
/**
* Creates an instance of {@code ReentrantLock} with the
* given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
下面我们也可以通过一个卖票的小例子来看ReentrantLock的使用,其中最主要的是lock和unlock方法,这方法需要成对出现,否则可能会出现锁未释放的情况。
class Ticket1 {
private int number = 50;
Lock lock = new ReentrantLock();
/**
* 卖票的方式
*/
public synchronized void sale() {
// 加锁
lock.lock();
try {
if (number > 0) {
System.out.println(Thread.currentThread().getName() + "卖出了" + (number--) + "票,剩余" + number);
}
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
}
}
简单对比Lock和synchronized:
synchronized和Lock的类型不同,一个是关键字,一个是类。
Lock可以判断锁的状态,而synchronized是不可以的。
synchronized的锁释放是自动释放的,而Lock是需要手动释放锁。
synchronized线程会一直等待,而Lock是不一定等待下去。
synchronized是可重入锁,是非公平锁,Lock可以重入锁,可以设置是否公平。
Lock适合锁大量代码,synchronized适合锁少量的代码。
生产者的消费者问题
synchronized版
这个问题一直都是比较经典的问题,我们可以使用synchronized关键字实现配置wait方法,notifyAll方法和notify方法实现。
注意wait方法只能出现在同步方法中。
下面的例子需要防止虚假唤醒的问题,即需要采用循环的方式而不能采用if的方式,在JDK1.8文章的也有说明

class Data {
private int number = 0;
/**
* 资源类
*/
public synchronized void increment() {
// if (number != 0) {
while (number != 0) {
// 等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number++;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知其他线程
this.notifyAll();
}
public synchronized void decrement() {
// if (number == 0) {
while (number == 0) {
// 等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
number--;
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 通知
this.notifyAll();
}
}
Lock版
可能有人也会说,有synchronize不九可以处理了么,在Java中还有一个配合也是可以实现这个功能的,也就是Lock配置Condition的await方法和signal方法,这个比synchronizd强在哪里呢?这个可以实现精准唤醒,而通过synchronized方法里面的notify不能实现精准唤醒。
下面这个例子就可以实现按顺序实现唤醒。
class Data2 {
private Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
private int number = 1;
public void printA() {
lock.lock();
try {
while (number != 1) {
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 唤醒指定的人
number = 2;
condition2.signal();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB() {
lock.lock();
try {
while (number != 2) {
// 等待
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 唤醒指定的人
number = 3;
condition3.signal();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC() {
lock.lock();
try {
while (number != 3) {
// 等待
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "=>" + number);
// 唤醒指定的人
number = 1;
condition1.signal();
} catch (Exception ex) {
ex.printStackTrace();
} finally {
lock.unlock();
}
}
}
理解锁
我们如何判断锁的是谁?锁的是调用该方法的对象还是Class模板呢?这样可以帮助我们深入理解锁。
这里还是采用狂神视频的提到的8锁问题,应该看完就可以理解synchronized锁的判断。
场景一
是输出发短信还是打电话?发短信
发短信延迟4秒,是发短信还是打电话?发短信
synchronized锁的是方法的调用者,两个方法用的是同一把锁,谁先拿到先执行。
public class Test1 {
@SneakyThrows
public static void main(String[] args){
Phone phone = new Phone();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
TimeUnit.SECONDS.sleep(1);
new Thread(() -> {
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
场景二
下面先输出哪个结果?发短信还是hello? hello
hello方法是没有锁的,不是同步方法,不受锁影响
public class Test2 {
public static void main(String[] args){
Phone1 phone = new Phone1();
new Thread(() -> {
phone.sendMsg();
}, "A").start();
new Thread(() -> {
phone.call();
}, "B").start();
new Thread(() -> {
phone.hello();
}, "C").start();
}
}
class Phone1 {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
public void hello() {
System.out.println("hello");
}
}
场景三
- 下面重新声明两个对象,一个调用同步发短信方法,一个调用call方法,先执行哪个方法? call
因为两个同步方法,锁的是方法调用者的对象,这里有两把锁
public class Test2 {
public static void main(String[] args){
Phone1 phone1 = new Phone1();
Phone1 phone2 = new Phone1();
new Thread(() -> {
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
class Phone1 {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
public void hello() {
System.out.println("hello");
}
}
场景四
- 增加两个静态同步方法,只有一个对象,先打印,发短信?打电话?发短信
类一加载就有了,这里锁的Class对象,Class是唯一的
public class Test3 {
public static void main(String[] args){
Phone3 phone3 = new Phone3();
new Thread(() -> {
phone3.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone3.call();
}, "B").start();
}
}
class Phone3 {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public static synchronized void call() {
System.out.println("call");
}
public void hello() {
System.out.println("hello");
}
}
两个对象,是发短信还是打电话?发短信
Class模板只有一个
public class Test3 {
public static void main(String[] args){
Phone3 phone3 = new Phone3();
Phone3 phone2 = new Phone3();
new Thread(() -> {
phone3.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone2.call();
}, "B").start();
}
}
- 一个对象,一个静态同步方法,一个普通同步方法,先发短信,还是先打电话?先打电话
sendMsg锁的是Class模板,call锁的是调用的对象
- 如果是两个对象,结果也是一样,结果还是打电话
public class Test4 {
public static void main(String[] args){
Phone4 phone4 = new Phone4();
new Thread(() -> {
phone4.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(() -> {
phone4.call();
}, "B").start();
}
}
class Phone4 {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(4);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
常见的并发辅助类
CountDownLatch
CountDownLatch里面定义了一个减法计算器,和一个阻塞队列,常用的方法有countDown和await方法,countDown相当于-1,await方法会等待计算器归零,然后再被唤醒向下执行。
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
// 总数6
CountDownLatch countDownLatch = new CountDownLatch(5);
// -1
countDownLatch.countDown();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "go out!");
countDownLatch.countDown();
}, String.valueOf(i)).start();
}
// 等待计数器归零,然后再向下执行
countDownLatch.await();
System.out.println("close door!");
}
}
CyclicBarrier
需要等待线程全部达到共同屏障点的同步辅助,其实就是一个加法计算器,主要是可以实现让一组线程达到同一屏障点,然后再进行后续操作。
public class CyclicBarrierDemo {
public static void main(String[] args){
CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () ->{
System.out.println("召唤神龙成功!");
});
for (int i = 1; i <= 7; i++) {
int temp = i;
new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "收集" + temp + "个龙珠");
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
}).start();
}
}
}
Semaphore
一个计数信号量,其中常用的两个方法acquire用于获取许可证,release释放许可证。
可以用于限流等场景,比如下面的停车场例子,当初始化为一个时,也可以用于做互斥锁。
public class SemaphoreTest {
public static void main(String[] args){
// 停车位
Semaphore semaphore = new Semaphore(3);
for (int i = 1; i <= 6; i++) {
new Thread(() -> {
// 等到
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "抢到车位");
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName() + "离开车位");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
semaphore.release();
}
}, String.valueOf(i)).start();
}
}
}
读写锁
读锁可以允许多个线程同时读,而写锁只允许一个线程写。
下面的例子模拟了一个缓存,这里也采用volatie关键字,用于保证map的数据的可见性。

public class ReadWriteLockDemo {
public static void main(String[] args){
MyCache myCache = new MyCache();
// 多线程写
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.put(temp + "", temp + "");
}, String.valueOf(i)).start();
}
// 多线程读
for (int i = 1; i <= 5; i++) {
final int temp = i;
new Thread(() -> {
myCache.get(temp + "");
}, String.valueOf(i)).start();
}
}
}
class MyCache {
private volatile Map<String, Object> map = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 一个线程写
*/
public void put(String key, Object value) {
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "写入");
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "写入成功!");
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
/**
* 多个线程读
*/
public void get(String key) {
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + "读取");
Object value = map.get(key);
System.out.println(Thread.currentThread().getName() + "读取成功:" + value);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
总结
本文章分享的主要是一些并发工具类的使用和介绍,希望大家喜欢!
参考资料
狂神的JUC视频(B站上有,讲的还可以! )
JUC并发常用工具学习的更多相关文章
- JUC : 并发编程工具类的使用
个人博客网:https://wushaopei.github.io/ (你想要这里多有) 一.JUC是什么 1.JUC定义 JUC,即java.util.concurrent 在并发编程中使用的 ...
- org.apache.commons等常用工具学习
StringUtils 1,StringUtils.isNotBlank isNotEmpty : 判断某字符串是否非空 StringUtils.isNotEmpty(null) = false St ...
- Java并发指南14:JUC中常用的Unsafe和Locksupport
本文转自网络,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutoria ...
- JUC并发编程学习笔记
JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...
- 深度学习框架PyTorch一书的学习-第五章-常用工具模块
https://github.com/chenyuntc/pytorch-book/blob/v1.0/chapter5-常用工具/chapter5.ipynb 希望大家直接到上面的网址去查看代码,下 ...
- 学习游戏服务器开发必看,C++游戏服务器开发常用工具介绍
C++游戏服务器开发常用工具介绍 在软件开发过程中需要使用的工具类型实属众多,从需求建模到软件测试,从代码编译到工程管理,这些工具都对项目有着不可替代的作用.庄子有云,"吾生也有涯,而知也无 ...
- 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟
1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...
- JUC 包下工具类,它的名字叫 LockSupport !你造么?
前言 LockSupport 是 JUC 中常用的一个工具类,主要作用是挂起和唤醒线程.在阅读 JUC 源码中经常看到,所以很有必要了解一下. 公众号:liuzhihangs ,记录工作学习中的技术. ...
- JUC并发编程与高性能内存队列disruptor实战-上
JUC并发实战 Synchonized与Lock 区别 Synchronized是Java的关键字,由JVM层面实现的,Lock是一个接口,有实现类,由JDK实现. Synchronized无法获取锁 ...
- 多线程JUC并发篇常见面试详解
@ 目录 1.JUC 简介 2.线程和进程 3.并非与并行 4.线程的状态 5.wait/sleep的区别 6.Lock 锁(重点) 1.Lock锁 2.公平非公平: 3.ReentrantLock ...
随机推荐
- 01 ansible的基本介绍
1.现有的企业服务器环境 在现在的企业中,特别是互联网公司,他们的业务量众多:比如负载均衡服务器.web服务器.动态解析(php)服务器.数据库(mysql)服务器以及网站缓存服务器,等等: 例如:一 ...
- centos安装k8s注意点
安装方法,参考 https://blog.csdn.net/frankgy01/article/details/127936367 https://www.cnblogs.com/yangzp/p/1 ...
- 树莓派利用摄像头实现web在线监控
1.https://shumeipai.nxez.com/2021/10/21/raspberry-pi-usb-camera-to-realize-remote-network-monitoring ...
- ubuntu手动创建快捷方式
新建document,重命名为XXX.desktop,打开文件 以sublime为例,填写 [Desktop Entry] Version=1.0 Type=Application Name=Subl ...
- autossh 使用
Table of Contents 1. centos7下配置为服务 2. 命令式使用 2.1. 映射远程主机防火墙之后的端口到本机 2.2. 映射本机端口到远程主机 centos7下配置为服务 编辑 ...
- token解决cookie的弊端
token解决cookie的弊端 目录 token解决cookie的弊端 cookie的弊端 token解决弊端一 什么是token和JWT JWT的构成 token工作流程 token解决弊端二 C ...
- 页面录制服务上线:RESTful API 调用实现,所见所录即所得
我们为很多实时互动场景提供了服务.在一些场景中,用户不仅需要实时互动,还需要把互动的过程录下来.那么一个好的录制解决方案究竟需要具备哪些特征呢? 在回答这个问题之前,先聊一下客户使用录制的原因.一般来 ...
- 深入理解 Python 虚拟机:集合(set)的实现原理及源码剖析
深入理解 Python 虚拟机:集合(set)的实现原理及源码剖析 在本篇文章当中主要给大家介绍在 cpython 虚拟机当中的集合 set 的实现原理(哈希表)以及对应的源代码分析. 数据结构介绍 ...
- ros系统(1)
在虚拟机上安装好ros系统之后,打开终端,启动ROS Master,输入roscore命令,结果如下: 再启动小海龟仿真器,输入命令:rosrun turtlesim turtlesim_node,结 ...
- python + BeautifulSoup + selenium 实现爬取中医智库的古籍分类的数据
爬取内容为 该图片下的七个分类, 然后对应的每个种类的书本信息(摘要和目录) 效果为 代码如下 import requests from bs4 import BeautifulSoup import ...