java多线程-Lock

来源:互联网 发布:网络摄像头连接拾音器 编辑:程序博客网 时间:2024/06/01 09:04

1.synchronize缺陷

      代码块被synchronize修饰时,当一个线程获取了对应的线程锁并执行该代码块时,其他线程只能一直等待,直到获取锁的线程释放锁,而这里得到锁的线程释放锁只有两种情况:

    (1)得到锁的线程执行完了该代码块,然后线程释放线程锁

    (2)线程执行发生异常,此时JVM会让线程自动释放锁

     如果这个得到锁的线程由于要等待IO或者其他原因(如调用了sleep方法)被阻塞了,但是又没有释放锁,其他线程只能干巴巴等待,这非常影响程序执行的效率。因此,就需要有一种机制可以不让等待的线程一直无限期等待下去(比如只等待一段时间或者能够响应中断),通过Lock就可以实现

     再有一个例子:

            当多个线程读写文件时,读写、写写都会发生冲突,但是读读操作不会发生冲突。但是如果采用synchronize关键字来实现同步的话,就会导致一个问题:如果多个线程只是进行读操作,当一个线程正在进行读操作时,其他线程只能等待而无法进行读操作。因此,就需要一种机制来使得多个线程都只是进行读操作时,线程之间不会发生冲突,Lock就可办到。另外,通过Lock可以知道有没有成功获取到锁。这个是synchronize无法办到的。

     

2.synchronize与Lock的区别

      (1)synchronize是Java的关键字,因此是内置特性。Lock是一个类,通过这个类可以实现同步访问

      (2)synchronize不需要用户手动去释放锁:当synchronize方法或代码块执行完后,系统会让线程自动释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放,则会造成死锁现象


3.锁分类

(1)可重入锁

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

        看看如下代码:

class MyClass{    public synchronized void method1()     {        method2();    }         public synchronized void method2()     {             }}
       上述代码中的两个方法都是用了synchronized,加入某一止咳,线程A执行到了method1,此时线程A获取了这个对象的锁,而由于method2也是synchronized方法,加入synchronized不具有可重入性,此时线程A需要重新申请锁。但是这样就会造成一个问题,因为线程A已经持有了该对象的锁,而又在申请该对象的锁,这样线程A会直等待而永远不会得到申请的锁。

      由于synchronized和Lock都具备可重入性,所以不会发生上述现象


(2)可中断锁

        如果某一线程A正在执行锁中的代码,另一线程B正在等待获取该锁,可能由于等待时间过长,B不想再等了,想先去处理其他事情,我们可以让他中断自己或者在别的线程中中断它,这种就是可中断锁。

        synchronized是不可中断锁,Lock是可中断锁

(3)公平锁

        公平锁即尽量以请求锁的顺序来获取锁。比如同时有多个线程在等待同一个锁,当这个锁被释放时,等待时间最久的线程(最先请求的线程)会获得该锁,这就是公平锁。

        synchronized是非公平锁,也就是说无法保证锁的获取是按照请求锁的顺序进行的,这可能导致一些线程永远获取不到锁。

        ReentrantLock和ReentrantReadWriteLock,默认是非公平锁,但是可以设置为公平锁。

        在ReentrantLock中定义了公平和非公平锁两个类

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);        }    }

 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;        }    }
        我们在创建ReentrantLock对象时,可以通过以下方式设置锁的公平性

ReentrantLock lock = new ReentrantLock(true);
         true为公平锁,false为非公平锁。默认情况下,如果使用无参构造器,则是非公平锁。

    public ReentrantLock() {        sync = new NonfairSync();    }    /**     * Creates an instance of {@code ReentrantLock} with the     * given fairness policy.     *     * @param fair {@code true} if this lock should use a fair ordering policy     */    public ReentrantLock(boolean fair) {        sync = fair ? new FairSync() : new NonfairSync();    }
       另外,在ReentrantLock中定义了很多方法:

    public final boolean isFair() {             //用来判断是否是公平锁        return sync instanceof FairSync;    }

    public boolean isLocked() {                //用来判断锁是否已经被获取了        return sync.isLocked();    }
      
    public final boolean hasQueuedThreads() {   //判断是否有线程在等待该锁        return sync.hasQueuedThreads();    }
      ReentrantReadWriteLock也有类似的方法,同样也可以设置为公平锁和非公平锁。注意,ReentrantReadWriteLock并没有实现Lock接口,实现的是ReadWriteLock

(4)读写锁

        读写锁将对一个资源(比如文件)的访问分成了2个锁,一个读锁和一个写锁。正因为有了读写所,才使得多个线程之间的读操作不会发生冲突

3.Lock

      通过代码查看,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,lockInterruptibly,tryLock都是用来获取锁的,unLock是用来释放锁的

       (1)lock()

               使用最多的方法,就是为了获取锁,如果锁已经被其他线程获取,则进行等待

               如果采用Lock,则必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此,一般来说,使用lock必须在try、catch中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被释放,防止死锁的发生。

Lock lock = ...;lock.lock();try{    //处理任务}catch(Exception ex){     }finally{    lock.unlock();   //释放锁}

       (2)tryLock()

               有返回值,用来表示是否获得了锁,如果返回true,则说明获取成功,如果获取失败(锁已经被其他线程获取),则返回false。也就是说,这个方法无论如何都会立即返回,拿不到不会一直等

Lock lock = ...;if(lock.tryLock()){     try     {         //处理任务     }     catch(Exception ex)     {              }     finally     {         lock.unlock();   //释放锁     } }else {    //如果不能获取锁,则直接做其他事情}

       (3)tryLock(long time, TimeUnit unit)

               和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果一开始拿到或者在等待时间内拿到了锁,就返回false


       (4)lockInterruptibly()

               当通过这个方法去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断。其实也就是说两个线程A、B同时通过lock.lockInterruptibly()想获取某个锁时,假如A获取到了锁,而B在等待,那么对B调用threadB.interrput()方法能够中断B线程的等待过程

public void method() throws InterruptedException {    lock.lockInterruptibly();    try     {       //.....    }    finally     {        lock.unlock();    }  }
            注意:当一个线程获取了锁之后,是不会被interrupt()方法中断的。因为单独调用interrupt()方法只能中断阻塞中的线程,而不能中断正在运行的线程。因此当通过lockInterruptibly()获取线程锁时,如果不能获取到,只有在进行等待的状态下,是可以响应中断的。

           而用synchronize修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只能一直等待下去。


4.ReentrantLock

      意思是可重入锁。ReentrantLock是唯一实现了Lock接口的类。

      (1)使用lock()获取锁

public class TestLock {List<Integer> list = new ArrayList<Integer>();public static void main(String[] args) {final TestLock test = new TestLock();new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();}protected void insert(Thread currentThread) {Lock lock = new ReentrantLock();lock.lock();try{System.out.println(currentThread.getName()+" get lock");for(int i=0; i<5;i++)list.add(i);}catch(Exception e){}finally{System.out.println(currentThread.getName()+" release lock");lock.unlock();}}}
         结果为:

Thread-0 get lockThread-1 get lockThread-1 release lockThread-0 release lock
        为何是这样呢?因为各个线程使用的是自己的Lock,那么肯定就起不到同步的作用了,所以将lock改为成员变量即可

public class TestLock {List<Integer> list = new ArrayList<Integer>();Lock lock = new ReentrantLock();public static void main(String[] args) {final TestLock test = new TestLock();new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();}protected void insert(Thread currentThread) {lock.lock();try{System.out.println(currentThread.getName()+" get lock");for(int i=0; i<5;i++)list.add(i);}catch(Exception e){}finally{System.out.println(currentThread.getName()+" release lock");lock.unlock();}}}
           结果为:

Thread-0 get lockThread-0 release lockThread-1 get lockThread-1 release lock

      (2)使用tryLock()获取锁
public class TestLock {static List<Integer> list = new ArrayList<Integer>();Lock lock = new ReentrantLock();public static void main(String[] args) {final TestLock test = new TestLock();new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}}).start();}protected void insert(Thread currentThread) {if(lock.tryLock()){try{System.out.println(currentThread.getName()+" get lock");for(int i=0; i<5;i++)list.add(i);}catch(Exception e){}finally{System.out.println(currentThread.getName()+" release lock");lock.unlock();}}else{System.out.println(currentThread.getName()+" can get lock");}}}
        结果为:

Thread-0 get lockThread-0 release lockThread-1 get lockThread-1 release lock

      (3)使用lockInterruptibly()获取锁
public class TestLock {static List<Integer> list = new ArrayList<Integer>();Lock lock = new ReentrantLock();public static void main(String[] args) {final TestLock test = new TestLock();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {test.insert(Thread.currentThread());}});t1.start();t2.start();try {Thread.sleep(20);}catch (InterruptedException e) {e.printStackTrace();}t2.interrupt();}protected void insert(Thread currentThread)     {try {lock.lockInterruptibly();System.out.println(currentThread.getName() + " get lock");for (int i = 0; i < 5; i++)list.add(i);} catch (InterruptedException e) {e.printStackTrace();}finally {System.out.println(currentThread.getName() + " release lock");lock.unlock();}}}
         也就是说在20ms之后还没有得到锁,那么就interrupt线程。



5.ReadWriteLock

    ReadWriteLock也是一个接口,其中只定义了2个方法:

public interface ReadWriteLock {    /**     * @return the lock used for reading.     */    Lock readLock();     /**     * @return the lock used for writing.     */    Lock writeLock();}
         一个用来获取读锁,一个用来获取写锁。也就是说,将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作。

         ReentrantReadWriteLock是ReadWriteLock接口的一个实现类。加入有多个线程要同时进行读操作的话,先看一下synchronized达到的效果。

public class TestLock {public static void main(String[] args) {final TestLock test = new TestLock();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {test.get(Thread.currentThread());}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {test.get(Thread.currentThread());}});t1.start();t2.start();try {Thread.sleep(20);}catch (InterruptedException e) {e.printStackTrace();}t2.interrupt();}synchronized protected void get(Thread currentThread) {long start = System.currentTimeMillis();while(System.currentTimeMillis()-start<=1)System.out.println(currentThread.getName()+"正在进行读操作");System.out.println(currentThread.getName()+"完成读操作");}}
              结果为:

Thread-0正在进行读操作Thread-0正在进行读操作Thread-0正在进行读操作Thread-0正在进行读操作Thread-0完成读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1完成读操作
             也就是说,知道thread1读操作完成之后,才会执行thread2的读操作

             下面使用读写锁:

public class TestLock {ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();public static void main(String[] args) {final TestLock test = new TestLock();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {test.get(Thread.currentThread());}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {test.get(Thread.currentThread());}});t1.start();t2.start();try {Thread.sleep(20);}catch (InterruptedException e) {e.printStackTrace();}t2.interrupt();}protected void get(Thread currentThread) {rwl.readLock().lock();try{long start = System.currentTimeMillis();while(System.currentTimeMillis()-start<=1)System.out.println(currentThread.getName()+"正在进行读操作");System.out.println(currentThread.getName()+"完成读操作");}catch(Exception e){}finally{rwl.readLock().unlock();}}}
            结果为:

Thread-1正在进行读操作Thread-0正在进行读操作Thread-0正在进行读操作Thread-0正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-0正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1正在进行读操作Thread-1完成读操作Thread-0正在进行读操作Thread-0完成读操作
            说明两个线程可以同时进行读操作,这样就大大提高了读操作的效率。

            注意:

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


6.Lock与synchronized区别

    (1)Lock是一个接口,而synchronized是java中的关键字

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

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

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

            从性能上来说,如果资源竞争不激烈,两者的性能差不多,但是当有大量线程同时竞争时,此时Lock的性能要远远优于synchronized
















0 0
原创粉丝点击