实战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为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中,存在一个问题,当一个变量连续被修改两次,最后一次修改成原来的值,这样当前对象就无法判断该对象是否被修改过

实战java高并发程序设计第四章-锁优化(二)_第1张图片
image.png

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

    //有一家蛋糕店,当客户余额低于20元时,主动给客户赠送20元余额,每位客户只能被赠送一次
    //如果使用原子类的话,当客户低于20元,获赠20元余额时,客户再次消费20元整数额,此时,赠予前和赠予后金额一致,故可能存在多次赠予的情况
    public class AtomicReferenceDemo {
    static AtomicReference money=new AtomicReference();
    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 money=new AtomicStampedReference(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 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

你可能感兴趣的:(实战java高并发程序设计第四章-锁优化(二))