java锁机制

来源:互联网 发布:平板推荐知乎 编辑:程序博客网 时间:2024/06/05 19:02

Synchronized Lock

Java在JDK1.5之前都是靠synchronized关键字保证同步的,这种通过使用一致的锁定协议来协调对共享状态的访问,可以确保无论哪个线程持有守护变量的锁,都采用独占的方式来访问这些变量,如果出现多个线程同时访问锁,那第一些线线程将被挂起,当线程恢复执行时,必须等待其它线程执行完他们的时间片以后才能被调度执行,在挂起和恢复执行过程中存在着很大的开销。锁还存在着其它一些缺点,当一个线程正在等待锁时,它不能做任何事。如果一个线程在持有锁的情况下被延迟执行,那么所有需要这个锁的线程都无法执行下去。如果被阻塞的线程优先级高,而持有锁的线程优先级低,将会导致优先级反转(Priority Inversion)。
 
 
 
volatile的优势

    与锁相比,volatile变量是一和更轻量级的同步机制,因为在使用这些变量时不会发生上下文切换和线程调度等操作,但是volatile变量也存在一些局限:不能用于构建原子的复合操作,因此当一个变量依赖旧值时就不能使用volatile变量
 
 
 
悲观锁和乐观锁

独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。而另一个更加有效的锁就是乐观锁。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。
 
 
 
CAS操作

    Compare and Swap,比较并操作,CPU指令,在大多数处理器架构,包括IA32、Space中采用的都是CAS指令,CAS的语义是“我认为V的值应该为A,如果是,那么将V的值更新为B,否则不修改并告诉V的值实际为多少”,CAS是项乐观锁技术,当多个线程尝试使用CAS同时更新同一个变量时,只有其中一个线程能更新变量的值,而其它线程都失败,失败的线程并不会被挂起,而是被告知这次竞争中失败,并可以再次尝试。CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
 
 
 
JVM对CAS的支持

    在JDK1.5之前,如果不编写明确的代码就无法执行CAS操作,在JDK1.5中引入了底层的支持,在int、long和对象的引用等类型上都公开了CAS的操作,并且JVM把它们编译为底层硬件提供的最有效的方法,在运行CAS的平台上,运行时把它们编译为相应的机器指令,如果处理器不支持CAS指令,那么JVM将使用自旋锁。在原子类变量中,如java.util.concurrent.atomic中的AtomicXXX,都使用了这些底层的JVM支持为数字类型的引用类型提供一种高效的CAS操作,而在java.util.concurrent中的大多数类在实现时都直接或间接的使用了这些原子变量类。
 
 
 
下面代码说明了CAS的语义(并不是真正的实现,CAS的实现是调用native方法):
 


Java代码 
1./**
2. * Huisou.com Inc.
3. * Copyright (c) 2011-2012 All Rights Reserved.
4. */ 
5. 
6.package thread; 
7. 
8.import org.apache.http.annotation.GuardedBy; 
9.import org.apache.http.annotation.ThreadSafe; 
10. 
11./**
12. * @description
13. * 
14. * @author chenzehe
15. * @email hljuczh@163.com
16. * @create 2013-1-6 上午09:02:47
17. */ 
18.@ThreadSafe 
19.public class SimulatedCAS { 
20.    @GuardedBy("this") 
21.    private int value; 
22.     
23.    public synchronized int get() { 
24.        return value; 
25.    } 
26.     
27.    public synchronized int comparedAndSwap(int expectedValue, int newValue) { 
28.        int oldValue = value; 
29.        if (oldValue == expectedValue) { 
30.            value = newValue; 
31.        } 
32.        return oldValue; 
33.    } 
34.     
35.    public synchronized boolean compareAndSet(int expectedValue, int newValue) { 
36.        return (expectedValue == comparedAndSwap(expectedValue, newValue)); 
37.    } 
38.} 
 
 下面代码演示了非阻塞计数器:
 


Java代码 
1./**
2. * Huisou.com Inc.
3. * Copyright (c) 2011-2012 All Rights Reserved.
4. */ 
5. 
6.package thread; 
7. 
8./**
9. * @description
10. * 
11. * @author chenzehe
12. * @email hljuczh@163.com
13. * @create 2013-1-6 上午09:48:52
14. */ 
15. 
16.public class CasCounnter { 
17.    private SimulatedCAS    value; 
18.     
19.    public int getValue() { 
20.        return value.get(); 
21.    } 
22.     
23.    public int increment() { 
24.        int v; 
25.        do { 
26.            v = value.get(); 
27.        } while (v != value.comparedAndSwap(v, v + 1)); 
28.        return v + 1; 
29.    } 
30.} 
 
 AtomicInteger中实现自增的代码为:
 


Java代码 
1.public final int getAndIncrement() { 
2.        for (;;) { 
3.            int current = get(); 
4.            int next = current + 1; 
5.            if (compareAndSet(current, next)) 
6.                return current; 
7.        } 
8.} 
9. 
10.public final boolean compareAndSet(int expect, int update) { 
11.    return unsafe.compareAndSwapInt(this, valueOffset, expect, update); 
12.} 
  
表面上看起来,基于CAS的计数器似乎比基于锁的计数器在性能上更差一些,因为它还要执行更多的操作和更复杂的控制流,并且还依赖看似复杂的CAS操作,实际上,当竞争程度不高时,基于CAS的计数器在性能上远远超过了基于锁的计数器,而在没有竞争时甚至更高,如果要快速获取无竞争的锁,那么至少需要一次CAS操作加上与其它锁相关的操作,因此基于锁的计数即使在最好的情况下也会比基于CAS的计数器在一般情况下执行更多的操作。由于CAS在大多数情况下都能执行成功,因此硬件能够正确的预测while循环中的分支,从而把控制逻辑的开锁降到最低。
 
 
 
锁与原子变量的性能比较

    在高度竞争的情况下,锁的性能将超过原子变量的性能,但在更真实的竞争情况下,原子变量的性能将超过锁的性能,这是因为锁在发竞争时会挂起线程,从而降低了CPU的使用率和共享内存总线上的同步通信量,另一方面,如果使用原子变量,那么发出调用的类负责对竞争进行管理,在遇到竞争时立即重试,这通常是种正确的做法,但是在竞争激烈环境下会导致更多的竞争。在实际的情况中,任何一个程序都不会除了竞争锁或原子变量而什么事都不做,不会达到很高的竞争,所以在更常见的情况下,原子变量的效率会更高,在可伸缩性上要高于锁。
 
 
 
非阻塞算法

    如果在某个算法中,一个线程的失败或挂起不会引起其它线程也失败或挂起,那么这个算法就被称为非阻塞算法;如果在算法的每个步骤中都存在每个线程能够执行下去,那么这种算法称为无锁算法(Lock-Free)。如果在算法中仅将CAS用于协调线程之间的操作,并且能正确的实现,那么它即是一种非阻塞算法,也是一种无锁算法。在非阻塞算法中通常不会出现死锁和优先级反转问题,但可能出现饥饿和活锁问题,因为在算法中会反复的重试。
 
下面代码为非阻塞的栈(使用Treiber算法):
 


Java代码 
1.package thread; 
2.import java.util.concurrent.atomic.AtomicReference; 
3.public class ConcurrentStack<T> { 
4.    private AtomicReference<Node<T>>    stacks  = new AtomicReference<Node<T>>(); 
5.    public T push(T e) { 
6.        Node<T> oldNode, newNode; 
7.        for (;;) { // 这里的处理非常的特别,也是必须如此的。 
8.            oldNode = stacks.get(); 
9.            newNode = new Node<T>(e, oldNode); 
10.            if (stacks.compareAndSet(oldNode, newNode)) { 
11.                return e; 
12.            } 
13.        } 
14.    }    
15.    public T pop() { 
16.        Node<T> oldNode, newNode; 
17.        do{ 
18.                        oldNode = stacks.get(); 
19.                        if(oldNode==null){ 
20.                              return null; 
21.                        } 
22.            newNode = oldNode.next; 
23.                }while(!stacks.compareAndSet(oldNode, newNode)); 
24.                 return oldNode.object; 
25.    }    
26.    private static final class Node<T> { 
27.        private T       object;      
28.        private Node<T>   next;        
29.        private Node(T object, Node<T> next) { 
30.            this.object = object; 
31.            this.next = next; 
32.        } 
33.    }    
34.} 
  
ABA问题

     如果在算法中的节点可以被循环使用,那么在使用CAS指令时就会出现这种问题,主要指在没有垃圾回收机制的环境中,在CAS操作中,在更新之前先判断V的值是否仍然A,如果是的话就继续执行更新操作,但是有的时候还需要知道“自从上次看到V的值为A以后,这个值是否发生了变化?”,在某些算法中,如果V的值先由A变成B,再由B变成A,那么仍然认为是发生了变化,并需要重新执行算法中的步骤。在这种情况下,即使链表的头节点仍然指向之前观察到的节点,但也不足以说明链表的内容没有变化。如果通过垃圾回收机制仍然无法避免ABA问题,那么还有下面简单方案:不是更新某个引用的值,而是更新两个值,包括一个引用和一个版本号,即使这个值由A变为B,然后为变为A,版本号也是不同的。AtomicStampedReference和AtomicMarkableReference支持在两个变量上执行原子的条件更新。AtomicStampedReference更新一个“对象-引用”二元组,通过在引用上加上“版本号”,从而避免ABA问题,AtomicMarkableReference将更新一个“对象引用-布尔值”的二元组。

 

Lock与synchronized 的区别

LockSynchronizedReentrantLock线程区别 .


多次思考过这个问题,都没有形成理论,今天有时间了,我把他总结出来,希望对大家有所帮助
 
 
 
 
 
1、ReentrantLock 拥有Synchronized相同的并发性和内存语义,此外还多了 锁投票,定时锁等候和中断锁等候
 
     线程A和B都要获取对象O的锁定,假设A获取了对象O锁,B将等待A释放对O的锁定,
 
     如果使用 synchronized ,如果A不释放,B将一直等下去,不能被中断
 
     如果 使用ReentrantLock,如果A不释放,可以使B在等待了足够长的时间以后,中断等待,而干别的事情
 
 
 
    ReentrantLock获取锁定与三种方式:
    a)  lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
 
    b) tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
 
    c)tryLock(long timeout,TimeUnit unit),   如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
 
    d) lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断
 
 
 
2、synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
 
 
 
3、在资源竞争不是很激烈的情况下,Synchronized的性能要优于ReetrantLock,但是在资源竞争很激烈的情况下,Synchronized的性能会下降几十倍,但是ReetrantLock的性能能维持常态;
 
 
 
 
 
下面内容 是转载 http://zzhonghe.iteye.com/blog/826162
 
 
 
5.0的多线程任务包对于同步的性能方面有了很大的改进,在原有synchronized关键字的基础上,又增加了ReentrantLock,以及各种Atomic类。了解其性能的优劣程度,有助与我们在特定的情形下做出正确的选择。

总体的结论先摆出来: 

synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。

ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。

Atomic:
和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段代码中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。


所以,我们写同步的时候,优先考虑synchronized,如果有特殊需要,再进一步优化。ReentrantLock和Atomic如果用的不好,不仅不能提高性能,还可能带来灾难。

先贴测试结果:再贴代码(Atomic测试代码不准确,一个同步中只能有1个Actomic,这里用了2个,但是这里的测试只看速度)
==========================
round:100000 thread:5
Sync = 35301694
Lock = 56255753
Atom = 43467535
==========================
round:200000 thread:10
Sync = 110514604
Lock = 204235455
Atom = 170535361
==========================
round:300000 thread:15
Sync = 253123791
Lock = 448577123
Atom = 362797227
==========================
round:400000 thread:20
Sync = 16562148262
Lock = 846454786
Atom = 667947183
==========================
round:500000 thread:25
Sync = 26932301731
Lock = 1273354016
Atom = 982564544
 
代码如下:
 
 
 


Java代码 
1.package test.thread;    
2.   
3.import static java.lang.System.out;    
4.   
5.import java.util.Random;    
6.import java.util.concurrent.BrokenBarrierException;    
7.import java.util.concurrent.CyclicBarrier;    
8.import java.util.concurrent.ExecutorService;    
9.import java.util.concurrent.Executors;    
10.import java.util.concurrent.atomic.AtomicInteger;    
11.import java.util.concurrent.atomic.AtomicLong;    
12.import java.util.concurrent.locks.ReentrantLock;    
13.   
14.public class TestSyncMethods {    
15.        
16.    public static void test(int round,int threadNum,CyclicBarrier cyclicBarrier){    
17.        new SyncTest("Sync",round,threadNum,cyclicBarrier).testTime();    
18.        new LockTest("Lock",round,threadNum,cyclicBarrier).testTime();    
19.        new AtomicTest("Atom",round,threadNum,cyclicBarrier).testTime();    
20.    }    
21.   
22.    public static void main(String args[]){    
23.            
24.        for(int i=0;i<5;i++){    
25.            int round=100000*(i+1);    
26.            int threadNum=5*(i+1);    
27.            CyclicBarrier cb=new CyclicBarrier(threadNum*2+1);    
28.            out.println("==========================");    
29.            out.println("round:"+round+" thread:"+threadNum);    
30.            test(round,threadNum,cb);    
31.                
32.        }    
33.    }    
34.}    
35.   
36.class SyncTest extends TestTemplate{    
37.    public SyncTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){    
38.        super( _id, _round, _threadNum, _cb);    
39.    }    
40.    @Override   
41.    /**  
42.     * synchronized关键字不在方法签名里面,所以不涉及重载问题  
43.     */   
44.    synchronized long  getValue() {    
45.        return super.countValue;    
46.    }    
47.    @Override   
48.    synchronized void  sumValue() {    
49.        super.countValue+=preInit[index++%round];    
50.    }    
51.}    
52.   
53.   
54.class LockTest extends TestTemplate{    
55.    ReentrantLock lock=new ReentrantLock();    
56.    public LockTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){    
57.        super( _id, _round, _threadNum, _cb);    
58.    }    
59.    /**  
60.     * synchronized关键字不在方法签名里面,所以不涉及重载问题  
61.     */   
62.    @Override   
63.    long getValue() {    
64.        try{    
65.            lock.lock();    
66.            return super.countValue;    
67.        }finally{    
68.            lock.unlock();    
69.        }    
70.    }    
71.    @Override   
72.    void sumValue() {    
73.        try{    
74.            lock.lock();    
75.            super.countValue+=preInit[index++%round];    
76.        }finally{    
77.            lock.unlock();    
78.        }    
79.    }    
80.}    
81.   
82.   
83.class AtomicTest extends TestTemplate{    
84.    public AtomicTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){    
85.        super( _id, _round, _threadNum, _cb);    
86.    }    
87.    @Override   
88.    /**  
89.     * synchronized关键字不在方法签名里面,所以不涉及重载问题  
90.     */   
91.    long  getValue() {    
92.        return super.countValueAtmoic.get();    
93.    }    
94.    @Override   
95.    void  sumValue() {    
96.        super.countValueAtmoic.addAndGet(super.preInit[indexAtomic.get()%round]);    
97.    }    
98.}    
99.abstract class TestTemplate{    
100.    private String id;    
101.    protected int round;    
102.    private int threadNum;    
103.    protected long countValue;    
104.    protected AtomicLong countValueAtmoic=new AtomicLong(0);    
105.    protected int[] preInit;    
106.    protected int index;    
107.    protected AtomicInteger indexAtomic=new AtomicInteger(0);    
108.    Random r=new Random(47);    
109.    //任务栅栏,同批任务,先到达wait的任务挂起,一直等到全部任务到达制定的wait地点后,才能全部唤醒,继续执行    
110.    private CyclicBarrier cb;    
111.    public TestTemplate(String _id,int _round,int _threadNum,CyclicBarrier _cb){    
112.        this.id=_id;    
113.        this.round=_round;    
114.        this.threadNum=_threadNum;    
115.        cb=_cb;    
116.        preInit=new int[round];    
117.        for(int i=0;i<preInit.length;i++){    
118.            preInit[i]=r.nextInt(100);    
119.        }    
120.    }    
121.        
122.    abstract void sumValue();    
123.    /*  
124.     * 对long的操作是非原子的,原子操作只针对32位  
125.     * long是64位,底层操作的时候分2个32位读写,因此不是线程安全  
126.     */   
127.    abstract long getValue();    
128.   
129.    public void testTime(){    
130.        ExecutorService se=Executors.newCachedThreadPool();    
131.        long start=System.nanoTime();    
132.        //同时开启2*ThreadNum个数的读写线程    
133.        for(int i=0;i<threadNum;i++){    
134.            se.execute(new Runnable(){    
135.                public void run() {    
136.                    for(int i=0;i<round;i++){    
137.                        sumValue();    
138.                    }    
139.   
140.                    //每个线程执行完同步方法后就等待    
141.                    try {    
142.                        cb.await();    
143.                    } catch (InterruptedException e) {    
144.                        // TODO Auto-generated catch block    
145.                        e.printStackTrace();    
146.                    } catch (BrokenBarrierException e) {    
147.                        // TODO Auto-generated catch block    
148.                        e.printStackTrace();    
149.                    }    
150.   
151.   
152.                }    
153.            });    
154.            se.execute(new Runnable(){    
155.                public void run() {    
156.   
157.                    getValue();    
158.                    try {    
159.                        //每个线程执行完同步方法后就等待    
160.                        cb.await();    
161.                    } catch (InterruptedException e) {    
162.                        // TODO Auto-generated catch block    
163.                        e.printStackTrace();    
164.                    } catch (BrokenBarrierException e) {    
165.                        // TODO Auto-generated catch block    
166.                        e.printStackTrace();    
167.                    }    
168.   
169.                }    
170.            });    
171.        }    
172.            
173.        try {    
174.            //当前统计线程也wait,所以CyclicBarrier的初始值是threadNum*2+1    
175.            cb.await();    
176.        } catch (InterruptedException e) {    
177.            // TODO Auto-generated catch block    
178.            e.printStackTrace();    
179.        } catch (BrokenBarrierException e) {    
180.            // TODO Auto-generated catch block    
181.            e.printStackTrace();    
182.        }    
183.        //所有线程执行完成之后,才会跑到这一步    
184.        long duration=System.nanoTime()-start;    
185.        out.println(id+" = "+duration);    
186.            
187.    }    
188.   
189.}   

0 0
原创粉丝点击