Java 并发:原子类

时间:2022-02-25 18:01:09

相关文章:
1.原子类 ,锁
http://blog.csdn.net/youyou1543724847/article/details/52735510

2.多线程相关的

3.线程安全的集合
http://blog.csdn.net/youyou1543724847/article/details/52734876

4.ThreadLocal
http://blog.csdn.net/youyou1543724847/article/details/52460852

5.并发:其他(如volatile,原子类,不变类)
http://blog.csdn.net/youyou1543724847/article/details/52735829

概述:
关于JAVA原生的并发相关的,差不多都在java.util.concurent包下。里面的东西分成如下几大类:
1.原子类
2.锁相关的
3.多线程相关的
4.线程安全的集合,关于线程安全的集合,参考 http://blog.csdn.net/youyou1543724847/article/details/52734876
5.ThreadLocal
6.并发编程(如volatile,原子类,不变类)

下面主要讲述1,2,3,5,6

参考文章:
http://blog.csdn.net/jackfrued/article/details/44499227

原子类

Java 5中的java.util.concurrent包下面有一个atomic子包,其中有几个以Atomic打头的类,例如AtomicInteger和AtomicLong。它们利用了现代处理器的特性,可以用非阻塞的方式完成原子操作。
主要有:
AtomicBoolean
AtomicInteger
AtomicIntegerArray
AtomicIntegerFieldUpdater
AtomicLong
AtomicLongArray
AtomicLongFieldUpdater
AtomicMarkableReference
AtomicReference
AtomicReferenceArray
AtomicReferenceFieldUpdater
AtomicStampedReference
DoubleAccumulator
DoubleAdder
LongAccumulator
LongAdder

AtomicBoolean

定义:

public class AtomicBoolean implements java.io.Serializable {
private static final long serialVersionUID = 4654671469794556979L;
// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicBoolean.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

/**
* Creates a new {@code AtomicBoolean} with the given initial value.
*
* @param initialValue the initial value
*/

public AtomicBoolean(boolean initialValue) {
value = initialValue ? 1 : 0;
}

主要方法:
Java 并发:原子类

get,set方法因为不依赖于当前值,所以直接可以进行操作(有value的volatile保证可见性)

    public final boolean get() {
return value != 0;
}
public final void set(boolean newValue) {
value = newValue ? 1 : 0;
}

对于依赖当前值的操作,则通过unsafe来进行操作

    public final boolean compareAndSet(boolean expect, boolean update) {
int e = expect ? 1 : 0;
int u = update ? 1 : 0;
return unsafe.compareAndSwapInt(this, valueOffset, e, u);
}
//boolean底层还是通过int来实现的,所以,这里通过设置的int型的值


public final boolean getAndSet(boolean newValue) {
boolean prev;
do {
prev = get();
} while (!compareAndSet(prev, newValue));
return prev;
}

AtomicInteger

public class AtomicInteger extends Number implements java.io.Serializable {
private static final long serialVersionUID = 6214790243416807050L;

// setup to use Unsafe.compareAndSwapInt for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicInteger.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile int value;

/**
* Creates a new AtomicInteger with the given initial value.
*
* @param initialValue the initial value
*/

public AtomicInteger(int initialValue) {
value = initialValue;
}

基本的结构和AtomicBoolean相同,volatile的value,get/set直接操作value的值

    public final int get() {
return value;
}

/**
* Sets to the given value.
*
* @param newValue the new value
*/

public final void set(int newValue) {
value = newValue;
}
    public final int getAndIncrement() {
return unsafe.getAndAddInt(this, valueOffset, 1);
}

/**
* Atomically decrements by one the current value.
*
* @return the previous value
*/

public final int getAndDecrement() {
return unsafe.getAndAddInt(this, valueOffset, -1);
}

其他的操作都是直接通过unsafe操作的,没什么大多复杂的实现

AtomicLong

public class AtomicLong extends Number implements java.io.Serializable {
private static final long serialVersionUID = 1927816293512124184L;

// setup to use Unsafe.compareAndSwapLong for updates
private static final Unsafe unsafe = Unsafe.getUnsafe();
private static final long valueOffset;

/**
* Records whether the underlying JVM supports lockless
* compareAndSwap for longs. While the Unsafe.compareAndSwapLong
* method works in either case, some constructions should be
* handled at Java level to avoid locking user-visible locks.
*/

static final boolean VM_SUPPORTS_LONG_CAS = VMSupportsCS8();

/**
* Returns whether underlying JVM supports lockless CompareAndSet
* for longs. Called only once and cached in VM_SUPPORTS_LONG_CAS.
*/

private static native boolean VMSupportsCS8();

static {
try {
valueOffset = unsafe.objectFieldOffset
(AtomicLong.class.getDeclaredField("value"));
} catch (Exception ex) { throw new Error(ex); }
}

private volatile long value;

和atomicInteger不同的是,atomicLong因为是8字节长度的,(底层机器宽度可能是4个字节长度的,因此,还需要做另外的通过)

其他和atomicInteger差不多

使用案例:
Java 并发:原子类

锁相关

参考文献:
http://www.cnblogs.com/dolphin0520/p/3923167.html

一.synchronized的缺陷

synchronized是java中的一个关键字,也就是说是Java语言内置的特性。那么为什么会出现Lock呢?
  在上面一篇文章中,我们了解到如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
  2)线程执行发生异常,此时JVM会让线程自动释放锁。
  那么如果这个获取锁的线程由于要等待IO或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
  因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过Lock就可以办到。
  再举个例子:当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
  但是采用synchronized关键字来实现同步的话,就会导致一个问题:
  如果多个线程都只是进行读操作,所以当一个线程在进行读操作时,其他线程只能等待无法进行读操作。
  因此就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,通过Lock就可以办到。
  另外,通过Lock可以知道线程有没有成功获取到锁。这个是synchronized无法办到的。
  总结一下,也就是说Lock提供了比synchronized更多的功能。但是要注意以下几点:
  1)Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问;
  2)Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

二.锁的相关概念介绍

1.可重入锁

  如果锁具备可重入性,则称作为可重入锁。像synchronized和ReentrantLock都是可重入锁,可重入性在我看来实际上表明了锁的分配机制:基于线程的分配,而不是基于方法调用的分配。举个简单的例子,当一个线程执行到某个synchronized方法时,比如说method1,而在method1中会调用另外一个synchronized方法method2,此时线程不必重新去申请锁,而是可以直接执行方法method2。
Java 并发:原子类

上述代码中的两个方法method1和method2都用synchronized修饰了,假如某一时刻,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,假如synchronized不具备可重入性,此时线程A需要重新申请锁。但是这就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请获取该对象的锁,这样就会线程A一直等待永远不会获取到的锁。
而由于synchronized和Lock都具备可重入性,所以不会发生上述现象。

机制:每个锁都关联一个请求计数器和一个占有他的线程,当请求计数器为0时,这个锁可以被认为是unhled的,当一个线程请求一个unheld的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线程再次请求这个锁时,请求计数器就会增加,当该线程退出syncronized块时,计数器减1,当计数器为0时,锁被释放。

2.可中断锁

  可中断锁:顾名思义,就是可以相应中断的锁。
  在Java中,synchronized就不是可中断锁,而Lock是可中断锁。
  如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,线程B不想等待了,想先处理其他事情,我们可以让它中断自己或者在别的线程中中断它,这种就是可中断锁。
  在前面演示lockInterruptibly()的用法时已经体现了Lock的可中断性。

3.公平锁

  公平锁即尽量以请求锁的顺序来获取锁。比如同是有多个线程在等待一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该所,这种就是公平锁。
  非公平锁即无法保证锁的获取是按照请求锁的顺序进行的。这样就可能导致某个或者一些线程永远获取不到锁。
  在Java中,synchronized就是非公平锁,它无法保证等待的线程获取锁的顺序。
  而对于ReentrantLock和ReentrantReadWriteLock,它默认情况下是非公平锁,但是可以设置为公平锁。
我们可以在创建ReentrantLock对象时,通过以下方式来设置锁的公平性:
ReentrantLock lock = new ReentrantLock(true); 如果参数为true表示为公平锁,为fasle为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。
另外在ReentrantLock类中定义了很多方法,比如:
  isFair() //判断锁是否是公平锁
  isLocked() //判断锁是否被任何线程获取了
  isHeldByCurrentThread() //判断锁是否被当前线程获取了
  hasQueuedThreads() //判断是否有线程在等待该锁
  在ReentrantReadWriteLock中也有类似的方法,同样也可以设置为公平锁和非公平锁。不过要记住,ReentrantReadWriteLock并未实现Lock接口,它实现的是ReadWriteLock接口。

4.读写锁

  读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。
  正因为有了读写锁,才使得多个线程之间的读操作不会发生冲突。
  ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
  可以通过readLock()获取读锁,通过writeLock()获取写锁

5.偏向锁

Java偏向锁(Biased Locking)是Java6引入的一项多线程优化。它通过消除资源无竞争情况下的同步原语,进一步提高了程序的运行性能。
偏向锁,顾名思义,它会偏向于第一个访问锁的线程,如果在接下来的运行过程中,该锁没有被其他的线程访问,则持有偏向锁的线程将永远不需要触发同步。
如果在运行过程中,遇到了其他线程抢占锁,则持有偏向锁的线程会被挂起,JVM会尝试消除它身上的偏向锁,将锁恢复到标准的轻量级锁。(偏向锁只能在单线程下起作用)
因此 流程是这样的 偏向锁->轻量级锁->重量级锁

锁存在Java对象头里。如果对象是数组类型,则虚拟机用3个Word(字宽)存储对象头,如果对象是非数组类型,则用2字宽存储对象头。在32位虚拟机中,一字宽等于四字节,即32bit。
机制:每个锁都关联一个请求计数器和一个占有他的线程,当请求计数器为0时,这个锁可以被认为是unhled的,当一个线程请求一个unheld的锁时,JVM记录锁的拥有者,并把锁的请求计数加1,如果同一个线程再次请求这个锁时,请求计数器就会增加,当该线程退出syncronized块时,计数器减1,当计数器为0时,锁被释放(这就保证了锁是可重入的,不会发生死锁的情况)。

偏向锁,简单的讲,就是在锁对象的对象头中有个ThreaddId字段,这个字段如果是空的,第一次获取锁的时候,就将自身的ThreadId写入到锁的ThreadId字段内,将锁头内的是否偏向锁的状态位置1.这样下次获取锁的时候,直接检查ThreadId是否和自身线程Id一致,如果一致,则认为当前线程已经获取了锁,因此不需再次获取锁,略过了轻量级锁和重量级锁的加锁阶段。提高了效率。
Java 并发:原子类

但是偏向锁也有一个问题,就是当锁有竞争关系的时候,需要解除偏向锁,使锁进入竞争的状态。
具体图片如下:
Java 并发:原子类
Java 并发:原子类

偏向锁的抢占,其实就是两个进程对锁的抢占,在synchrnized锁下表现为轻量锁方式进行抢占
Java 并发:原子类

注意这时只是偏向锁的释放,之后会进入到轻量级锁阶段,两个线程进入锁竞争状态,一个具体例子可以参考synchronized锁机制。
注:synchronized锁流程如下(转自http://xly1981.iteye.com/blog/1766224):
第一步,检查MarkWord里面是不是放的自己的ThreadId ,如果是,表示当前线程是处于 “偏向锁”
第二步,如果MarkWord不是自己的ThreadId,锁升级,这时候,用CAS来执行切换,新的线程根据MarkWord里面现有的ThreadId,通知之前线程暂停,之前线程将Markword的内容置为空。
第三步,两个线程都把对象的HashCode复制到自己新建的用于存储锁的记录空间,接着开始通过CAS操作,把共享对象的MarKword的内容修改为自己新建的记录空间的地址的方式竞争MarkWord,
第四步,第三步中成功执行CAS的获得资源,失败的则进入自旋
第五步,自旋的线程在自旋过程中,成功获得资源(即之前获的资源的线程执行完成并释放了共享资源),则整个状态依然处于 轻量级锁的状态,如果自旋失败
第六步,进入重量级锁的状态,这个时候,自旋的线程进行阻塞,等待之前线程执行完成并唤醒自己

总结:
偏向锁,其实是无锁竞争下可重入锁的简单实现

6.乐观锁,悲观锁

悲观锁,正如其名,它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

一个典型的倚赖数据库的悲观锁调用:

select * from account where name=”Erica” for update

这条sql 语句锁定了account 表中所有符合检索条件(name=”Erica”)的记录。
本次事务提交之前(事务提交时会释放事务过程中的锁),外界无法修改这些记录。

乐观锁(Optimistic Locking)相对悲观锁而言,乐观锁机制采取了更加宽松的加锁机制。悲观锁大多数情况下依靠数据库的锁机制实现,以保证操作最大程度的独占性。但随之而来的就是数据库性能的大量开销,特别是对长事务而言,这样的开销往往无法承受。如一个金融系统,当某个操作员读取用户的数据,并在读出的用户数据的基础上进行修改时(如更改用户帐户余额),如果采用悲观锁机制,也就意味着整个操作过程中(从操作员读出数据、开始修改直至提交修改结果的全过程,甚至还包括操作员中途去煮咖啡的时间),数据库记录始终处于加锁状态,可以想见,如果面对几百上千个并发,这样的情况将导致怎样的后果。

乐观锁机制在一定程度上解决了这个问题。乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。

读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

对于上面修改用户帐户信息的例子而言,假设数据库中帐户信息表中有一version字段,当前值为1;而当前帐户余额字段(balance)为 1001Aversion=1 50( 100 50)。

2 在操作员A操作的过程中,操作员B也读入此用户信息(version=1),并
从其帐户余额中扣除 20 100-$20)。

3 操作员A完成了修改工作,将数据版本号加一(version=2),连同帐户扣
除后余额(balance=$50),提交至数据库更新,此时由于提交数据版本大
于数据库记录当前版本,数据被更新,数据库记录version更新为2。

4 操作员B完成了操作,也将版本号加一(version=2)试图向数据库提交数
据(balance=$80),但此时比对数据库记录版本时发现,操作员B提交的数据版本号为2,数据库记录当前版本也为2,不满足“提交版本必须大于记录当前版本才能执行更新“的乐观锁策略,因此,操作员B 的提交被驳回。这样,就避免了操作员B 用基于version=1 的旧数据修改的结果覆盖操作员A的操作结果的可能。

从上面的例子可以看出,乐观锁机制避免了长事务中的数据库加锁开销(操作员A和操作员B操作过程中,都没有对数据库数据加锁),大大提升了大并发量下的系统整体性能表现。

需要注意的是,乐观锁机制往往基于系统中的数据存储逻辑,因此也具备一定的局限性,如在上例中,由于乐观锁机制是在我们的系统中实现,来自外部系统的用户余额更新操作不受我们系统的控制,因此可能会造成脏数据被更新到数据库中。在系统设计阶段,我们应该充分考虑到这些情况出现的可能性,并进行相应调整(如

将乐观锁策略在数据库存储过程中实现,对外只开放基于此存储过程的数据更新途径,而不是将数据库表直接对外公开)。

三.java.util.concurrent.locks包下常用的类和接口

Lock

Java 并发:原子类

下面来逐个讲述Lock接口中每个方法的使用,lock()、tryLock()、tryLock(long time, TimeUnit unit)和lockInterruptibly()是用来获取锁的。unLock()方法是用来释放锁的。newCondition()这个方法暂且不在此讲述,会在后面的线程协作一文中讲述。

  在Lock中声明了四个方法来获取锁,那么这四个方法有何区别呢?
  lock()方法:是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。
  由于在前面讲到如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。通常使用Lock来进行同步的话,是以下面这种形式去使用的:
  Java 并发:原子类
tryLock()方法:是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,也就说这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
  所以,一般情况下通过tryLock来获取锁时是这样使用的:
Java 并发:原子类

lockInterruptibly():方法比较特殊,当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。也就使说,当两个线程同时通过lock.lockInterruptibly()想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程。
由于lockInterruptibly()的声明中抛出了异常,所以lock.lockInterruptibly()必须放在try块中或者在调用lockInterruptibly()的方法外声明抛出InterruptedException。
  因此lockInterruptibly()一般的使用形式如下:
  Java 并发:原子类
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为本身在前面的文章中讲过单独调用interrupt()方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。
  因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。(即如果没有获取到锁,则进入等待,但是处于等待的线程可以通过interrupt()方法唤醒)
  而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。

2.ReentrantLock

ReentrantLock,意思是“可重入锁”,关于可重入锁的概念在下一节讲述。ReentrantLock是唯一实现了Lock接口的类,并且ReentrantLock提供了更多的方法。下面通过一些实例看具体看一下如何使用ReentrantLock。
Java 并发:原子类

Java 并发:原子类

例二:tryLock()的使用
Java 并发:原子类
Java 并发:原子类

例三:lockInterruptibly()响应中断的使用方法:
Java 并发:原子类

Java 并发:原子类

Java 并发:原子类

3.ReadWriteLock

ReadWriteLock也是一个接口,在它里面只定义了两个方法:

 一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。下面的ReentrantReadWriteLock实现了ReadWriteLock接口。

4.ReentrantReadWriteLock

  ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。
  下面通过几个例子来看一下ReentrantReadWriteLock具体用法。
  假如有多个线程要同时进行读操作的话,先看一下synchronized达到的效果:
  

public class Test {
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();

public static void main(String[] args) {
final Test test = new Test();

new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();

new Thread(){
public void run() {
test.get(Thread.currentThread());
};
}.start();

}

public synchronized void get(Thread thread) {
long start = System.currentTimeMillis();
while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"读操作完毕");
}
}

Java 并发:原子类

而改成用读写锁的话:

  public void get(Thread thread) {
rwl.readLock().lock();
try {
long start = System.currentTimeMillis();

while(System.currentTimeMillis() - start <= 1) {
System.out.println(thread.getName()+"正在进行读操作");
}
System.out.println(thread.getName()+"读操作完毕");
} finally {
rwl.readLock().unlock();
}
}

Java 并发:原子类

不过要注意的是,如果有一个线程已经占用了读锁,则此时其他线程如果要申请写锁,则申请写锁的线程会一直等待释放读锁。

  如果有一个线程已经占用了写锁,则此时其他线程如果申请写锁或者读锁,则申请的线程会一直等待释放写锁。

5.java8新增的乐观锁StampedLock

java8新的API,用户补充reenteredlock(对于reenteredlock来说,如果读线程非常多,则有可能导致写请求饥饿)

StampedLock有三种模式,如果获取锁成功,则会返回一个时间戳,该时间戳在后续的释放的时候必须当参数传入。

写模式: writeLock()
读模式: readLock()
乐观读模式: tryOptimisticRead() 返回非0的时间戳当且仅当这个锁当前不处于写模式下。如果从锁获取到调用validate(long) 方法时,该锁都没有被写请求获取过(即判断这段时间内读取的数据是否有效)。This mode can be thought of as an extremely weak version of a read-lock, that can be broken by a writer at any time。该模式用于代码非常短,且应用对吞吐量有很高的需求。

同时,该类支持锁的升级: tryConvertToWriteLock(long) attempts to “upgrade” a mode, returning a valid write stamp 如果满足这3个条件中的一个:
(1) already in writing mode
(2) in reading mode and there are no other readers or
(3) in optimistic mode and the lock is available.
The forms of these methods are designed to help reduce some of the code bloat that otherwise occurs in retry-based designs.

另外,注意:这个锁不是可重入的。 They are not reentrant, so locked bodies should not call other unknown methods that may try to re-acquire locks (although you may pass a stamp to other methods that can use or convert it).

6.Lock和synchronized的选择
  总结来说,Lock和synchronized有以下几点不同:
  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;
  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  5)Lock可以提高多个线程进行读操作的效率。
  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。