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

来源:互联网 发布:hyperion数据 编辑:程序博客网 时间:2024/05/18 09:18

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;}@Overridepublic 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;}@Overridepublic 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对象封装的Randompublic static ThreadLocal<Random> trnd = new ThreadLocal<Random>() {@Overrideprotected 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;}}//消耗的时间@Overridepublic 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];//普通Randomfor (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的Randomfor (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 729msclass java.lang.Thread take 787msclass java.lang.Thread take 801msclass java.lang.Thread take 804msclass java.lang.Thread take 803ms多线程访问同一个Random实例:3924msclass java.lang.Thread take 39msclass java.lang.Thread take 39msclass java.lang.Thread take 39msclass java.lang.Thread take 40msclass 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)) {//每次充值新增20System.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();}}