【JUC】第二章 线程间通信、集合的线程安全
第二章 线程间通信、集合的线程安全
文章目录
- 第二章 线程间通信、集合的线程安全
- 一、线程间通信
- 1.介绍
- 2.synchronized 方案
- 3.Lock 方案
- 4.定制化线程通信
- 二、集合的线程安全
- 1.集合线程不安全演示
- 2.Vector
- 3.Collections
- 4.CopyOnWriteList
一、线程间通信
1.介绍
多线程编程步骤:
- 创建资源类,在资源类创建属性和操作方法
- 操作方法分三步
- 判断
- 干活
- 通知
- 创建多个线程,调用资源类的操作方法
- 防止虚假唤醒问题
线程间通信的模型有两种:共享内存和消息传递,以下通过 synchronized 和 Lock 分别实现
场景:两个线程,一个线程对当前数值加 1,另一个线程对当前数值减 1
2.synchronized 方案
package com.sisyphus.sync;/*** @Description: synchronized 方案* @Param: $* @return: $* @Author: Sisyphus* @Date: $*/
public class ThreadDemo1 {//第三步,创建多个线程,调用资源类的操作方法public static void main(String[] args) {Share share = new Share();//创建线程new Thread(()->{for (int i = 1; i <= 10; i++) {try {share.incr();} catch (InterruptedException e) {e.printStackTrace();}}},"AA").start();new Thread(()->{for (int i = 1; i <= 10; i++) {try {share.decr();} catch (InterruptedException e) {e.printStackTrace();}}},"BB").start();new Thread(()->{for (int i = 1; i <= 10; i++) {try {share.incr();} catch (InterruptedException e) {e.printStackTrace();}}},"CC").start();new Thread(()->{for (int i = 1; i <= 10; i++) {try {share.decr();} catch (InterruptedException e) {e.printStackTrace();}}},"DD").start();}
}//第一步 创建资源类,定义属性和操作方法
class Share{//初始值private int number = 0;//+1 的方法public synchronized void incr() throws InterruptedException {//第二步 判断 干活 通知while(number != 0){ //判断 number 值是否为 0,如果不是,等待this.wait();}//如果 number 值是 0,就 +1 操作number++;System.out.println(Thread.currentThread().getName()+ " :: " + number);//通知其他线程this.notifyAll();}//-1 的方法public synchronized void decr() throws InterruptedException {//判断while(number != 1){this.wait();}//干活number--;System.out.println(Thread.currentThread().getName()+ " :: " + number);//通知其他线程this.notifyAll();}
}
3.Lock 方案
package com.sisyphus.lock;import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @Description: Lock 方案* @Param: $* @return: $* @Author: Sisyphus* @Date: $*/
public class ThreadDemo2 {public static void main(String[] args) {Share share = new Share();new Thread(()->{for(int i = 1; i <= 10; i++ ){try {share.incr();} catch (InterruptedException e) {e.printStackTrace();}}},"AA").start();new Thread(()->{for(int i = 1; i <= 10; i++ ){try {share.decr();} catch (InterruptedException e) {e.printStackTrace();}}},"BB").start();new Thread(()->{for(int i = 1; i <= 10; i++ ){try {share.incr();} catch (InterruptedException e) {e.printStackTrace();}}},"CC").start();new Thread(()->{for(int i = 1; i <= 10; i++ ){try {share.decr();} catch (InterruptedException e) {e.printStackTrace();}}},"DD").start();}
}//第一步 创建资源类,定义属性和操作方法
class Share{private int number = 0;//创建 Lockprivate final Lock lock = new ReentrantLock();private final Condition condition = lock.newCondition();//+1public void incr() throws InterruptedException {//上锁lock.lock();try{//判断while(number != 0){condition.await();}//干活number++;System.out.println(Thread.currentThread().getName()+ " :: " + number);//通知condition.signalAll();}finally {//解锁lock.unlock();}}//-1public void decr() throws InterruptedException {//上锁lock.lock();try{//判断while(number != 1){condition.await();}//干活number--;System.out.println(Thread.currentThread().getName()+ " :: " + number);//通知condition.signalAll();}finally {//解锁lock.unlock();}}
}
4.定制化线程通信
场景:启动三个线程,按照如下要求:
- AA 打印 5 次,BB 打印 10 次,CC 打印 15 次
- 总共进行10轮
package com.sisyphus.lock;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.*;/*** @Description: $* @Param: $* @return: $* @Author: Sisyphus* @Date: $*/
public class ThreadDemo3 {public static void main(String[] args) {ShareResource shareResource = new ShareResource();new Thread(()->{for (int i = 1; i <= 10; i++) {try {shareResource.print5(i);} catch (InterruptedException e) {e.printStackTrace();}}},"AA").start();new Thread(()->{for (int i = 1; i <= 10; i++) {try {shareResource.print10(i);} catch (InterruptedException e) {e.printStackTrace();}}},"BB").start();new Thread(()->{for (int i = 1; i <= 10; i++) {try {shareResource.print15(i);} catch (InterruptedException e) {e.printStackTrace();}}},"CC").start();}}//第一步 创建资源类
class ShareResource{//定义标志位private int flag = 1; // 1 AA 2 BB 3 CC//创建 Lock 锁private final Lock lock = new ReentrantLock();//创建三个 conditionprivate final Condition c1 = lock.newCondition();private final Condition c2 = lock.newCondition();private final Condition c3 = lock.newCondition();//打印 5 次,参数第几轮public void print5(int loop) throws InterruptedException {try{//上锁lock.lock();//判断while(flag != 1){c1.await();}//干活for (int i = 1; i <= 5; i++) {System.out.println(Thread.currentThread().getName() + " :: " + i + "轮数:" + loop);}//通知flag = 2; //修改标志位 2c2.signal();//通知 BB 线程}finally {//释放锁lock.unlock();}}//打印 10 次,参数第几轮public void print10(int loop) throws InterruptedException {try{//上锁lock.lock();//判断while(flag != 2){c2.await();}//干活for (int i = 1; i <= 10; i++) {System.out.println(Thread.currentThread().getName() + " :: " + i + "轮数:" + loop);}//通知flag = 3; //修改标志位 3c3.signal();//通知 CC 线程}finally {//释放锁lock.unlock();}}//打印 15 次,参数第几轮public void print15(int loop) throws InterruptedException {try{//上锁lock.lock();//判断while(flag != 3){c3.await();}//干活for (int i = 1; i <= 15; i++) {System.out.println(Thread.currentThread().getName() + " :: " + i + "轮数:" + loop);}//通知flag = 1; //修改标志位 1c1.signal();//通知 AA 线程}finally {//释放锁lock.unlock();}}
}
二、集合的线程安全
1.集合线程不安全演示
package com.sisyphus.lock;import java.util.ArrayList;
import java.util.List;
import java.util.UUID;/*** @Description: list 集合线程不安全* @Param: $* @return: $* @Author: Sisyphus* @Date: $*/
public class ThreadDemo4 {public static void main(String[] args) {//创建 ArrayList 集合List<String> list = new ArrayList<>();for (int i = 0; i < 100; i++) {new Thread(()->{//向集合内添加内容list.add(UUID.randomUUID().toString().substring(0,8));//向集合内获取内容System.out.println(list);},String.valueOf(i)).start();}}
}
异常内容:
java.util.ConcurrentModificationException
查看 ArrayList 的 add 源码
我们发现 add 方法并没有用 synchronized 修饰
我们可以通过三种方式来解决这个问题:
- Vector
- Collections
- CopyOnWriteArrayList
2.Vector
我们可以看到,Vector 的 add 方法使用了 synchronized 修饰,因此不会出现问题
代码很简单,只需要把 ArrayList 换成 Vector 即可,这里不再进行演示
需要注意,这种方式已经几乎不再使用了,因为它的性能太低了
3.Collections
Collections 提供了方法 synchronizedList 保证 List 是同步线程安全的
查看源码
和 Vector 一样,Collections 也几乎不再使用
4.CopyOnWriteList
CopyOnWriteList 相当于线程安全的 ArrayList,和 ArrayList 一样,它是个可变数组。但是和 ArrayList 不同的是,它具有以下特性:
- 它最适合具有以下特征的应用程序:List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突
- 它是线程安全的
- 可变操作(add、set、remove)的开销很大,因为需要复制整个数组
- 迭代器支持 hasNext()、next() 等不可变操作,但不支持可变操作
- 使用迭代器进行遍历的速度很快,并且不会与其它线程发生冲突,在构造迭代器时,迭代器依赖于不变的数组快照
CopyOnWrite 没有采用独占锁,而是采用读写分离的思想解决线程安全问题。写线程获取锁的时候,其他的线程会阻塞,它的核心是复制思想:
- 当我们往一个容器添加元素的时候,不是直接往当前容器添加,而是先将当前容器进行复制,复制出一个新的容器,然后往新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器
动态数组机制:
- CopyOnWriteList 内部有一个 volatile 数组来保存数据。在添加、修改或删除数据时会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给 volatile 数组。由于它在添加、修改或删除数据时都会新建数组,所以涉及到这些操作时,它的效率很低,如果只是进行遍历的话,效率比较高
线程安全机制通过 volatile 和互斥锁实现:
- 通过 volatile 数组保存数据,一个线程读取 volatile 时,总能看到其它线程对该 volatile 数组最后的写入,因此读到的数据总是最新的
- 通过互斥锁来保护数据,在添加、修改或删除数据时获取互斥锁。在添加、修改或删除之后,先将数据更新到 volatile 数组中,然后再释放互斥锁,达到保护数据的目的
HashSet 和 HashMap 的线程安全问题与 ArrayList 类似,也可以通过 CopyOnWriteArraySet 解决
【JUC】第二章 线程间通信、集合的线程安全相关推荐
- java 线程间通信 handler_Handler不同线程间的通信
转http://www.iteye.com/problems/69457 Activity启动后点击一个界面按钮后会开启一个服务(暂定为padService),在padService中会启动一个线程( ...
- linux高级编程基础系列:线程间通信
线程间通信机制: 线程是一种轻量级的进程. 进程的通信机制主要包括无名管道.有名管道.消息队列.信号量.共享内存以及信号等.这些机制都是由linux内核来维护的,实现起来都比较复杂,而且占用大量的系统 ...
- Net线程间通信的异步机制
线程间通信 我们看下面的图 图1 我们来看线程间通信的原理:线程(Thread B)和线程(Thread A)通信, 首先线程A 必须实现同步上下文对象(Synchronization Context ...
- 进程间通信和线程间通信
进程间通信 转自 https://www.cnblogs.com/LUO77/p/5816326.html 线程间通信 https://www.cnblogs.com/jobs1/p/107840 ...
- 线程同步--线程间通信
5月21日 线程同步--线程间通信 一.线程同步 线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用 mutex,lock 等. iOS 的原子操作函数是以 OSAtomic 开头的,比如 ...
- Java进阶知识 - 多线程与线程间通信
CountdownLatch, CyclicBarrier 分别适合什么场景呢? 大部分情况下, 子线程只需要关心自身执行的任务. 但在某些复杂的情况下, 需要使用多个线程来协同完成某个任务, 这就涉 ...
- Day127.JUC:线程间通信(Conditon)、并发容器类(CopyOnWrite)、JUC强大辅助类、Callable
. 目录 一.线程间通信 线程间通信改造成Lock版 Condition 定制化调用通信 Condition 二.并发容器类 (解决集合安全问题) CopyOnWrite 写时拷贝技术 三.JUC ...
- java线程间通信 实例_JAVA-初步认识-第十四章-线程间通信-示例
一. 引言 之前讲述了线程的基本使用,卖票和存钱.卖票相当于把资源都释放出来,被别人获取到.而存钱,则是把数据都存进去. 现在,我们将线程进行了改变.以前是多个线程在执行同一个动作,无论是继承还是实现 ...
- 【JUC并发编程03】线程间通信
文章目录 3 线程间通信 3.1 synchronized 实现案例 3.2 虚假唤醒问题 3.3 Lock 实现案例 3 线程间通信 线程间通信有两种实现方法: 关键字 synchronized 与 ...
最新文章
- js中!和!!的区别及用法
- 电脑仙人掌机器人作文_神奇的仙人掌作文400字
- 椭圆曲线加密算法ECC
- sigmoid函数的数值稳定性
- echo(),print(),print_r(),var_dump()的区别
- 热传导/物质扩散算法应用于推荐
- 通过jquer连接数据库里面的数据、LINQ简介
- 如何使用print()打印类的实例?
- Nginx笔记(一):安装
- 升级Windows 2003域的唯一DC
- Chrome浏览器语音自动播放功能
- 在 Coq 中形式化 100 个定理
- Oracle AutoVue 21.0.2.4 支持的文件格式
- 愚人节导入_在愚人节的恶作剧破坏之后,如何重置键盘的映射?
- 详解 GloVe 的原理和应用
- 按键精灵post请求_按键精灵安卓版能发送post和get请求吗
- android 隐私泄露 路径,一种Android应用隐私泄露漏洞检测方法与流程
- 20180415字节跳动今日头条笔试题——后台研发方向
- linux 如何重命名
- 深入理解布局约束 | 开发者说·DTalk
热门文章
- PyCharm 设置护眼背景色
- 简单的对象序列化协议(伪代码)
- python_str 字符串的所有方法
- undefined reference to libiconv_open'
- 笔记-Microsoft SQL Server 2008技术内幕:T-SQL语言基础-02 单表查询
- 多线程,死锁,DeadLock
- Gridview导出到EXCEL
- UVA 11198 Dancing Digits
- web程序设计(2)....开发流程?
- SPAW Editor .NET Edition v.2乱用:使用代码调整编辑器高度