实战java高并发程序设计第四章-锁优化(二)

承接上文,欢迎关注

实现原理

ThreadLocal类的get, set方法:

    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);          //获取threadlocalmap
        if (map != null)
            map.set(this, value);            //键位当前threadlocal对象,value为保存值
        else
            createMap(t, value);
    }
    
    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();
    }
回收清理:
线程类Thread在退出时,会进行一些清理工作,其中包括清理ThreadLocalMap
    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;
    }
注意:
使用线程池未必会清除threadlocal,因为线程总会存在,而不会退出,多次使用后可能造成内存泄漏.故此处推荐使用ThreadLocal.remove()方法来移除变量.
ThreadLocalMap实现了弱引用,即进行GC时,一旦发现弱引用则立即回收.ThreadLocalMap中每一个Entry是一个WeakReference<ThreadLocal>,ThreadLocal为key. 当ThreadLocal的外部强引用被回收时,ThreadLocalMap的key将会变成null.放系统进行ThreadLocalMap清理时(添加新变量进入表中时,会自动进行清理),垃圾数据会被回收.
无锁操作

无锁属于乐观锁,不采用重量级锁,主要实现原理为CAS比较交换.

比较交换CAS
特点:非阻塞性,免疫死锁问题,线程间影响比锁要小,总结来说,性能更强
CAS(V,E,N) V表示要更新的变量,E表示预期值,N表示新值.当V=E时才会去更新V=N,否则不作任何操作,多个线程去CAS更新时,只会有一个成功

线程安全整数:AtomicInteger
特点:线程安全,使用CAS,性能强
Java中的指针Unsafe类
sun.misc.Unsafe类型,仅能在JDK中使用,第三方无法调用

AtomicReference
AtomicReference与AtomicInteger相似,是对普通对象的引用.
在CAS中,存在一个问题,当一个变量连续被修改两次,最后一次修改成原来的值,这样当前对象就无法判断该对象是否被修改过

image.png

当存在这样一个场景时,AtomicReference便无效了:

    //有一家蛋糕店,当客户余额低于20元时,主动给客户赠送20元余额,每位客户只能被赠送一次
    //如果使用原子类的话,当客户低于20元,获赠20元余额时,客户再次消费20元整数额,此时,赠予前和赠予后金额一致,故可能存在多次赠予的情况
    public class AtomicReferenceDemo {
    static AtomicReference<Integer> money=new AtomicReference<Integer>();
    public static void main(String[] args) {
        money.set(19);
        //模拟多个线程同时更新后台数据库,为用户充值
        for(int i = 0 ; i < 3 ; i++) {              
            new Thread() {  
                public void run() {  
                    while(true){
                        while(true){
                            Integer m=money.get();
                            if(m<20){
                                if(money.compareAndSet(m, m+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("没有足够的金额");
                            break;
                        }
                    }
                    try {Thread.sleep(100);} catch (InterruptedException e) {}
                }
            }  
        }.start();  
    }
}
带时间戳的AtomicStampedReference
为解决上述问题,AtomicStampedeference内部维护了对象值,也维护了更新时间戳.
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 timestamp=money.getStamp();
            new Thread() {  
                public void run() {  
                    while(true){
                        while(true){
                            Integer m=money.getReference();
                            if(m<20){
                                if(money.compareAndSet(m, m+20,timestamp,timestamp+1)){    //CAS修改值,并且修改时间戳
                                    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 timestamp=money.getStamp();
                        Integer m=money.getReference();
                        if(m>10){
                            System.out.println("大于10元");
                            if(money.compareAndSet(m, m-10,timestamp,timestamp+1)){
                                System.out.println("成功消费10元,余额:"+money.getReference());
                                break;
                            }
                        }else{
                            System.out.println("没有足够的金额");
                            break;
                        }
                    }
                    try {Thread.sleep(100);} catch (InterruptedException e) {}
                }
            }  
        }.start();  
    }
}    
无锁的数组AtomicIntegerArray
原子数组还有AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
public class AtomicIntegerArrayDemo {
    static AtomicIntegerArray arr = new AtomicIntegerArray(10);
    public static class AddThread implements Runnable{
        public void run(){
           for(int k=0;k<10000;k++)
               arr.getAndIncrement(k%arr.length());        //k%length索引处+1
        }
    }
    public static void main(String[] args) throws InterruptedException {
        Thread[] ts=new Thread[10];
        for(int k=0;k<10;k++){
            ts[k]=new Thread(new AddThread());
        }
        for(int k=0;k<10;k++){ts[k].start();}
        for(int k=0;k<10;k++){ts[k].join();}
        System.out.println(arr);
    }
}
普通变量升级原子变量AtomicIntegerFieldUpdater

updater升级有三种: AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater

public class AtomicIntegerFieldUpdaterDemo {
    public static class Candidate{
        int id;
        volatile int score;        //不支持静态变量,且必须申明为volatile,且访问权限要支持(private不行)
    }
    public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater 
        = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");    //声明要升级的类以及成员变量.
    //检查Updater是否工作正确
    public static AtomicInteger allScore=new AtomicInteger(0);
    public static void main(String[] args) throws InterruptedException {
        final Candidate stu=new Candidate();
        Thread[] t=new Thread[10000];
        for(int i = 0 ; i < 10000 ; i++) {  
            t[i]=new Thread() {  
                public void run() {  
                    if(Math.random()>0.4){
                        scoreUpdater.incrementAndGet(stu);        //使用升级类去做CAS原子自增
                        allScore.incrementAndGet();                //验证升级类是否正确
                    }
                }  
            };
            t[i].start();
        }  
        for(int i = 0 ; i < 10000 ; i++) {  t[i].join();}
        System.out.println("score="+stu.score);        //最终打印 score和allscore一致.
        System.out.println("allScore="+allScore);
    }
}
注意:
1.只能修改可见范围内的变量,因为Updater使用反射得到变量.
2.为了保证变量被正确读取,必须为volatile
3.CAS通过对象实例中的偏移量进行复制,因此,不支持static变量
无锁的LockFreeVector

待续.....

SynchronousQueue
此类的容量为0,每一个写都要对应一个读操作,其内部也有很多无锁操作
对SynchronousQueue来说,它将put()和take()均抽象为一个共同方法Transferer.transfer().从字面上来看,就是数据传递
Object transfer(Object e,boolean timed,long nanos) //e不为空时,表示入队,e为空时表示出队
                                                   //timed 表示是否有超时,nanos表示超时时间
                                                   //返回值为空表示失败,不为空表示成功
SynchronousQueue内部会维护一个线程等待队列,保存等待线程及相关数据信息,如生产者将数据放入SynchronousQueue中,如果没有消费者接受,则数据本身和对象都会打包在队列中等待(SynchronousQueue本身容量为0,数据是不能放入的,但是内部的线程等待队列可以存放)
Transferer.transfer()函数的实现是SynchronousQueue的核心,大提分为三个步骤.

(1)如果等待队列为空,或者队列中节点的类型和本次操作是一致的,那么将当前操作压入队列等待。比如,等待队列中是读线程等待,本次操作也是读,因此这两 个读都需要等待。进入等待队列的线程可能会被挂起,它们会等待一个“匹配” 操作

(2)如果等待队列中的元素和本次操作是互补的(比如等待操作是读,而本次操作是写),那么就插入一个“完成”状态的节点,并且让它“匹配”到一个等待节点 上。接着弹出这两个节点,并且使得对应的两个线程继续执行。

(3)如果线程发现等待队列的节点就是“完成”节点,那么帮助这个节点完成任务,其流程和步骤(2)是一致的。

死锁
定位:
jps:查看java进程的进程ID
jstack (ID):查看线程的线程堆栈

为了避免死锁问题,可以使用无锁函数以及重入锁,通过重入锁的中断或者限时等待可以有效规避死锁带来的问题 

快来关注我把~
努力更新中
ovo

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352

推荐阅读更多精彩内容