Java Lock同步锁

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

接下来逐个介绍它们的方法

  • void lock(); 获取这个锁,如果锁同时被另一个线程拥有则发生阻塞
  • void unlock(); 释放这个锁
  • boolean tryLock(); 尝试获得锁而没有发生阻塞;如果成功返回真。这个方法会抢夺可用的锁,即使该锁有公平加锁策略,即使其他线程等很久。
  • boolean tryLock(long time, TimeUnit unit); 尝试获得锁,阻塞时间不会超过给定的值;如果成功返回true。
  • void lockInterruptibly(); 获得锁,但是会不确定地发生阻塞。如果线程被中断,跑出一个InterruptedException异常。
  • Condition newCondition(); 返回一个与该锁相关的条件对象

Lock需要主动的获取锁,并且发生异常时不会主动的释放锁。所以实际应用中,Lock必须在try{}catch{}块中,在finally中释放锁,保证锁一定被释放,不会发生死锁。

接下来尝试用写一个自定义锁,只是实现获取锁和释放锁的部分。

package gemme.thread.lock;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;

public class MyLock implements Lock {

    // 是否持有锁:默认为否
    private boolean isHoldLock = false;

    @Override
    public synchronized void lock() {
        if (isHoldLock) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        isHoldLock = true;
    }

    @Override
    public synchronized void unlock() {
        isHoldLock = false;
        notify();
    }

    @Override
    public void lockInterruptibly() throws InterruptedException {

    }

    @Override
    public boolean tryLock() {
        return false;
    }

    @Override
    public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
        return false;
    }

    @Override
    public Condition newCondition() {
        return null;
    }
}

尝试运行一下这段代码

package gemme.thread;

import gemme.thread.lock.MyLock;

import java.util.concurrent.locks.Lock;

public class UnSafeLock {

    private static int num = 0;

    private static Lock lock = new MyLock();

    public static void add() {
        lock.lock();
        num++;
        lock.unlock();
    }

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread() {
                @Override
                public void run() {
                    add();
                }
            }.start();
        }

        try {
            // 主线程可能会在其他线程还没结束时打印最终结果,所以让主线程先睡100毫秒
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(num);
    }
}

最终结果为

1000

打印的最终结果是预期结果,利用实现了一个synchronized关键字很简单的同步锁。那么尝试运行下面的代码

package gemme.thread.lock;

import java.util.concurrent.locks.Lock;

public class ReentryDemo {

    private static Lock myLock = new MyLock();

    public void methodA() {
        myLock.lock();
        System.out.println("methodA");
        methodB();
        myLock.unlock();
    }

    public void methodB() {
        myLock.lock();
        System.out.println("methodB");
        myLock.unlock();
    }

    public static void main(String[] args) {
        ReentryDemo demo = new ReentryDemo();
        demo.methodA();
    }
}

发现打印出methodA后并没有打印methodB,问题在哪里呢?

methodA

第一次将isHoldLock置为true后,第二次调用lock时调用了wait阻塞,线程就卡在这里了,没办法释放锁,所以上面的方式只能按顺序进行加锁和解锁。
我们对自定义锁MyLock进行优化

public class MyLock implements Lock {

    // 是否持有锁:默认为否
    private boolean isHoldLock = false;

    private Thread holdLockThread = null;

    private int reentryCount = 0;

    /**
     * 同一时刻,能且仅能有一个线程获取到锁,其他线程只能等待线程释放锁之后才能获取到锁
     * */
    @Override
    public synchronized void lock() {
        
        if (isHoldLock && holdLockThread != Thread.currentThread()) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        holdLockThread = Thread.currentThread();
        isHoldLock = true;
        reentryCount++;
    }

    @Override
    public synchronized void unlock() {
        // 判断当前线程是否是持有锁的线程;如果是,重入次数减一
        if (holdLockThread == Thread.currentThread()) {
            reentryCount--;

            if (reentryCount == 0) {
                isHoldLock = false;
                notify();
            }
        }
    }
}

再运行ReentryDemo发现执行结果为:

methodA
methodB

是预期结果,线程不再阻塞。

Lock和synchronized的区别

  • Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现,synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将 unLock()放到finally{} 中;
  • synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;
  • Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;
  • 通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  • Lock可以提高多个线程进行读操作的效率。

你可能感兴趣的:(多线程,Lock)