线程安全
线程安全,一般指的是某个对象,变量在进行多线程操作的时候,得到的结果与期望的结果一致。当我们使用线程去访问一个对象时,由于对象内部的某些操作原因,而导致的线程的最终结果不符合期望,就是线程不安全,例如:ArrayList是一个线程不安全的容器,如果在多线程中使用ArrayList,程序有可能出现隐藏的错误。例如:
package com.dong.testThread;
import java.util.ArrayList;
/**
* thread1,thread2线程向ArrayList容器***添加200,0个容器,得到的结果应该是20000,但是执行结果却不是20000:
* 有三种结果:
* ⑴抛出异常:Thread-0" java.lang.ArrayIndexOutOfBoundsException:
* ArrayList在扩容的过程中,内部一致性被破坏,由于没有锁的保护,另外一个线程访问到了不一致的内部状态,导致出现越界问题
* ⑵不是期望的值,但也没报错
* 由于多线程访问冲突,是的保存容器大小的变量被多线程不正常访问,同时两个线程同时对ArrayList中的同一个位置进行赋值导致的
* ⑶正常结果: 20000
*
* @author liuD
*
*/
public class TestArrayList {
static ArrayList<Integer> arrayList = new ArrayList<Integer>(66);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new AddThread());
Thread thread2 = new Thread(new AddThread());
thread1.start();thread2.start();
thread1.join();thread2.join();
System.out.println(ar.size());
}
public static class AddThread implements Runnable{
public void run() {
for(int i = 0 ;i<10000;i++) {
arrayList.add(i);
}
}
}
}
ThreadLocal:线程的局部变量,即只有当前线程可以访问这个对象,因此每个线程都有自己的ThreadLocal变量,当线程中有线程不安全的对象,操作时,可使用ThreadLocal来修饰该实例,变量,让每个线程都拥有一个该实例,变量,这样就不会因为竞争同一个资源而导致的线程不安全。
ThreadLocal的实现原理:
ThreadLocal的set()方法和get()方法:
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t); //获取线程的ThreadLocalMap,可以理解为一个hashMap
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap(t) -----》
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
t.threadLocals-----》
ThreadLocal.ThreadLocalMap threadLocals = null;
map.set(this, value) -----》
private void set(ThreadLocal<?> key, Object value) { //key为ThreadLocal当前对象, value是需要的值
Entry[] tab = table; //Entry数组对象,用于存储map对象的数组
int len = tab.length;
int i = key.threadLocalHashCode & (len-1); //利用key的哈希和数组长度,来确定当前ThreadLocal在entry[]中的下标
for (Entry e = tab[i]; e != null;e = tab[i = nextIndex(i, len)]) {
ThreadLocal<?> k = e.get(); //根据哈希值获取Entry对象
if (k == key) { //从entry数组中获取下标为i(i为当前ThreadLocal的哈希值)的ThreadLocal,即当前的ThreadLocal,如果 k == 当前ThreadLocal;
e.value = value; //将对象的新值赋给当前ThreadLocal对象
return;
}
if (k == null) { //如果 == null ,则替换原来的对象;
replaceStaleEntry(key, value, i);
return;
}
}
tab[i] = new Entry(key, value);
int sz = ++size;
if (!cleanSomeSlots(i, sz) && sz >= threshold)
rehash();
}
createMap(t, value) -----》
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
get()方法比较简单:
public T get() {
Thread t = Thread.currentThread(); //获取当前线程
ThreadLocalMap map = getMap(t); //获取当前线程的ThreadLocalMap对象
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this); //如果不为null,则获取Entry对象
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value; //获取其ThreadLocal对应的值
return result;
}
}
return setInitialValue();
}
ThreadLocal变量都是在其内部,如果线程不终止,就不会被回收,所以如果想要回收,可以使用ThreadLocal.remove()方法将变量再其内部移除;
总结:ThreadLocal的实现原理是将实例存储在一个数组中,数组的元素是一个个entry对象,数组的下标是ThreadLocal对象哈希和数组长度的与运算,因此,可以确保每个线程的ThreadLocal变量都是独立的。
无锁
前面我们介绍了CAS比较交换技术,可以实现不用锁机制就能保证安全的并发,接下来介绍无锁的一些类:
AtomicInteger: package java.util.concurrent.atomic;
private volatile int value; //AtomicInteger的value值,被volatile修饰,线程之间对value的修改可知
public final int get() {//注意返回的是一个final值,证明返回值不可被修改,这也是防止多线程导致数据不一致的一种手段
return value;
}
public final void set(int newValue) { //同上
value = newValue;
}
public final int getAndSet(int newValue) {//设置新值返回旧值
return U.getAndSetInt(this, VALUE, newValue);
}
public final int getAndIncrement() {//让value值增1,因为++不是线程安全的,同时AtomicInteger不使用锁
return U.getAndAddInt(this, VALUE, 1);
}
//注意:在AtomicInteger中,有关的++,--,等赋值操作被方法替代,同时,value值为私有的,即只能使用公共方法获取
无锁的对象引用:AtomicReference
AtomicInteger是对整数的封装,AtomicReference是对普通对象引用,保证你在修改对象引用时的线程安全。
AtomicReference的一个缺点是:使用比较交换技术,线程来判断一个对象是否可以被修改,如果当前值和期望值一致,则线程可以写入新值,如果当前值和期望值不一致,则证明有其他线程修改了值,故不写入新值,但是如果其他线程修改多次,又恰好修改到当前值的期望值,这个时候,也符合写入新值的条件,但是对象是否修改我们无法界定,对于一般问题,这个无所谓,例如我们做运算,不影响,但是对于业务,我们需要提防。因为做运算我们关注的是结果,但是对于业务,比较次数也有可能考虑在内。
Java引入AtomicStampedReference,即带时间戳的对象引用,即不仅更新值,还得更新时间 戳,只有都满足,才会修改数据;
普通变量也可以原子操作:AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater分别可以对int,long,普通对象进行CAS修改,
什么时候使用: 当代码都完成差不多,其中存在普通变量导致的线程不安全操作,为了做到最少的修改,可以使用工具类AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater来实现原子操作。
package com.dong.testThread;
import java.util.concurrent.atomic.AtomicIntegerFieldUpdater;
/**
* 使用AtomicIntegerFieldUpdater 来说普通变量具有原子操作
*
* @author liuD
*/
public class TestAtomicIntegerFieldUpdater {
public static void main(String[] args) {
AtomicIntegerFieldUpdater<att> aifu =
AtomicIntegerFieldUpdater.newUpdater(att.class,"vote");
final att updateobj = new att();
int newvalue= aifu.incrementAndGet(updateobj);
System.out.println(newvalue);
}
}
class att{
volatile int vote = 1 ; //注意这里不可以是static修饰;
}
其他操作同理
最后:内容来自《Java高并发程序设计》 作者葛一鸣 郭超 由衷感谢作者为我们提供书籍内容;