【Java高并发学习】JDK内部锁优化策略概要

JDK内部锁优化策略概要

图片过大导致字体较小,可下载后查看。

1.ThreadLocal

  1. 为每一个线程提供所需要的资源
  2. 需要在应用层面保证。如果在应用上为每一个线程分配了相同的对象实例,依旧无法保证线程安全

1.1例子

package threadLocal;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
 * ThreadLocal:
 * 1.判断当前线程是否持有使用的对象实例
 * 2.如果不持有则new新建一个并设置到当前线程中进行使用
 * 3.该功能的实现需要在应用层面进行保证
 * @author wsz
 * @date 2017年12月20日
 */

/*
     1.获取当前线程
     2.获取线程的ThreadLocalMap
     3.设值到ThreadLocalMap中
     public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }
 
 	 1.获取当前线程对象
 	 2.获取线程的ThreadLocalMap
 	 3.线程作为key获取实际的对象等数据
     public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

	
 */
public class ThreadLocalDemo {

	//SimpleDateFormat,parse()不是线程安全的
	private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
	
	static ThreadLocal<SimpleDateFormat> tl = new ThreadLocal<SimpleDateFormat>();
	
	public class Demo2 implements Runnable{
		private int i;
		
		public Demo2(int i) {
			this.i = i;
		}
		@Override
		public void run() {
			if(tl.get() == null)//如果当前线程不持有该对象实例,则新建一个并设置到当前线程中
				tl.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
			try {
				Date date = tl.get().parse("2017-12-20 20:12:"+i%60);
				System.out.println(date);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
	}
	
	public class Demo1 implements Runnable{
		private int i;
		public Demo1(int i) {
			this.i = i;
		}
		@Override
		public void run() {
			try {
				Date date = sdf.parse("2017-12-20 20:12:"+i%60);
				System.out.println(date);
			} catch (ParseException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		ExecutorService ftp = Executors.newFixedThreadPool(10);
		for(int i = 0 ; i< 5000 ; i++) {
//			ftp.execute(new ThreadLocalDemo().new Demo1(i));
			ftp.execute(new ThreadLocalDemo().new Demo2(i));
		}
	}
}

1.2set()

  1. 获取当前线程
  2. 获取线程的ThreadLocalMap
  3. 设值到ThreadLocalMap中
     public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

1.3get()

  1.  获取当前线程对象
  2.  获取线程的ThreadLocalMap
  3. 线程作为key获取实际的对象等数据
     public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

1.4exit()

ThreadLocal内部的变量只要线程不退出,将一直存在与内存中。在使用线程池时,这些线程不一定会退出,如果使用的对象占用的内存较大,可能出现内存泄漏的风险。
可以使用ThreadLocal.remove()方法将这些变量移除,JVM回收这些不再需要的对象。为了方便JVM的回收,在exit()方法中将某些变量都设置为了null。
    /**
     * This method is called by the system to give a Thread
     * a chance to clean up before it actually exits.
     */
    private void exit() {
        if (group != null) {
            group.threadTerminated(this);
            group = null;
        }
        /* Aggressively null out all reference fields: see bug 4006245 */
        target = null;
        /* Speed the release of some of these resources */
        threadLocals = null;
        inheritableThreadLocals = null;
        inheritedAccessControlContext = null;
        blocker = null;
        uncaughtExceptionHandler = null;
    }

1.5性能测试

线程利用两种不同资源类型来产生200W个随机数测试性能。
在多线程共享一个Random实例情况下,5个线程的总耗时时间为3924ms;在ThreadLocal模式下,耗时仅为198ms。性能差异很明显。
package threadLocal;

import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * 测试ThreadLocal对性能的影响
 * @author wsz
 * @date 2017年12月20日
 */
public class ThreadLocalEffic {
	public static final int NUM = 2000000;//每个线程要产生的随机数数量
	public static final int SIZE = 5;     //参与工作的线程数量
	static ExecutorService exe = Executors.newFixedThreadPool(SIZE);//固定数量的线程池
	//多线程共享对象
	public static Random rnd = new Random(123);	
	//ThreadLocal对象封装的Random
	public static ThreadLocal<Random> trnd = new ThreadLocal<Random>() {
		@Override
		protected Random initialValue() {
			return new Random(123);
		}
	};
	
	public class RndTest implements Callable<Long>{
		private int model = 0;
		
		public RndTest(int model) {
			this.model = model;
		}
		
		public Random getRandom() {//分类进行返回不同的随机数对象
			if(model == 0) {
				return rnd;
			}else if(model == 1) {
				return trnd.get();
			}else {
				return null;
			}
		}
		
		//消耗的时间
		@Override
		public Long call() throws Exception {
			long b = System.currentTimeMillis();
			for(int i= 0 ;i < NUM ; i++) {
				getRandom().nextInt();
			}
			long e = System.currentTimeMillis();
			System.out.println(Thread.currentThread().getClass()+" take "+(e-b)+"ms");
			return e-b;
		}
	}
	
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		Future<Long>[] futs = new Future[SIZE];
		//普通Random
		for (int i = 0; i < SIZE; i++) {
			futs[i] = exe.submit(new ThreadLocalEffic().new RndTest(0));
		}

		int totaltime = 0;
		for(int i =0; i< SIZE; i++) {
			totaltime += futs[i].get();
		}
		System.out.println("多线程访问同一个Random实例:"+totaltime+"ms");

		//ThreadLocal的Random
		for (int i = 0; i < SIZE; i++) {
			futs[i] = exe.submit(new ThreadLocalEffic().new RndTest(1));
		}

		totaltime = 0;
		for(int i =0; i< SIZE; i++) {
			totaltime += futs[i].get();
		}
		System.out.println("多线程访问同一个Random实例:"+totaltime+"ms");
		exe.shutdown();
	}

}

class java.lang.Thread take 729ms
class java.lang.Thread take 787ms
class java.lang.Thread take 801ms
class java.lang.Thread take 804ms
class java.lang.Thread take 803ms
多线程访问同一个Random实例:3924ms
class java.lang.Thread take 39ms
class java.lang.Thread take 39ms
class java.lang.Thread take 39ms
class java.lang.Thread take 40ms
class java.lang.Thread take 41ms
多线程访问同一个Random实例:198ms

2.悲观锁+无锁

  1. 也即普通的加锁模式,总是假设每一次的临界区操作会产生冲突;在并发多线程下若多个线程同时需要访问临界区资源,便牺牲时间进行等待。
  2. 无锁是一种乐观的策略,它会假设对资源的访问没有冲突,也不会进行等待。但是当遇到冲突时,将采用CAS比较交换技术鉴别线程冲突,一旦检测到冲突产生,就重试当前操作直到没有冲突为止。

2.1比较交换CAS


3.无锁的开发工具

3.1线程安全整数AtomicInteger

  1. private volatile int value;代表AtomicInteger的当前实际取值。
  2. valueOffset:保存value字段在AtomicInteger对象中的偏移量。
  3. incrementAdnGet():使用CAS操作将自己加1,并返回当前值。
  4. Unsafe类:采用指针技术。
public class AtomicInteger extends Number implements java.io.Serializable {
    private static final long serialVersionUID = 6214790243416807050L;

    // setup to use Unsafe.compareAndSwapInt for updates
    private static final Unsafe unsafe = Unsafe.getUnsafe();
    private static final long valueOffset;

    static {
        try {
            valueOffset = unsafe.objectFieldOffset
                (AtomicInteger.class.getDeclaredField("value"));
        } catch (Exception ex) { throw new Error(ex); }
    }

    private volatile int value;

    /**
     * Atomically increments by one the current value.
     *
     * @return the updated value
     */
    public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

3.2对象引用AtomicReference

  1. 封装对普通对象的引用,保证在修改对象引用时的线程安全
  2. 可能出现CAS技术中逻辑不严谨问题
package atomic;

import java.util.concurrent.atomic.AtomicReference;
/**
 * 初始账户19元;当小于20元时,每次充值20元,且只能最多充值一次;多于10元时,每次消费10元。
 * 使用AtomicReference将会被重复充值,不符合实际情况
 * @author wsz
 * @date 2017年12月21日
 */
public class AtomicReferenceDemo {
	//定义账户
	static AtomicReference<Integer> money = new AtomicReference<Integer>();
	
	public static void main(String[] args) {
		//初始账户有19元
		money.set(19);
		
		for(int i = 0; i <20; i++) {//线程进行充值
			new Thread() {
				public void run() {
					while(true) {
						Integer m = money.get();
						if(m < 20) {
							if(money.compareAndSet(m, m+20)) {//每次充值新增20
								System.out.println("少于20,充值成功,目前有:"+money.get());
								break;
							}
						}else {
							System.out.println("超过20");
							break;
						}
					}
				}
			}.start();
		}
		new Thread() {
			public void run() {
				for(int i = 0 ; i < 100 ; i++) {
					while(true) {
						Integer m = money.get();
						if(m > 10) {
							System.out.println("多于10");
							if(money.compareAndSet(m, m-10)) {
								System.out.println("消费10元,剩余:"+money.get());
								break;
							}
						}else {
							System.out.println("小于10元");
							break;
						}
					}
				}
				
			}
		}.start();
	}

}

3.3带有时间戳的AtomicStampedReference

  1. 记录修改状态,便能解决对象被反复修改导致线程无法判断对象状态的问题
  2. 不仅维护对象值;还维护时间戳。
  3. 数值被修改时,还更新时间戳,当时间戳不一致时,即可表示对象已被其他线程修改
package atomic;

import java.util.concurrent.atomic.AtomicStampedReference;
/**
 * AtomicStampedReference带有时间戳,由此可判断是否被其他线程修改
 * 此案例将只能新增一次数据
 * @author wsz
 * @date 2017年12月21日
 */
public class AtomicStampedReferenceDemo {
	
	static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);
	
	public static void main(String[] args) {
		
		for(int i = 0; i <3; i++) {//线程进行充值
			final int stamp = money.getStamp();//充值前,final时间戳,后面充值将修改一次时间戳(+1);
											  //后续操作的期望时间戳仍为第一次设置的值,但时间戳已被修改,无法满足。
			new Thread() {
				public void run() {
					while(true) {
						while(true) {
							Integer m = money.getReference();
							if(m < 20) {
								if(money.compareAndSet(m, m+20, stamp, stamp+1)) {//期望值、写入新值、期望时间戳、新时间戳
									System.out.println("余额小于20,充值成功,余额:"+money.getReference());
									break;
								}
							}else {
//								System.out.println("余额大于20");
								break;
							}
						}
					}
				}
			}.start();
		}
		new Thread() {
			public void run() {
				for(int i = 0 ; i < 100 ; i++) {
					while(true) {
						int stamp = money.getStamp();//获取时间戳
						Integer m = money.getReference();//获取值
						if(m > 10) {
							System.out.println("大于10");
							if(money.compareAndSet(m, m-10, stamp, stamp+1)) {
								System.out.println("消费10元,剩余:"+money.getReference());
								break;
							}
						}else {
							System.out.println("小于10元");
							break;
						}
					}
				}
				
			}
		}.start();
	}

}







  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值