对java多线程的线程安全性的一些总结
2016-12-16 09:13
190 查看
一直以来对java的线程安全都有点模糊,知道最近写程序时发现在处理大数据的时候,基本上都不可能离开多线程,而线程安全的忽略对数据的准确性产生比较严重的影响,在这里,对JAVA的线程安全做初步总结,以避免以后的代码中出现线程安全问题。
线程安全主要出现在两个方面:
1. 多线程,同时使用公共变量时,出现线程私自拷贝公共变量,导致公共变量出现不唯一的情况,线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。对这种情况,需要在代码中对变量进行volatile关键字的修饰,这样,java的虚拟机将拒接线程私自拷贝公共变量的情况,使该变量全局唯一,避免出现线程安全。Java提供的AtomicInteger,
AtomicLong和AtomicBoolean就是对这方面的应用。AtomicInteger等类型实际上是对int等基础类型进行了封装,并使用了volatile对数据变量进行了修饰。查看源码可见:
AtomicInteger里面定义了一个私有成员:private volatile int value;
2. 多线程时,每个线程对公共资源的使用存在多个步骤,而每个线程对这多个步骤的执行不具有原子性,容易被其他线程中途打断,造成数据的结果和预想的结果出现不一致的情形。比如ArrayList的增加元素的方法的源码实现:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
方法中,添加元素的分成了三个步骤(1判断是否需要扩充容器容量ensureCapacityInternal(size
+ 1);2. 计数器自增size++ 3.对元素对应的位置进行赋值。),这种情况在多线程的情况下容易出现被其他线程打断,出现a线程执行add到size++后被b线程打断,b线程在a线程执行自增的基础上继续执行自增并设置值后a线程得到cpu后执行赋值语句,覆盖了b线程设置的值。而java提供的线程安全的顺序容器Vector的添加元素方法的源码如下:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Synchronized修饰了add方法,进行了加锁,使得多线程同时调用add时进行了排他性,等待已经获得锁的线程执行该方法完毕后才能进去执行该方法里面的代码。
常用的线程安全容器
Vector ConcurrentHashMap Collections.synchronizedSet Collections.synchronizedList Collections.synchronizedMap BufferedWriter等
如果不确定该容器是否是线程安全,可以直接查看源码,看操作方法里面的代码是否用到synchronized进行修饰。总而言之,Synchronized用来保证多语句执行的原子性。另外还有Lock也能用来保证多语句的原子性,其实原理都是用锁机制保障资源使用在同一时间的使用者只有一个的特性。一般使用Synchronized就能满足需求。
--回头看看之前写的代码,很多时候没有考虑线程的安全性,比如经常使用简单的int,long 基础类型和List,hashmap,hashset等容器在多线程程序中而不自知。实在汗颜。
线程安全主要出现在两个方面:
1. 多线程,同时使用公共变量时,出现线程私自拷贝公共变量,导致公共变量出现不唯一的情况,线程为了提高效率,将某成员变量(如A)拷贝了一份(如B),线程中对A的访问其实访问的是B。只在某些动作时才进行A和B的同步。因此存在A和B不一致的情况。对这种情况,需要在代码中对变量进行volatile关键字的修饰,这样,java的虚拟机将拒接线程私自拷贝公共变量的情况,使该变量全局唯一,避免出现线程安全。Java提供的AtomicInteger,
AtomicLong和AtomicBoolean就是对这方面的应用。AtomicInteger等类型实际上是对int等基础类型进行了封装,并使用了volatile对数据变量进行了修饰。查看源码可见:
AtomicInteger里面定义了一个私有成员:private volatile int value;
2. 多线程时,每个线程对公共资源的使用存在多个步骤,而每个线程对这多个步骤的执行不具有原子性,容易被其他线程中途打断,造成数据的结果和预想的结果出现不一致的情形。比如ArrayList的增加元素的方法的源码实现:
public boolean add(E e) {
ensureCapacityInternal(size + 1); // Increments modCount!!
elementData[size++] = e;
return true;
}
方法中,添加元素的分成了三个步骤(1判断是否需要扩充容器容量ensureCapacityInternal(size
+ 1);2. 计数器自增size++ 3.对元素对应的位置进行赋值。),这种情况在多线程的情况下容易出现被其他线程打断,出现a线程执行add到size++后被b线程打断,b线程在a线程执行自增的基础上继续执行自增并设置值后a线程得到cpu后执行赋值语句,覆盖了b线程设置的值。而java提供的线程安全的顺序容器Vector的添加元素方法的源码如下:
public synchronized boolean add(E e) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = e;
return true;
}
Synchronized修饰了add方法,进行了加锁,使得多线程同时调用add时进行了排他性,等待已经获得锁的线程执行该方法完毕后才能进去执行该方法里面的代码。
常用的线程安全容器
Vector ConcurrentHashMap Collections.synchronizedSet Collections.synchronizedList Collections.synchronizedMap BufferedWriter等
如果不确定该容器是否是线程安全,可以直接查看源码,看操作方法里面的代码是否用到synchronized进行修饰。总而言之,Synchronized用来保证多语句执行的原子性。另外还有Lock也能用来保证多语句的原子性,其实原理都是用锁机制保障资源使用在同一时间的使用者只有一个的特性。一般使用Synchronized就能满足需求。
--回头看看之前写的代码,很多时候没有考虑线程的安全性,比如经常使用简单的int,long 基础类型和List,hashmap,hashset等容器在多线程程序中而不自知。实在汗颜。
相关文章推荐
- Java多线程编程总结笔记——六线程的同步与锁
- Java【多线程知识总结(1)】用Thread类创建线程
- java多线程总结一:线程的两种创建方式及优劣比较
- java多线程总结二:后台线程(守护线程)
- java多线程总结一: 线程的两种创建方式及优劣比较
- JAVA 多线程 及线程的 一些理解
- Java【多线程知识总结(8)】线程通信,wait()与notify()的运用
- java多线程总结二:后台线程(守护线程)
- Java【多线程知识总结(10)】线程通信之wait()与notify()的运用--模拟指挥官指挥2个连队交替轰炸战区
- java多线程总结一:线程的两种创建方式及优劣比较
- Java【多线程知识总结(2)】调用setDaemon(true)变成后台线程
- Java【多线程知识总结(5)】比较继承Thread类创建线程和实现Runnable接口创建线程这两种方式
- Java【多线程知识总结(3)】调用join()合并线程
- java多线程总结一: 线程的两种创建方式及优劣比较
- Java多线程编程总结笔记——一多线程基础知识
- Java【多线程知识总结(4)】通过实现Runnable接口创建线程
- Java【多线程知识总结(2)】调用setDaemon(true)变成后台线程
- Java【多线程知识总结(5)】比较继承Thread类创建线程和实现Runnable接口创建线程这两种方式
- java多线程总结一:线程的两种创建方式及优劣比较
- java多线程总结一: 线程的两种创建方式及优劣比较