今天主要来和大家分享一下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锁的判断。

场景一

  1. 是输出发短信还是打电话?发短信

  2. 发短信延迟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");
}
}
场景二
  1. 下面先输出哪个结果?发短信还是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");
}
}
场景三
  1. 下面重新声明两个对象,一个调用同步发短信方法,一个调用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");
}
}
场景四
  1. 增加两个静态同步方法,只有一个对象,先打印,发短信?打电话?发短信

类一加载就有了,这里锁的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");
}
}
  1. 两个对象,是发短信还是打电话?发短信

    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();
}
}
  1. 一个对象,一个静态同步方法,一个普通同步方法,先发短信,还是先打电话?先打电话

sendMsg锁的是Class模板,call锁的是调用的对象

  1. 如果是两个对象,结果也是一样,结果还是打电话
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并发常用工具学习的更多相关文章

  1. JUC : 并发编程工具类的使用

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 一.JUC是什么 1.JUC定义 JUC,即java.util.concurrent 在并发编程中使用的 ...

  2. org.apache.commons等常用工具学习

    StringUtils 1,StringUtils.isNotBlank isNotEmpty : 判断某字符串是否非空 StringUtils.isNotEmpty(null) = false St ...

  3. Java并发指南14:JUC中常用的Unsafe和Locksupport

    本文转自网络,侵删 本系列文章将整理到我在GitHub上的<Java面试指南>仓库,更多精彩内容请到我的仓库里查看 https://github.com/h2pl/Java-Tutoria ...

  4. JUC并发编程学习笔记

    JUC并发编程学习笔记 狂神JUC并发编程 总的来说还可以,学到一些新知识,但很多是学过的了,深入的部分不多. 线程与进程 进程:一个程序,程序的集合,比如一个音乐播发器,QQ程序等.一个进程往往包含 ...

  5. 深度学习框架PyTorch一书的学习-第五章-常用工具模块

    https://github.com/chenyuntc/pytorch-book/blob/v1.0/chapter5-常用工具/chapter5.ipynb 希望大家直接到上面的网址去查看代码,下 ...

  6. 学习游戏服务器开发必看,C++游戏服务器开发常用工具介绍

    C++游戏服务器开发常用工具介绍 在软件开发过程中需要使用的工具类型实属众多,从需求建模到软件测试,从代码编译到工程管理,这些工具都对项目有着不可替代的作用.庄子有云,"吾生也有涯,而知也无 ...

  7. 多线程进阶——JUC并发编程之CountDownLatch源码一探究竟

    1.学习切入点 JDK的并发包中提供了几个非常有用的并发工具类. CountDownLatch. CyclicBarrier和 Semaphore工具类提供了一种并发流程控制的手段.本文将介绍Coun ...

  8. JUC 包下工具类,它的名字叫 LockSupport !你造么?

    前言 LockSupport 是 JUC 中常用的一个工具类,主要作用是挂起和唤醒线程.在阅读 JUC 源码中经常看到,所以很有必要了解一下. 公众号:liuzhihangs ,记录工作学习中的技术. ...

  9. JUC并发编程与高性能内存队列disruptor实战-上

    JUC并发实战 Synchonized与Lock 区别 Synchronized是Java的关键字,由JVM层面实现的,Lock是一个接口,有实现类,由JDK实现. Synchronized无法获取锁 ...

  10. 多线程JUC并发篇常见面试详解

    @ 目录 1.JUC 简介 2.线程和进程 3.并非与并行 4.线程的状态 5.wait/sleep的区别 6.Lock 锁(重点) 1.Lock锁 2.公平非公平: 3.ReentrantLock ...

随机推荐

  1. oracle数据库常用操作

    1,调整显示格式 col username for a20 col DEFAULT_TABLESPACE for a30 2,查看表空间 select username,default_tablesp ...

  2. 博弈论练习8 Northcott Game(取石子问题)

    题目链接在这里:I-Northcott Game_牛客竞赛博弈专题班组合游戏基本概念.对抗搜索.Bash游戏.Nim游戏习题 (nowcoder.com) 这题是一个伪装的很好的取石子问题,可以发现, ...

  3. pycharm配置gitlab

    一.安装Git 下载地址: https://git-scm.com/downloads 安装说明: https://git-scm.com/downloads 二.pycharm配置gitlab: 1 ...

  4. Java VSCode 基础教学

    VSCode 超全设置1.下载2.插件安装3.项目创建4.设置5.快捷键6.优化7.导出 Jar 包 VSCode 超全设置 VSCode(Visual Studio Code) 是一款 Micros ...

  5. MYSQL DUAL(伪表)

    #DUAL是一个伪表,不存在的表. SELECT 8*9 FROM DUAL #输出72

  6. Https交互原理

    Http超文本传输协议 基于tcp和Ip实现的一种可靠的传输协议,可靠的保证了客户端和服务器之间的传输不会丢失,但是却没办法保证传输数据的安全性. Https是Http的升级版本,用于解决Http数据 ...

  7. php 中 session存储

    转载网址: https://blog.csdn.net/miliu123456/article/details/107048378/ php 中 session 更换存储方式(file, redis, ...

  8. python内置函数map()

    map()函数 介绍 map()是python的一个内置函数,其作用是返回一个迭代器,该迭代器将function函数应用于可迭代对象的每个项,并产生结果. map函数的语法: map(function ...

  9. Windows10 穿越火线手感和Windows7不一样

    如果是穿越火线或者其他FPS玩家,应该会感觉Win10和WIin7两者手感会有一定的区别.为什么升级了系统变菜了?心理作用?其实确实和系统有关系哦.我从Windows7升级到Windows10玩穿越火 ...

  10. $\mathcal{Friends\,\,Of\,--Mathic}$