Java并发:五种线程安全类型、线程安全的实现、枚举类型
1. Java中的线程安全
- Java线程安全:狭义地认为是多线程之间共享数据的访问。
- Java语言中各种操作共享的数据有5种类型:不可变、绝对线程安全、相对线程安全、线程兼容、线程独立
① 不可变
- 不可变(Immutable) 的对象一定是线程安全的,不需要再采取任何的线程安全保障措施。
- 只要能正确构建一个不可变对象,该对象永远不会在多个线程之间出现不一致的状态。
- 多线程环境下,应当尽量使对象成为不可变,来满足线程安全。
实现不可变=========》
- 如果共享数据是基本数据类型,使用final关键字对其进行修饰,就可以保证它是不可变的。
- 如果共享数据是一个对象,要保证对象的行为不会对其状态产生任何影响。
- String是不可变的,对其进行substring()、replace()、concat()等操作,返回的是新的String对象,原始的String对象的值不受影响。而如果对StringBuffer或者StringBuilder对象进行substring()、replace()、append()等操作,直接对原对象的值进行改变。
- 要构建不可变对象,需要将内部状态变量定义为final类型。如
java.lang.Integer
类中将value定义为final类型。=====》private final int value;
常见的不可变的类型:
- final关键字修饰的基本数据类型
- 枚举类型、String类型
- 常见的包装类型:Short、Integer、Long、Float、Double、Byte、Character等
- 大数据类型:BigInteger、BigDecimal
对于集合类型,可以使用 Collections.unmodifiableXXX()
方法来获取一个不可变的集合。
- 通过
Collections.unmodifiableMap(map)
获的一个不可变的Map类型。 Collections.unmodifiableXXX()
先对原始的集合进行拷贝,需要对集合进行修改的方法都直接抛出异常。
例如,如果获得的不可变map对象进行put()、remove()、clear()操作,则会抛出UnsupportedOperationException异常
② 绝对线程安全
绝对线程安全的实现,通常需要付出很大的、甚至不切实际的代价。
Java API中提供的线程安全,大多数都不是绝对线程安全。
例如,对于数组集合Vector的操作,如get()、add()、remove()都是有synchronized关键字修饰。有时调用时也需要手动添加同步手段,保证多线程的安全。
下面的代码看似不需要同步,实际运行过程中会报错。
import java.util.Vector;
/**
* @Author: lucy
* @Version 1.0
*/
public class VectorTest {
public static void main(String[] args) {
Vector<Integer> vector = new Vector<>();
while(true){
for (int i = 0; i < 10; i++) {
vector.add(i);
}
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < vector.size(); i++) {
System.out.println("获取vector的第" + i + "个元素: " + vector.get(i));
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i=0;i<vector.size();i++){
System.out.println("删除vector中的第" + i+"个元素");
vector.remove(i);
}
}
}).start();
while (Thread.activeCount()>20)
return;
}
}
}
出现ArrayIndexOutOfBoundsException
异常,原因:某个线程恰好删除了元素i,使得当前线程无法访问元素i。
Exception in thread "Thread-1109" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 1
at java.util.Vector.remove(Vector.java:831)
at VectorTest$2.run(VectorTest.java:28)
at java.lang.Thread.run(Thread.java:745)
需要将对元素的get和remove构造成同步代码块:
synchronized (vector){
for (int i = 0; i < vector.size(); i++) {
System.out.println("获取vector的第" + i + "个元素: " + vector.get(i));
}
}
synchronized (vector){
for (int i=0;i<vector.size();i++){
System.out.println("删除vector中的第" + i+"个元素");
vector.remove(i);
}
}
③ 相对线程安全
- 相对线程安全需要保证对该对象的单个操作是线程安全的,在必要的时候可以使用同步措施实现线程安全。
- 大部分的线程安全类都属于相对线程安全,如Java容器中的Vector、HashTable、通过
Collections.synchronizedXXX()
方法包装的集合。
④ 线程兼容
- Java中大部分的类都是线程兼容的,通过添加同步措施,可以保证在多线程环境中安全使用这些类的对象。
- 如常见的ArrayList、HashTableMap都是线程兼容的。
⑤ 线程对立
- 线程对立是指:无法通过添加同步措施,实现多线程中的安全使用。
- 线程对立的常见操作有:Thread类的suspend()和resume()(已经被JDK声明废除),
System.setIn()
和System.setOut()
等。
2. Java的枚举类型
通过enum关键字修饰的数据类型,叫枚举类型。
- 枚举类型的每个元素都有自己的序号,通常从0开始编号。
- 可以通过values()方法遍历枚举类型,通过name()或者toString()获取枚举类型的名称
- 通过ordinal()方法获取枚举类型中元素的序号
public class EnumData {
public static void main(String[] args) {
for (Family family : Family.values()) {
System.out.println(family.name() + ":" + family.ordinal());
}
}
}
enum Family {
GRADMOTHER, GRANDFATHER, MOTHER, FATHER, DAUGHTER, SON;
}
3. Java线程安全的实现
① 互斥同步
互斥同步(Mutex Exclusion & Synchronization)是一种常见的并发正确性保障手段。
- 同步:多个线程并发访问共享数据,保证共享数据同一时刻只被一个(或者一些,使用信号量)线程使用。
- 互斥:互斥是实现同步的一种手段,主要的互斥实现方式:临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)。
同步与互斥的关系:
- 互斥是原因,同步是结果。
- 同步是目的,互斥是方法。
Java中,最基本的实现互斥同步的手段是synchronized关键字,其次是JUC包中的ReentrantLock。
Java并发:五种线程安全类型、线程安全的实现、枚举类型的更多相关文章
- Java并发(二十一):线程池实现原理
一.总览 线程池类ThreadPoolExecutor的相关类需要先了解: (图片来自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8% ...
- java并发编程笔记(七)——线程池
java并发编程笔记(七)--线程池 new Thread弊端 每次new Thread新建对象,性能差 线程缺乏统一管理,可能无限制的新建线程,相互竞争,有可能占用过多系统资源导致死机或者OOM 缺 ...
- java并发编程笔记(三)——线程安全性
java并发编程笔记(三)--线程安全性 线程安全性: 当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些进程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现 ...
- Java并发编程之set集合的线程安全类你知道吗
Java并发编程之-set集合的线程安全类 Java中set集合怎么保证线程安全,这种方式你知道吗? 在Java中set集合是 本篇是<凯哥(凯哥Java:kagejava)并发编程学习> ...
- Java并发编程(您不知道的线程池操作)
Java并发编程(您不知道的线程池操作) 这几篇博客,一直在谈线程,设想一下这个场景,如果并发的线程很多,然而每个线程如果执行的时间很多的话,这样的话,就会大量的降低系统的效率.这时候就可以采用线程池 ...
- 【Java并发编程】之二:线程中断
[Java并发编程]之二:线程中断 使用interrupt()中断线程 当一个线程运行时,另一个线程可以调用对应的Thread对象的interrupt()方法来中断它,该方法只是在目标线程中设置一 ...
- 【Java 并发】Executor框架机制与线程池配置使用
[Java 并发]Executor框架机制与线程池配置使用 一,Executor框架Executor框架便是Java 5中引入的,其内部使用了线程池机制,在java.util.cocurrent 包下 ...
- Java并发编程(您不知道的线程池操作), 最受欢迎的 8 位 Java 大师,Java并发包中的同步队列SynchronousQueue实现原理
Java_并发编程培训 java并发程序设计教程 JUC Exchanger 一.概述 Exchanger 可以在对中对元素进行配对和交换的线程的同步点.每个线程将条目上的某个方法呈现给 exchan ...
- java 并发(五)---AbstractQueuedSynchronizer
文章部分图片和代码来自参考文章. LockSupport 和 CLH 和 ConditionObject 阅读源码首先看一下注解 ,知道了大概的意思后,再进行分析.注释一开始就进行了概括.AQS的实现 ...
- java的五种数据类型解析
不知道大家对java的简单数据类型是否了解,下面针对Java的五种类型简单数据类型表示数字和字符,进行详细的讲解和分析. 一.简单数据类型初始化 在Java语言中,简单数据类型作为类的成员变量声明时自 ...
随机推荐
- 大数据学习day35----flume01-------1 agent(关于agent的一些问题),2 event,3 有关agent和event的一些问题,4 transaction(事务控制机制),5 flume安装 6.Flume入门案例
具体见文档,以下只是简单笔记(内容不全) 1.agent Flume中最核心的角色是agent,flume采集系统就是由一个个agent连接起来所形成的一个或简单或复杂的数据传输通道.对于每一个Age ...
- 零基础学习java------day17------缓冲字节流,转换字节流,简化流,缓冲字符流,序列化和对象流
1. 缓冲字节流 缓冲区:缓冲区实质上是一个数组.通常它是一个字节数组,但是也可以使用其他种类的数组.但是一个缓冲区不 仅仅 是一个数组.缓冲区提供了对数据的结构化访问,而且还可以跟踪系统的读/写进程 ...
- 【Reverse】DLL注入
DLL注入就是将dll粘贴到指定的进程空间中,通过dll状态触发目标事件 DLL注入的大概流程 https://uploader.shimo.im/f/CXFwwkEH6FPM0rtT.png!thu ...
- 【STM32】使用SDIO进行SD卡读写,包含文件管理FatFs(六)-FatFs使用的思路介绍
[STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(一)-初步认识SD卡 [STM32]使用SDIO进行SD卡读写,包含文件管理FatFs(二)-了解SD总线,命令的相关介绍 [STM3 ...
- Fragment放置后台很久(Home键退出很长时间),返回时出现Fragment重叠解决方案
后来在google查到相关资料,原因是:当Fragment长久不使用,系统进行回收,FragmentActivity调用onSaveInstanceState保存Fragment对象.很长时间后,再次 ...
- 找出1小时内占用cpu最多的10个进程的shell脚本
cpu时间是一项重要的资源,有时,我们需要跟踪某个时间内占用cpu周期最多的进程.在普通的桌面系统或膝上系统中,cpu处于高负荷状态也许不会引发什么问题.但对于需要处理大量请求的服务器来讲,cpu是极 ...
- ES6——>let,箭头函数,this指向小记
let let允许你声明一个作用域被限制在块级中的变量.语句或者表达式. 还是那个经典的问题:创建5个li,点击不同的li能够打印出当前li的序号. 如果在for循环中使用**var**来声明变量i的 ...
- Sentinel之流控规则
在上文Sentinel流量防卫兵中讲到了Sentinel入门以及流控规则一小部分,而Sentinel还有以下规则: 熔断降级规则 热点参数规则 系统规则 黑白名单规则 本文要讲的是流控规则 流量控制规 ...
- Innodb Cluster集群部署配置
目录 一.简介 二.环境声明 三.部署 安装(均操作) 配置(均操作) 开启group_replication(均操作) 启动group_replication 创建集群(在mysql-1执行) 创建 ...
- Linux进程操作
查看进程启动时间 ps -eo pid,lstart | grep PID 查看进程的运行多久 ps -eo pid,etime |grep PID 查看进程中启动了哪些线程 top -H -p pi ...