python 多线程 信号量安全实现方式,synchronized和lock,信号量的区别

3360人阅读
J2SE(51)
Lock的锁定是通过代码实现的,而 synchronized 是在 JVM 层面上实现的
synchronized
synchronized在锁定时如果方法块抛出异常,JVM 会自动将锁释放掉,不会因为出了异常没有释放锁造成线程死锁。但是 Lock 的话就享受不到 JVM 带来自动的功能,出现异常时必须在 finally 将锁释放掉,否则将会引起死锁。
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
一直很喜欢synchronized,因为使用它很方便。比如,需要对一个方法进行同步,那么只需在方法的签名添加一个synchronized关键字。
public void test() {}
pubilc synchronized void test() {}
synchronized 也可以用在一个代码块上,看
public void test() {
synchronized(obj) {
System.out.println("===");
synchronized 用在方法和代码块上有什么区别呢?
synchronized 用在方法签名上(以test为例),当某个线程调用此方法时,会获取该实例的对象锁,方法未结束之前,其他线程只能去等待。当这个方法执行完时,才会释放对象锁。其他线程才有机会去抢占这把锁,去执行方法test,但是发生这一切的基础应当是所有线程使用的同一个对象实例,才能实现互斥的现象。否则synchronized关键字将失去意义。
(但是如果该方法为类方法,即其修饰符为static,那么synchronized意味着某个调用此方法的线程当前会拥有该类的锁,只要该线程持续在当前方法内运行,其他线程依然无法获得方法的使用权!)
synchronized 用在代码块的使用方式:synchronized(obj){//todo code here}
当线程运行到该代码块内,就会拥有obj对象的对象锁,如果多个线程共享同一个Object对象,那么此时就会形成互斥!特别的,当obj == this时,表示当前调用该方法的实例对象。即
public void test() {
synchronized(this) {
此时,其效果等同于
public synchronized void test() {
使用synchronized代码块,可以只对需要同步的代码进行同步,这样可以大大的提高效率。
使用synchronized 代码块相比方法有两点优势:
可以只对需要同步的使用
与wait()/notify()/nitifyAll()一起使用时,比较方便
生产者/消费者问题
wait() 与notify()/notifyAll()
这三个方法都是Object的方法,并不是线程的方法!
释放占有的对象锁,线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序。而sleep()不同的是,线程调用此方法后,会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁。也就是说,在休眠期间,其他线程依然无法进入此代码内部。休眠结束,线程重新获得cpu,执行代码。wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会!
该方法会唤醒因为调用对象的wait()而等待的线程,其实就是对对象锁的唤醒,从而使得wait()的线程可以有机会获取对象锁。调用notify()后,并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM则会在等待的线程中调度一个线程去获得对象锁,执行代码。需要注意的是,wait()和notify()必须在synchronized代码块中调用。
notifyAll()
则是唤醒所有等待的线程。
为了说明这一点,举例如下:
两个线程依次打印”放入鸡蛋”“拿到鸡蛋”,分别总共打印10次。
class Plate{
/** 装鸡蛋的盘子 */
List&Object& eggs = new ArrayList&Object&();
/** 放鸡蛋 */
public synchronized void putEgg(Object egg){
while (eggs.size() == 1){
catch (InterruptedException e){
e.printStackTrace();
eggs.add(egg);
System.out.println("放入鸡蛋");
/** 取鸡蛋 */
public synchronized Object getEgg(){
while (eggs.size() == 0){
catch (InterruptedException e){
e.printStackTrace();
Object egg = eggs.get(0);
eggs.clear();
System.out.println("拿到鸡蛋");
测试类如下:
public class Egg{
public static void main(String args[]){
final Plate plate = new Plate();
for (int i = 0; i & 10; i++){
new Thread(new Runnable(){
private Object egg = new Object();
public void run(){
plate.putEgg(egg);
}).start();
new Thread(new Runnable(){
public void run(){
plate.getEgg();
}).start();
await() 与signal()/signalAll()
除了wait()和notify()协作完成线程同步之外,使用Lock也可以完成同样的目的。
ReentrantLock 与synchronized有相同的并发性和内存语义,还包含了中断锁等候和定时锁等候,意味着线程A如果先获得了对象obj的锁,那么线程B可以在等待指定时间内依然无法获取锁,那么就会自动放弃该锁。
但是由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否,而ReentrantLock使用代码实现的,系统无法自动释放锁,需要在代码中finally子句中显式释放锁lock.unlock();
同样的例子,使用lock 如何实现呢?
class Plate{
/** 装鸡蛋的盘子 */
List&Object& eggs = new ArrayList&Object&();
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
/** 放鸡蛋 */
public void putEgg(Object egg){
lock.lock();
while (eggs.size() == 1){
condition.await();
catch (InterruptedException e){
e.printStackTrace();
eggs.add(egg);
condition.signalAll();
System.out.println("放入鸡蛋");
} finally{
lock.unlock();
/** 取鸡蛋 */
public Object getEgg(){
lock.lock();
while (eggs.size() == 0){
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
Object egg = eggs.get(0);
eggs.clear();
condition.signalAll();
System.out.println("拿到鸡蛋");
} finally{
lock.unlock();
测试类如下
public class Egg{
public static void main(String args[]){
final Plate plate = new Plate();
for (int i = 0; i & 10; i++){
new Thread(new Runnable(){
private Object egg = new Object();
public void run(){
plate.putEgg(egg);
}).start();
new Thread(new Runnable(){
public void run(){
plate.getEgg();
}).start();
使用建议:
在并发量比较小的情况下,使用synchronized是个不错的选择,但是在并发量比较高的情况下,其性能下降很严重,此时ReentrantLock是个不错的方案。
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1370320次
积分:15709
积分:15709
排名:第604名
原创:318篇
转载:77篇
评论:281条
(5)(5)(9)(5)(1)(4)(5)(3)(3)(9)(12)(6)(9)(4)(4)(4)(3)(5)(11)(7)(1)(3)(10)(16)(11)(16)(14)(8)(16)(25)(1)(3)(1)(2)(8)(29)(19)(3)(1)(1)(1)(2)(1)下次自动登录
现在的位置:
& 综合 & 正文
比较ReentrantLock和synchronized和信号量Semaphore实现的同步性能
得出结论:
(1)使用Lock的性能比使用synchronized关键字要提高4~5倍;
(2)使用信号量实现同步的速度大约比synchronized要慢10~20%;
(3)使用atomic包的AtomicInter速度是比Lock要快1一个数量级。
synchronized:
在资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译通常会尽可能的进行优化synchronize,另外可读性非常好,不管用没用过5.0多线程包的程序员都能理解。
ReentrantLock:
ReentrantLock提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步(synchronized的同步是不能Interrupt的)等。在资源竞争不激烈的情形下,性能稍微比synchronized差点点。但是当同步非常激烈的时候,synchronized的性能一下子能下降好几十倍。而ReentrantLock确还能维持常态。
和上面的类似,不激烈情况下,性能比synchronized略逊,而激烈的时候,也能维持常态。激烈的时候,Atomic的性能会优于ReentrantLock一倍左右。但是其有一个缺点,就是只能同步一个值,一段中只能出现一个Atomic的变量,多于一个同步无效。因为他不能在多个Atomic之间同步。
ReentrantLock 类
java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度、性能特性或者锁定语义。ReentrantLock类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized块时,才释放锁。
在查看清单 1 中的代码示例时,可以看到 Lock 和 synchronized 有一点明显的区别 —— lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时bomb,当有一天bomb爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。
public abstract class Test {
protected S
protectedCyclicB
protected intthreadN
protectedExecutorS
public Test(Stringid, CyclicBarrier barrier, long count, int threadNum,
ExecutorServiceexecutor) {
this.barrier=
this.count=
this.threadNum= threadN
this.executor=
public voidstartTest() {
long start= System.currentTimeMillis();
for (int j= 0; j & threadN j++) {
executor.execute(newThread() {
publicvoid run() {
for(int i = 0; i & i++) {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
}catch (BrokenBarrierException e) {
e.printStackTrace();
barrier.await();
} catch(InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierExceptione) {
e.printStackTrace();
// 所有线程执行完成之后,才会跑到这一步
longduration = System.currentTimeMillis() -
System.out.println(id+ " = " + duration);
protected abstractvoid test();
测试类ReentreLockTest 源码
import thread.test.T
public class ReentreLockTest {
private static longCOUNT = 1000000;
private static Locklock = new ReentrantLock();
private static longlockCounter = 0;
private static longsyncCounter = 0;
private static longsemaCounter = 0;
private staticAtomicLong atomicCounter = new AtomicLong(0);
private staticObject syncLock = new Object();
private staticSemaphore mutex = new Semaphore(1);
public static voidtestLock(int num, int threadCount) {
static longgetLock() {
lock.lock();
returnlockC
} finally{
lock.unlock();
static longgetSync() {
synchronized(syncLock) {
returnsyncC
static longgetAtom() {
returnatomicCounter.get();
static longgetSemaphore() throws InterruptedException {
mutex.acquire();
returnsemaC
} finally{
mutex.release();
static longgetLockInc() {
lock.lock();
return++lockC
} finally{
lock.unlock();
static longgetSyncInc() {
synchronized(syncLock) {
return++syncC
static longgetAtomInc() {
returnatomicCounter.getAndIncrement();
static classSemaTest extends Test {
publicSemaTest(String id, CyclicBarrier barrier, long count,
intthreadNum, ExecutorService executor) {
super(id,barrier, count, threadNum, executor);
protectedvoid test() {
getSemaphore();
}catch (InterruptedException e) {
e.printStackTrace();
static classLockTest extends Test {
public LockTest(Stringid, CyclicBarrier barrier, long count,
intthreadNum, ExecutorService executor) {
super(id,barrier, count, threadNum, executor);
protectedvoid test() {
getLock();
static classSyncTest extends Test {
publicSyncTest(String id, CyclicBarrier barrier, long count,
intthreadNum, ExecutorService executor) {
super(id,barrier, count, threadNum, executor);
protectedvoid test() {
getSync();
static classAtomicTest extends Test {
publicAtomicTest(String id, CyclicBarrier barrier, long count,
intthreadNum, ExecutorService executor) {
super(id,barrier, count, threadNum, executor);
protectedvoid test() {
getAtom();
public static void test(Stringid, long count, int threadNum,
ExecutorServiceexecutor) {
finalCyclicBarrier barrier = new CyclicBarrier(threadNum + 1,
newThread() {
publicvoid run() {
System.out.println("==============================");
System.out.println("count= " + count + "/t" + "Thread Count = "
+threadNum);
newLockTest("Lock ", barrier, COUNT, threadNum, executor).startTest();
newSyncTest("Sync ", barrier, COUNT, threadNum, executor).startTest();
newAtomicTest("Atom ", barrier, COUNT, threadNum, executor)
.startTest();
newSemaTest("Sema ", barrier, COUNT, threadNum, executor)
.startTest();
System.out.println("==============================");
public static voidmain(String[] args) {
for (int i= 1; i & 5; i++) {
ExecutorServiceexecutor = Executors.newFixedThreadPool(10 * i);
test("",COUNT * i, 10 * i, executor);
==============================
count = 1000000
ThreadCount = 10
==============================
==============================
count = 2000000
ThreadCount = 20
==============================
==============================
count = 3000000
ThreadCount = 30
==============================
==============================
count = 4000000
ThreadCount = 40
package test.
import static java.lang.System.
import java.util.R
import java.util.concurrent.BrokenBarrierE
import java.util.concurrent.CyclicB
import java.util.concurrent.ExecutorS
import java.util.concurrent.E
import java.util.concurrent.atomic.AtomicI
import java.util.concurrent.atomic.AtomicL
import java.util.concurrent.locks.ReentrantL
public class TestSyncMethods {
public static voidtest(int round,int threadNum,CyclicBarrier cyclicBarrier){
newSyncTest("Sync",round,threadNum,cyclicBarrier).testTime();
newLockTest("Lock",round,threadNum,cyclicBarrier).testTime();
newAtomicTest("Atom",round,threadNum,cyclicBarrier).testTime();
public static voidmain(String args[]){
for(inti=0;i&5;i++){
intround=100000*(i+1);
intthreadNum=5*(i+1);
CyclicBarriercb=new CyclicBarrier(threadNum*2+1);
out.println("==========================");
out.println("round:"+round+"thread:"+threadNum);
test(round,threadNum,cb);
class SyncTest extends TestTemplate{
publicSyncTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){
super(_id, _round, _threadNum, _cb);
* synchronized关键字不在方法签名里面,所以不涉及重载问题
synchronizedlong
getValue() {
returnsuper.countV
synchronizedvoid
sumValue() {
super.countValue+=preInit[index++%round];
class LockTest extends TestTemplate{
ReentrantLocklock=new ReentrantLock();
publicLockTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){
super(_id, _round, _threadNum, _cb);
* synchronized关键字不在方法签名里面,所以不涉及重载问题
long getValue() {
lock.lock();
returnsuper.countV
lock.unlock();
void sumValue() {
lock.lock();
super.countValue+=preInit[index++%round];
lock.unlock();
class AtomicTest extends TestTemplate{
publicAtomicTest(String _id,int _round,int _threadNum,CyclicBarrier _cb){
super(_id, _round, _threadNum, _cb);
* synchronized关键字不在方法签名里面,所以不涉及重载问题
getValue() {
returnsuper.countValueAtmoic.get();
sumValue() {
super.countValueAtmoic.addAndGet(super.preInit[indexAtomic.get()%round]);
abstract class TestTemplate{
private intthreadN
protected longcountV
protected AtomicLongcountValueAtmoic=new AtomicLong(0);
protected int[]preI
protectedAtomicInteger indexAtomic=new AtomicInteger(0);
Random r=newRandom(47);
//任务栅栏,同批任务,先到达wait的任务挂起,一直等到全部任务到达制定的wait地点后,才能全部唤醒,继续执行
privateCyclicB
publicTestTemplate(String _id,int _round,int _threadNum,CyclicBarrier _cb){
this.round=_
this.threadNum=_threadN
preInit=newint[round];
for(inti=0;i&preInit.i++){
preInit[i]=r.nextInt(100);
abstract voidsumValue();
* 对long的操作是非原子的,原子操作只针对32位
* long是64位,底层操作的时候分2个32位读写,因此不是线程安全
abstract long getValue();
public voidtestTime(){
ExecutorServicese=Executors.newCachedThreadPool();
longstart=System.nanoTime();
//同时开启2*ThreadNum个数的读写线程
for(inti=0;i&threadNi++){
se.execute(newRunnable(){
publicvoid run() {
for(inti=0;i&i++){
sumValue();
//每个线程执行完同步方法后就等待
cb.await();
}catch (InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}catch (BrokenBarrierException e) {
//TODO Auto-generated catch block
e.printStackTrace();
se.execute(newRunnable(){
publicvoid run() {
getValue();
//每个线程执行完同步方法后就等待
cb.await();
}catch (InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
}catch (BrokenBarrierException e) {
//TODO Auto-generated catch block
e.printStackTrace();
//当前统计线程也wait,所以CyclicBarrier的初始值是threadNum*2+1
cb.await();
} catch(InterruptedException e) {
//TODO Auto-generated catch block
e.printStackTrace();
} catch(BrokenBarrierException e) {
//TODO Auto-generated catch block
e.printStackTrace();
//所有线程执行完成之后,才会跑到这一步
longduration=System.nanoTime()-
out.println(id+"= "+duration);
&&&&推荐文章:
【上篇】【下篇】5589人阅读
为了比较一下ReentrantLock和synchronized的性能,做了一下性能测试:
得出结论:
(1)使用Lock的性能比使用synchronized关键字要提高4~5倍;
(2)使用信号量实现同步的速度大约比synchronized要慢10~20%;
(3)使用atomic包的AtomicInter速度是比Lock要快1一个数量级。
ReentrantLock 类 java.util.concurrent.lock 中的 Lock 框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock 的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。ReentrantLock 类实现了 Lock,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized 的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续)synchronized 块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized 块时,才释放锁。
在查看清单 1 中的代码示例时,可以看到 Lock 和 synchronized 有一点明显的区别 && lock 必须在 finally 块中释放。否则,如果受保护的代码将抛出异常,锁就有可能永远得不到释放!这一点区别看起来可能没什么,但是实际上,它极为重要。忘记在 finally 块中释放锁,可能会在程序中留下一个定时bomb,当有一天bomb爆炸时,您要花费很大力气才有找到源头在哪。而使用同步,JVM 将确保锁会获得自动释放。
& Test的源码
public abstract class Test {
protected S
protected CyclicB
protected int threadN
protected ExecutorS
public Test(String id, CyclicBarrier barrier, long count, int threadNum,
ExecutorService executor) {
this.barrier =
this.count =
this.threadNum = threadN
this.executor =
public void startTest() {
long start = System.currentTimeMillis();
for (int j = 0; j & threadN j++) {
executor.execute(new Thread() {
public void run() {
for (int i = 0; i & i++) {
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
barrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
// 所有线程执行完成之后,才会跑到这一步
long duration = System.currentTimeMillis() -
System.out.println(id + " = " + duration);
protected abstract void test();
测试类ReentreLockTest 源码
&import thread.test.T
public class ReentreLockTest {
private static long COUNT = 1000000;
private static Lock lock = new ReentrantLock();
private static long lockCounter = 0;
private static long syncCounter = 0;
private static long semaCounter = 0;
private static AtomicLong atomicCounter = new AtomicLong(0);
private static Object syncLock = new Object();
private static Semaphore mutex = new Semaphore(1);
public static void testLock(int num, int threadCount) {
static long getLock() {
lock.lock();
return lockC
} finally {
lock.unlock();
static long getSync() {
synchronized (syncLock) {
return syncC
static long getAtom() {
return atomicCounter.get();
static long getSemaphore() throws InterruptedException {
mutex.acquire();
return semaC
} finally {
mutex.release();
static long getLockInc() {
lock.lock();
return ++lockC
} finally {
lock.unlock();
static long getSyncInc() {
synchronized (syncLock) {
return ++syncC
static long getAtomInc() {
return atomicCounter.getAndIncrement();
static class SemaTest extends Test {
public SemaTest(String id, CyclicBarrier barrier, long count,
int threadNum, ExecutorService executor) {
super(id, barrier, count, threadNum, executor);
protected void test() {
getSemaphore();
} catch (InterruptedException e) {
e.printStackTrace();
static class LockTest extends Test {
public LockTest(String id, CyclicBarrier barrier, long count,
int threadNum, ExecutorService executor) {
super(id, barrier, count, threadNum, executor);
protected void test() {
getLock();
static class SyncTest extends Test {
public SyncTest(String id, CyclicBarrier barrier, long count,
int threadNum, ExecutorService executor) {
super(id, barrier, count, threadNum, executor);
protected void test() {
getSync();
static class AtomicTest extends Test {
public AtomicTest(String id, CyclicBarrier barrier, long count,
int threadNum, ExecutorService executor) {
super(id, barrier, count, threadNum, executor);
protected void test() {
getAtom();
public static void test(String id, long count, int threadNum,
ExecutorService executor) {
final CyclicBarrier barrier = new CyclicBarrier(threadNum + 1,
new Thread() {
public void run() {
System.out.println("==============================");
System.out.println("count = " + count + "/t" + "Thread Count = "
+ threadNum);
new LockTest("Lock ", barrier, COUNT, threadNum, executor).startTest();
new SyncTest("Sync ", barrier, COUNT, threadNum, executor).startTest();
new AtomicTest("Atom ", barrier, COUNT, threadNum, executor)
.startTest();
new SemaTest("Sema ", barrier, COUNT, threadNum, executor)
.startTest();
System.out.println("==============================");
public static void main(String[] args) {
for (int i = 1; i & 5; i++) {
ExecutorService executor = Executors.newFixedThreadPool(10 * i);
test("", COUNT * i, 10 * i, executor);
==============================
count = 1000000 Thread Count = 10
==============================
==============================
count = 2000000 Thread Count = 20
==============================
==============================
count = 3000000 Thread Count = 30
==============================
==============================
count = 4000000 Thread Count = 40
==============================
&&相关文章推荐
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:1622678次
积分:11044
积分:11044
排名:第1281名
原创:239篇
转载:46篇
评论:95条
(1)(2)(10)(3)(1)(2)(1)(2)(3)(1)(6)(1)(1)(2)(1)(11)(2)(2)(2)(3)(4)(1)(2)(4)(2)(6)(2)(1)(7)(4)(5)(3)(5)(8)(12)(3)(1)(3)(10)(3)(2)(7)(8)(4)(1)(3)(1)(3)(5)(13)(2)(17)(3)(6)(6)(2)(1)(3)(4)(7)(12)(1)(5)(7)(1)(14)(2)(3)

我要回帖

更多关于 synchronized与lock 的文章

 

随机推荐