前言
本篇主要讲解如何去优化锁机制
或者克服多线程因为锁可导致性能下降的问题ThreadLocal线程变量
有这样一个场景,前面是一大桶水,10个人去喝水,为了保证线程安全,我们要在杯子上加锁
导致大家轮着排队喝水,因为加了锁的杯子是同步的,只能有一个人拿着这个唯一的杯子喝水这样子大家都喝完一杯水需要很长的时间如果我们给每个人分发一个杯子呢?是不是每人喝到水的时间缩小到了十分之一多线程并发也是一个道理
在每个Thread中都有自己的数据存放空间(ThreadLocalMap)而ThreadLocal就是在当前线程的存放空间中存放数据下面这个例子,在每个线程中存放一个arraylist,而不是大家去公用一个arraylistpublic class ThreadLocalTest { public static ThreadLocalthreadLocal = new ThreadLocal (); public static ArrayList list = new ArrayList(); public static class Demo implements Runnable { private int i; public Demo(int i) { this.i = i; } @Override public void run() { list.add(i); threadLocal.set(list); System.out.println(threadLocal.get()); } } public static void main(String[] args) throws InterruptedException { ExecutorService es = Executors.newFixedThreadPool(5); for (int j = 0; j < 200; j++) { es.execute(new Demo(j)); } Thread.sleep(3000); System.out.println(list.size()); es.shutdown(); }}
在每个线程内部有一块存储区域叫做ThreadLocalMap
可以看到,ThreadLocal采用set,get存取值方式只有线程完全关闭时,在ThreadLocalMap中的数据才会被GC回收这时有一个值得考虑的问题
我们使用线程池来开发的时候,线程池中的线程并不会关闭,它只是处于空闲状态也就是说,我们如果把过大的数据存储在当前线程的ThreadLocalMap中,线程不断的调用,被空闲...最后会导致内存溢出解决方法是当不需要这些数据时使用ThreadLocal.remove()方法将变量给移除CAS操作
还有一种脱离锁的机制,那就是CAS
CAS带着三个变量,分别是:V更新变量:需要返回的变量E预期值:原来的值N新值,传进来的新变量只有当预期值和新值相等时,才会把V=N,如果不相等,说明该操作会让数据无法同步
根据上面的解释,大概就能知道CAS其实也是在保护数据的同步性当多个线程进行CAS操作时,可想只有一个线程能成功更新,之后其它线程的E和V会不地进行断比较
所以CAS的同步锁的实现是一样的CAS操作的并发包在Atomic包中,atomic实现了很多类型
不管是AtomicInteger还是AtomicReference,都有相同点,请观察它们的源码:private volatile V value;private static final long valueOffset;
以上是AtomicReferenc
private volatile int value;private static final long valueOffset;
以上是AtomicIntege
都有value,这是它们的当前实际值
valueOffset保存的是value的偏移量下面给出一个简单的AtomicIntege例子:
public class AtomicTest { public static AtomicInteger atomicInteger = new AtomicInteger(); //public static AtomicReference atomicReference = new AtomicReference(); public static class Demo implements Runnable{ @Override public void run() { for (int j=0;j<1000;j++){ atomicInteger.incrementAndGet(); //当前值加1并且返回当前值 } } } public static void main(String[] args) throws InterruptedException { ExecutorService es = Executors.newFixedThreadPool(10); for (int i =0;i<10;i++){ es.submit(new Demo()); } Thread.sleep(5000); System.out.println(atomicInteger); }}
你试着执行一下,如果打印出10000说明线程安全
使用CAS操作比同步锁拥有更好的性能我们来看下incrementAndGet()
的源码:
public final int incrementAndGet() { return unsafe.getAndAddInt(this, valueOffset, 1) + 1; }
来看下getAndAddInt()
源码:
public final int getAndAddInt(Object var1, long var2, int var4) { int var5; do { var5 = this.getIntVolatile(var1, var2); } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4)); return var5; }
这里有一个循环,再细看源码发现是native的,虽然看不到原生代码,但是可以看出它这里做了一个CAS操作,不断地进行多个变量的比较,只有预设值和新值相等时,才跳出循环
var5就是需要更新的变量,var1和var2是预设值和新值死锁
讲了那么多无锁的操作,我们来看一下一个死锁的现象
两个线程互相占着对方想得到的锁,就会出现死锁状况public class DeadLock extends Thread{ protected String suo; public static String zuo = new String(); public static String you = new String(); public DeadLock(String suo){ this.suo=suo; } @Override public void run(){ if (suo==zuo){ synchronized (zuo){ System.out.println("拿到了左,正在拿右......"); synchronized (you){ System.out.println("拿到了右,成功了"); } } } if (suo==you){ synchronized (you){ System.out.println("拿到了右,正在拿左......"); synchronized (zuo){ System.out.println("拿到了zuo,成功了"); } } } } public static void main(String[] args) throws InterruptedException { for (int i=0;i<10000;i++){ DeadLock t1 = new DeadLock(zuo); DeadLock t2 = new DeadLock(you); t1.start();t2.start(); } Thread.sleep(50000); }}
如图:
出现了两个线程的死锁现象,所以说去锁不仅能提升性能,也能防止死锁的产生
今天就先到这里,大家可以看看这些内容的拓展
记得点关注看更新,谢谢阅读