博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
Java高并发之从零到放弃
阅读量:6433 次
发布时间:2019-06-23

本文共 4191 字,大约阅读时间需要 13 分钟。

前言

本篇主要讲解如何去优化锁机制
或者克服多线程因为锁可导致性能下降的问题
ThreadLocal线程变量
有这样一个场景,前面是一大桶水,10个人去喝水,为了保证线程安全,我们要在杯子上加锁
导致大家轮着排队喝水,因为加了锁的杯子是同步的,只能有一个人拿着这个唯一的杯子喝水
这样子大家都喝完一杯水需要很长的时间
如果我们给每个人分发一个杯子呢?是不是每人喝到水的时间缩小到了十分之一
多线程并发也是一个道理
在每个Thread中都有自己的数据存放空间(ThreadLocalMap)
而ThreadLocal就是在当前线程的存放空间中存放数据
下面这个例子,在每个线程中存放一个arraylist,而不是大家去公用一个arraylist

public class ThreadLocalTest {    public static ThreadLocal threadLocal = 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);    }}

如图:

image

出现了两个线程的死锁现象,所以说去锁不仅能提升性能,也能防止死锁的产生。

文章来源:

更多参考内容请登录:

转载地址:http://ztaga.baihongyu.com/

你可能感兴趣的文章
Hangfire 使用笔记
查看>>
(C#)Windows Shell 外壳编程系列8 - 同后缀名不同图标?
查看>>
【实操】配置负载均衡
查看>>
教你彻底学会c语言基础——文件操作
查看>>
对Java基本数据类型的再思考
查看>>
滑动返回-方式1
查看>>
如何使用免费控件将Word表格中的数据导入到Excel中
查看>>
seafile服务器配置
查看>>
印度OTA产业获资本青睐,HappyEasyGo完成数千万美金A+轮融资
查看>>
HyperLedger Fabric 1.2 区块链应用场景(3.1)
查看>>
【Java入门提高篇】Day13 Java中的反射机制
查看>>
也谈谈初创公司的技术团队建设
查看>>
关于 Django 开发的 11 件事
查看>>
Android屏蔽隐藏系统自带输入键盘
查看>>
阿里云 APM 解决方案地图
查看>>
中国HBase技术社区第一届MeetUp-HBase2.0研讨圆桌会
查看>>
阿里云服务器亚太南部 1、亚太南部 2、亚太东北 1、 亚太东北 2是哪个城市
查看>>
学渣的模块化之路——50行代码带你手写一个common.js规范
查看>>
把前端监控做到极致
查看>>
python——变量
查看>>