java.util.concurrent包下的都是线程安全的

synchronized关键字可以实现同步访问
还有 java.util.concurrent.locks 包下也提供了另一种方式实现同步访问

一、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则必须要用户去手动释放锁 如果没有主动释放锁 就有可能导致出现死锁现象
3)synchronized是悲观锁 Lock是乐观锁
所以性能上来说 Lock性能更好
4)synchronized和lock其实底层都是CAS锁(CAS原子锁)
悲观锁都是乐观锁封装出来的
Java本身是无法实现原子锁的 只是因为Java调用了操作系统的接口
(原子锁是在硬件上实现的 Java是软件 原子锁是需要依赖操作系统和硬件的支持)

二、java.util.concurrent.locks包下常用的类

![4@30I4~NFP%8V00M5~VTUT.jpg

ReadWriteLock和Lock都是接口
1)ReadWriteLock的实现类有ReetrantReadWriteLock,其中ReadLock和WriteLock是ReetrantReadWriteLock的静态内部类
且ReadLock和WriteLock都实现了Lock接口
2)Lock接口常用的实现类就是ReetrantLock类

1 Lock接口中的方法

  1. public interface Lock {
  2. void lock();
  3. void lockInterruptibly() throws InterruptedException;
  4. boolean tryLock();
  5. boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  6. void unlock();
  7. Condition newCondition();
  8. }

lock()、tryLock()、tryLock(long time, TimeUnit unit)、lockInterruptibly()是用来获取锁的
unLock()方法是用来释放锁的
下面的ReentrantLock类实现了Lock接口
ReentrantLock类(可重入锁)
在ReentrantLock中定义了2个静态内部类 一个是NotFairSync 一个是FairSync 分别用来实现非公平锁和公平锁的
所以可以在创建ReentrantLock对象时 通过以下方式来设置锁的公平性:
ReentrantLock lock = new ReentrantLock(true);
//如果参数为true表示为公平锁 为fasle为非公平锁
//默认情况下 如果使用无参构造器 则是非公平锁
另外在ReentrantLock类中定义了很多方法 比如:
1)isFair()
//判断锁是否是公平锁
2)isLocked()
//判断锁是否被任何线程获取了
3)isHeldByCurrentThread()
//判断锁是否被当前线程获取了
4)hasQueuedThreads()
//判断是否有线程在等待该锁
1 lock()方法
平常使用得最多的一个方法 就是用来获取锁 如果锁已被其他线程获取 则进行等待(死循环 也叫自旋

如果使用的是Lock接口下实现类的方法 必须主动去释放锁 而且在发生异常时 不会自动释放锁
因此一般来说 使用Lock必须在try{}catch{}块中进行 并且将释放锁的操作放在finally块中进行 以保证锁一定被被释放 防止死锁的发生
使用方式:

  1. Lock lock = ...;
  2. lock.lock();
  3. try{
  4. //处理任务
  5. }catch(Exception exception){
  6. }finally{
  7. lock.unlock();
  8. //释放锁
  9. }

2 trtLock()、tryLock(long time, TimeUnit unit)
tryLock()方法是有返回值的 它表示用来尝试获取锁
如果获取成功 则返回true 如果获取失败(即锁已被其他线程获取)则返回false
也就说这个方法无论如何都会立即返回 在拿不到锁时不会一直在那等待

tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的
只不过区别在于这个方法在拿不到锁时会等待一定的时间 在时间期限之内如果还拿不到锁 就返回false
如果一开始拿到锁或者在等待期间内拿到了锁 则返回true
time是时间 utit是时间单位
使用方式:

  1. Lock lock = ...;
  2. if(lock.tryLock()){
  3. try{
  4. //处理任务
  5. }catch(Exception exception){
  6. }finally{
  7. lock.unlock();
  8. //释放锁
  9. }
  10. }else{
  11. //如果获取不到 则直接做其他事
  12. }

3 **lockInterruptibly()**
lockInterruptibly()方法比较特殊 当通过这个方法去获取锁时 如果线程正在等待获取锁 则这个线程能够响应中断 即中断线程的等待状态
也就是说 当两个线程同时通过lock.lockInterruptibly()想获取某个锁时 假若此时线程A获取到了锁 而线程B只有在等待
那么对线程B调用threadB.interrupt()方法能够中断线程B的等待过程

注意 当一个线程获取了锁之后 是不会被interrupt()方法中断的
单独调用interrupt()方法不能中断正在运行过程中的线程 只能中断阻塞过程中的线程
因此当通过lockInterruptibly()方法获取某个锁时 如果不能获取到 只有进行等待的情况下 是可以响应中断的
而用synchronized修饰的话 当一个线程处于等待某个锁的状态 是无法被中断的 只有一直等待下去

由于lockInterruptibly()的声明中需要抛出了异常
所以lock.lockInterruptibly()必须放在try块中 或者 在调用lock.Interruptibly()的方法结构上声明抛出InterruptedException

2 ReadWriteLock接口中的方法

  1. public interface ReadWriteLock {
  2. Lock readLock();
  3. Lock writeLock();
  4. }

只有两个方法 一个用来获取读锁 一个用来获取写锁
也就是说将文件的读写操作分开 分成2个锁来分配给线程 从而使得多个线程可以同时进行读操作
下面的ReentrantReadWriteLock实现了ReadWriteLock接口
ReentrantReadWriteLock类
类中包含 ReadLock写锁 和 WriteLock读锁
最核心的两个方法就是readLock()获取写锁和writeLock()获取读锁
不过要注意的是 如果有一个线程已经占用了读锁 则此时其他线程如果要申请写锁 则申请写锁的线程会一直等待释放读锁
如果有一个线程已经占用了写锁 则此时其他线程如果申请写锁或者读锁 则申请的线程会一直等待释放写锁