ThreadLocal和无锁

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/ld3205/article/details/83542123

线程安全

线程安全,一般指的是某个对象,变量在进行多线程操作的时候,得到的结果与期望的结果一致。当我们使用线程去访问一个对象时,由于对象内部的某些操作原因,而导致的线程的最终结果不符合期望,就是线程不安全,例如: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的一个缺点是:使用比较交换技术,线程来判断一个对象是否可以被修改,如果当前值和期望值一致,则线程可以写入新值,如果当前值和期望值不一致,则证明有其他线程修改了值,故不写入新值,但是如果其他线程修改多次,又恰好修改到当前值的期望值,这个时候,也符合写入新值的条件,但是对象是否修改我们无法界定,对于一般问题,这个无所谓,例如我们做运算,不影响,但是对于业务,我们需要提防。因为做运算我们关注的是结果,但是对于业务,比较次数也有可能考虑在内。

扫描二维码关注公众号,回复: 3932193 查看本文章

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高并发程序设计》   作者葛一鸣 郭超  由衷感谢作者为我们提供书籍内容;

 

猜你喜欢

转载自blog.csdn.net/ld3205/article/details/83542123
今日推荐