图灵学院:Java高并发之Lock

concurrent   lock




从Java5之后,在java.util.concurrent.locks包下提供了另外一种方式来实现同步访问,那就是Lock。讨论lock前先了解下synchronized


一:synchronized


  synchronized是java中的一个关键字,也就是说是Java语言内置的特性,如果一个代码块被synchronized修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁

  释放的两种情况

  

  1)获取锁的线程执行完了该代码块,然后线程释放对锁的占有,在次期间该对象不能被释放或者操作。

  2) 线程发生异常,JVM自动会释放锁,无需用户处理。

  

  如果一个对象被加了synchronized,那么不管线程是读或者写操作都不可以操作。我们来看一段代码加深下synchronized的理解

public class SynchronizedTest {
     public static void main(String[] args) {
         Thread a = new ThreadRunC();
         a.start();
         try {
             Thread.sleep(1000);
         } catch (InterruptedException e) {
             e.printStackTrace();
         }
         System.out.println("我是主线程 不加同步"+C.C);
         synchronized (C.C) {
             System.out.println("我是主线程:" + C.C);
         }
     }
 }
 class ThreadRunC extends Thread {
     public void run() {
         synchronized (C.C) {
             System.out.println("我要开始执行任务C。。。。" + Thread.currentThread().getName());
             try {
                 Thread.sleep(5000);
             } catch (InterruptedException e) {
                 e.printStackTrace();
             }
             System.out.println("我在执行任务结束了C。。。。" + Thread.currentThread().getName());
         }
     }
 }
 class C {
     static Integer C = new Integer(1);
 }

 

 结果:

 

 我要开始执行任务C。。。。Thread-0
 我是主线程 不加同步1
 我在执行任务结束了C。。。。Thread-0
 我是主线程:1

 

 有此可见C.C对象在进入子线程后休眠了5s,那么主线程的synchronized (C.C)一直处于等待状态,直到子线程结束,它才执行。

 

 二:Lock


Lock不是Java语言内置的,synchronized是Java语言的关键字,因此是内置特性。Lock(jdk1.5以后)是一个类(实际是接口),通过这个类可以实现同步访问,它和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。

public interface Lock {
    void lock(); //获取锁。
    void lockInterruptibly() throws InterruptedException;  //如果当前线程未被中断,则获取锁。
    boolean tryLock(); //    仅在调用时锁为空闲状态才获取该锁。
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
    void unlock();//     释放锁。
    Condition newCondition(); //返回绑定到此 Lock 实例的新 Condition 实例。
}


1:ReentrantLock  


ReentrantLock,意思是“可重入锁”,ReentrantLock由最近成功获取锁,还没有释放的线程所拥有,当锁被另一个线程拥有时,调用lock的线程可以成功获取锁。如果锁已经被当前线程拥有,当前线程会立即返回。此类的构造方法提供一个可选的公平参数。默认是非公平锁

public ReentrantLock(boolean fair) {  
    sync = fair ? new FairSync() : new NonfairSync();  
}

公平与非公平有何区别,所谓公平就是严格按照FIFO顺序获取锁,非公平安全由程序员自己设计,比如可以按优先级,也可以按运行次数等规则来选择

公平加锁代码

/**
     * Sync object for fair locks
     */
    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;
        final void lock() {
            acquire(1);
        }
        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();   
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

   

   1、首先判断锁有没有被持有,如果被持有,就判断持有锁的线程是不是当前线程,如果不是就啥也不做,返回获取失败,如果是就增加重入数,返回成功获取;

   2、如果锁没有被任何线程持有(c==0),首先判断当前结点前面是否还有线程在排除等待锁,如果有,直接返回获取失败,否则将锁持有数设为acquires,一般为1,然后设置锁的拥有者为当前线程,成功获取。

     

    非公平加锁

 static final class NonfairSync extends Sync {
            private static final long serialVersionUID = 7316153563782823691L;
            /**
             * Performs lock.  Try immediate barge, backing up to normal
             * acquire on failure.
             */
            final void lock() {
                if (compareAndSetState(0, 1))
                    setExclusiveOwnerThread(Thread.currentThread());
                else
                    acquire(1);
            }
            protected final boolean tryAcquire(int acquires) {
                return nonfairTryAcquire(acquires);
            }
        }
        
  
  final boolean nonfairTryAcquire(int acquires) {
             final Thread current = Thread.currentThread();
             int c = getState();
             if (c == 0) {
                 if (compareAndSetState(0, acquires)) {
                     setExclusiveOwnerThread(current);
                     return true;
                 }
             }
             else if (current == getExclusiveOwnerThread()) {
                 int nextc = c + acquires;
                 if (nextc < 0) // overflow
                     throw new Error("Maximum lock count exceeded");
                 setState(nextc);
                 return true;
             }
             return false;
         }

代码几乎一模一样,唯一不同的是在知道锁持有数为0时,直接将当前线程设置为锁的持有者,这一点和公平版本的tryAcquire是有区别的,也就是说非公平机制采用的是抢占式模型。

2.ReadWriteLock


public interface ReadWriteLock {
    /**
     * Returns the lock used for reading.
     *
     * @return the lock used for reading.
     */
    Lock readLock();
 
    /**
     * Returns the lock used for writing.
     *
     * @return the lock used for writing.
     */
    Lock writeLock();
}


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

3 ReentrantReadWriteLock

  ReentrantReadWriteLock里面提供了很多丰富的方法,不过最主要的有两个方法:readLock()和writeLock()用来获取读锁和写锁。实现了ReadWriteLock接口

 

 测试代码

public class LockRWDemo {
     static   ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
     public static void main(String[] args) {
         Thread a = new ThreadRunW();
         Thread b = new ThreadRunW();
         a.start();
         b.start();
     }
    
 }
 class ThreadRunR extends Thread {
    
     public void run() {
         LockRWDemo.rwl.readLock().lock();;
         try {
             long start = System.currentTimeMillis();
             
             while(System.currentTimeMillis() - start <= 1) {
                 System.out.println(Thread.currentThread().getName()+"正在进行读操作");
             }
             System.out.println(Thread.currentThread().getName()+"读操作完毕");
         } finally {
             LockRWDemo.rwl.readLock().unlock();
         }
       
     }
 }

       读的操作是两个线程交替的进行的,这样就大大提升了读操作的效率。

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

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

 

class ThreadRunW extends Thread {
    
     public void run() {
         LockRWDemo.rwl.writeLock().lock();;
         try {
             long start = System.currentTimeMillis();
             
             while(System.currentTimeMillis() - start <= 1) {
                 System.out.println(Thread.currentThread().getName()+"正在进行写操作");
             }
             System.out.println(Thread.currentThread().getName()+"写操作完毕");
         } finally {
             LockRWDemo.rwl.writeLock().unlock();
         }
       
     }
 }

 此代码的结果是只能等一个线程结束了才能执行另外一个。

 

 

三:Lock和synchronized区别和选择


   总结来说,Lock和synchronized有以下几点不同:


    1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;


    2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;


    3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;


    4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。


    5)Lock可以提高多个线程进行读操作的效率。

猜你喜欢

转载自blog.csdn.net/qq_42135428/article/details/80322947