synchronized和重入锁ReentrantLock对比

197 阅读3分钟

synchronized局限性

  • 无法中断一个正在获取锁的线程

当一个线程想获取已经被其他线程持有的锁时,就会发生堵塞,假设已经持有锁的线程一直不释放锁,那么线程就会一直等待下去。

  • 无法指定获得锁的等待时间

相同点

  • 独占锁:一次只允许一个线程访问
  • 可重入锁: 一个线程可重复获得自己已获得锁,不会发生死锁。简单来说,递归的时候不会发生死锁

不同点

  • Lock不是java内置的,synchronized是JVM内置的,因此是内置特性。
  • 释放锁的方式:
  1. Lock 必须要在finally中手动释放锁
  2. synchronized会根据锁区域代码自动执行完毕,或者发生异常,JVM会自动释放锁
  • 公平: Lock是可公平可不公平锁,synchronized是不公平锁

ReentrantLock

1.中断响应

线程在等待锁的过程中,程序可以根据需要取消对锁的请求。在对锁的请求中,统一使用lockInterruptibly()方法。这是一个可以对中断进行响应的锁请求动作,即在等待锁的过程中,可以响应中断。

public class IntLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;
    /**
     * 控制加锁顺序,方便构造死锁
     * @param lock
     */
    public IntLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {
        try {
            if (lock == 1) {
                lock1.lockInterruptibly();
                try{
                    Thread.sleep(500);
                }catch(InterruptedException e){}
                lock2.lockInterruptibly();
            } else {
                lock2.lockInterruptibly();
                try{
                    Thread.sleep(500);
                }catch(InterruptedException e){}
                lock1.lockInterruptibly();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock1.isHeldByCurrentThread())
                lock1.unlock();
            if (lock2.isHeldByCurrentThread())
                lock2.unlock();
            System.out.println(Thread.currentThread().getId()+":线程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        IntLock r1 = new IntLock(1);
        IntLock r2 = new IntLock(2);
        Thread t1 = new Thread(r1);
        Thread t2 = new Thread(r2);
        t1.start();t2.start();
        Thread.sleep(1000);
        //中断其中一个线程
        t2.interrupt();
    }
}

线程1和12启动后,t1先占用lock1,再占用lock2; 12先占用lok2,再占用lock1.因此,很容易形成t1和12之间的相互等待.在主线程main中处于休眠状态时,这两个线程处于死锁的状态。然后由于t2线程被中断,故t2会放弃对lock1的申请,同时释放已获得的lock2。这个操作导致tl线程可 以顺利得到lock2而继续执行下去。

2.限时等待

public class TimeLock implements Runnable{
	public static ReentrantLock lock=new ReentrantLock();
	@Override
	public void run() {
		try {
			if(lock.tryLock(5, TimeUnit.SECONDS)){
				Thread.sleep(6000);
			}else{
				System.out.println("get lock failed");
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{lock.unlock();}
	}
	public static void main(String[] args) {
		TimeLock tl=new TimeLock();
		Thread t1=new Thread(tl);
		Thread t2=new Thread(tl);
		t1.start();
		t2.start();
	}
}

当线程无法在指定时间内获得锁后,请求锁失败,当然trylock()方法也可以不带参数运行,在这种情况下,当前线程会尝试获得锁,如果锁并没有被其他锁只能用,则申请锁成功,斗则不会进行等待,立即返回false。

3.公平锁

g公平锁不会产生饥饿现象,只要排队一定可以等到锁,但是需要维护一个有序队列,因此实现成本较高,性能低下。

public class FairLock implements Runnable {
    public static ReentrantLock fairLock = new ReentrantLock();

    @Override
    public void run() {
        while(true){
        try{
            fairLock.lock();
            System.out.println(Thread.currentThread().getName()+" 获得锁");
        }finally{
            fairLock.unlock();
        }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        FairLock r1 = new FairLock();
        Thread t1=new Thread(r1,"Thread_t1");
        Thread t2=new Thread(r1,"Thread_t2");
        t1.start();t2.start();
    }
}