第二章 线程间通信、集合的线程安全

文章目录

  • 第二章 线程间通信、集合的线程安全
  • 一、线程间通信
    • 1.介绍
    • 2.synchronized 方案
    • 3.Lock 方案
    • 4.定制化线程通信
  • 二、集合的线程安全
    • 1.集合线程不安全演示
    • 2.Vector
    • 3.Collections
    • 4.CopyOnWriteList

一、线程间通信

1.介绍

多线程编程步骤:

  1. 创建资源类,在资源类创建属性和操作方法
  2. 操作方法分三步
    1. 判断
    2. 干活
    3. 通知
  3. 创建多个线程,调用资源类的操作方法
  4. 防止虚假唤醒问题

线程间通信的模型有两种:共享内存和消息传递,以下通过 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】第二章 线程间通信、集合的线程安全相关推荐

  1. java 线程间通信 handler_Handler不同线程间的通信

    转http://www.iteye.com/problems/69457 Activity启动后点击一个界面按钮后会开启一个服务(暂定为padService),在padService中会启动一个线程( ...

  2. linux高级编程基础系列:线程间通信

    线程间通信机制: 线程是一种轻量级的进程. 进程的通信机制主要包括无名管道.有名管道.消息队列.信号量.共享内存以及信号等.这些机制都是由linux内核来维护的,实现起来都比较复杂,而且占用大量的系统 ...

  3. Net线程间通信的异步机制

    线程间通信 我们看下面的图 图1 我们来看线程间通信的原理:线程(Thread B)和线程(Thread A)通信, 首先线程A 必须实现同步上下文对象(Synchronization Context ...

  4. 进程间通信和线程间通信

    进程间通信 转自  https://www.cnblogs.com/LUO77/p/5816326.html 线程间通信  https://www.cnblogs.com/jobs1/p/107840 ...

  5. 线程同步--线程间通信

    5月21日 线程同步--线程间通信 一.线程同步  线程的同步方法跟其他系统下类似,我们可以用原子操作,可以用 mutex,lock 等.  iOS 的原子操作函数是以 OSAtomic 开头的,比如 ...

  6. Java进阶知识 - 多线程与线程间通信

    CountdownLatch, CyclicBarrier 分别适合什么场景呢? 大部分情况下, 子线程只需要关心自身执行的任务. 但在某些复杂的情况下, 需要使用多个线程来协同完成某个任务, 这就涉 ...

  7. Day127.JUC:线程间通信(Conditon)、并发容器类(CopyOnWrite)、JUC强大辅助类、Callable

    . 目录 一.线程间通信 线程间通信改造成Lock版  Condition 定制化调用通信 Condition 二.并发容器类 (解决集合安全问题) CopyOnWrite 写时拷贝技术 三.JUC ...

  8. java线程间通信 实例_JAVA-初步认识-第十四章-线程间通信-示例

    一. 引言 之前讲述了线程的基本使用,卖票和存钱.卖票相当于把资源都释放出来,被别人获取到.而存钱,则是把数据都存进去. 现在,我们将线程进行了改变.以前是多个线程在执行同一个动作,无论是继承还是实现 ...

  9. 【JUC并发编程03】线程间通信

    文章目录 3 线程间通信 3.1 synchronized 实现案例 3.2 虚假唤醒问题 3.3 Lock 实现案例 3 线程间通信 线程间通信有两种实现方法: 关键字 synchronized 与 ...

最新文章

  1. js中!和!!的区别及用法
  2. 电脑仙人掌机器人作文_神奇的仙人掌作文400字
  3. 椭圆曲线加密算法ECC
  4. sigmoid函数的数值稳定性
  5. echo(),print(),print_r(),var_dump()的区别
  6. 热传导/物质扩散算法应用于推荐
  7. 通过jquer连接数据库里面的数据、LINQ简介
  8. 如何使用print()打印类的实例?
  9. Nginx笔记(一):安装
  10. 升级Windows 2003域的唯一DC
  11. Chrome浏览器语音自动播放功能
  12. 在 Coq 中形式化 100 个定理
  13. Oracle AutoVue 21.0.2.4 支持的文件格式
  14. 愚人节导入_在愚人节的恶作剧破坏之后,如何重置键盘的映射?
  15. 详解 GloVe 的原理和应用
  16. 按键精灵post请求_按键精灵安卓版能发送post和get请求吗
  17. android 隐私泄露 路径,一种Android应用隐私泄露漏洞检测方法与流程
  18. 20180415字节跳动今日头条笔试题——后台研发方向
  19. linux 如何重命名
  20. 深入理解布局约束 | 开发者说·DTalk

热门文章

  1. PyCharm 设置护眼背景色
  2. 简单的对象序列化协议(伪代码)
  3. python_str 字符串的所有方法
  4. undefined reference to libiconv_open'
  5. 笔记-Microsoft SQL Server 2008技术内幕:T-SQL语言基础-02 单表查询
  6. 多线程,死锁,DeadLock
  7. Gridview导出到EXCEL
  8. UVA 11198 Dancing Digits
  9. web程序设计(2)....开发流程?
  10. SPAW Editor .NET Edition v.2乱用:使用代码调整编辑器高度