1.问题背景
在多线程中,经常会遇到多个线程访问一个共享资源的问题,为了保证数据的一致性,就引入了一种锁的机制。线程想要访问共享资源必须要拿到锁,拿到锁的线程可以访问共享资源,访问结束后会释放锁,这样其他线程才有机会拿到锁进而访问共享资源。
2.锁的分类
-
可重入锁
同一个线程在同步方法中可以执行另一个同步方法,而不需要重新获得锁 -
可中断锁
在等待锁的过程中可中断 -
公平锁
按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利 -
读写锁
多资源的读取和写入分开处理,读的时候可以多个线程一起读,写的时候必须同步地写
3.锁机制
当某线程首次申请锁时,系统将会记录锁的占有者,并将计数器置为1,当同一个线程再次请求该锁时,计数器加1,当该线程退出一个同步块时,计数器减1,直到计数器的值为0时,其他线程才有机会申请锁
4.死锁是如何产生的
线程1持有A资源,同时请求获取B资源,但此时这个B资源已经被线程2占有了,同时线程2请求A资源。
死锁demo
public class DeadLockDemo {
class Thread1 extends Thread {
private Object obj1;
private Object obj2;
public Thread1(Object obj1, Object obj2, String name) {
super(name);
this.obj1 = obj1;
this.obj2 = obj2;
}
public void run() {
System.out.println(getName() + "开始执行");
System.out.println(getName() + "正在申请资源A...");
synchronized (obj1) {
System.out.println(getName() + ":申请资源A成功");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName() + "正在申请资源B...");
synchronized (obj2) {
System.out.println(getName() + ":申请资源B成功");
}
}
System.out.println(getName() + "执行结束");
}
}
class Thread2 extends Thread {
private Object obj1;
private Object obj2;
public Thread2(Object obj1, Object obj2, String name) {
super(name);
this.obj1 = obj1;
this.obj2 = obj2;
}
public void run() {
System.out.println(getName() + "开始执行");
System.out.println(getName() + "正在申请资源B...");
synchronized (obj2) {
System.out.println(getName() + ":申请资源B成功");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(getName() + "正在申请资源A...");
synchronized (obj1) {
System.out.println(getName() + ":申请资源A成功");
}
}
System.out.println(getName() + "执行结束");
}
}
public static void main(String[] args) {
DeadLockDemo demo = new DeadLockDemo();
Object obj1 = new Object(); // 资源A
Object obj2 = new Object(); // 资源B
Thread1 t1 = demo.new Thread1(obj1, obj2, "线程1");
Thread2 t2 = demo.new Thread2(obj1, obj2, "线程2");
t1.start();
t2.start();
}
}
运行结果
线程1开始执行
线程1正在申请资源A...
线程1:申请资源A成功
线程2开始执行
线程2正在申请资源B...
线程2:申请资源B成功
线程2正在申请资源A...
线程1正在申请资源B...
5.下面将用代码来介绍锁的使用
下文中将要用到的锁均采用ReentrantLock这个类
5.1 一个简单的锁
在上一篇介绍synchronized关键字时,3.2给出的程序其实就是一个用synchronized实现的简单的锁,下面将给出通过Lock来实现锁
public class LockDemo {
private static ReentrantLock lock = new ReentrantLock();
private volatile static int number;
private static void add() {
lock.lock();
number++;
lock.unlock();
}
public static void main(String[] args) {
for (int i = 0; i < 1000; i++) {
new Thread(new Runnable() {
public void run() {
add();
}
}).start();
}
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println(number);
}
}
5.2 可重入性锁
synchronized和Lock都是可重入性锁,下面只给出Lock的重入性demo,synchronized是一个道理
public class ReentrantLockDemo {
private ReentrantLock lock = new ReentrantLock(false);
private void outer() {
lock.lock();
System.out.println("我是outer");
inner();
lock.unlock();
}
private void inner() {
lock.lock();
System.out.println("我是inner");
lock.unlock();
}
public static void main(String[] args) {
ReentrantLockDemo demo = new ReentrantLockDemo();
demo.outer();
}
}
运行结果
我是outer
我是inner
额外介绍一种非重入性锁
public class MyLockDemo {
// 一种非可重入锁
class MyLock {
private boolean isLocked = false;
public synchronized void lock() {
while (isLocked) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
isLocked = true;
}
public synchronized void unlock() {
isLocked = false;
notify();
}
}
private MyLock lock = new MyLock();
private void outer() {
lock.lock();
System.out.println("我是outer");
inner();
lock.unlock();
}
private void inner() {
lock.lock();
System.out.println("我是inner");
lock.unlock();
}
public static void main(String[] args) {
MyLockDemo demo = new MyLockDemo();
demo.outer();
}
}
运行结果
我是outer
分析,由于在inner方法中调用lock时,会执行wait方法,使得当前线程处于阻塞状态
5.2 可中断锁
Lock是可中断锁,通过调用lockInterruptibly方法,该方法会抛出中断异常,当线程被中断时,会立即抛出异常,而不会等待获取锁
public class InterruptLockDemo {
private ReentrantLock lock = new ReentrantLock();
public void methodA() {
try {
lock.lockInterruptibly();
System.out.println("我是方法A:" + System.currentTimeMillis());
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void methodB() {
try {
lock.lockInterruptibly();
System.out.println("我是方法B:" + System.currentTimeMillis());
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName()+":我被中断了"+System.currentTimeMillis());
} finally {
if (lock.tryLock()) {
lock.unlock();
}
}
}
public static void main(String[] args) {
final InterruptLockDemo demo = new InterruptLockDemo();
Thread t1 = new Thread(new Runnable() {
public void run() {
demo.methodA();
}
},"线程1");
Thread t2 = new Thread(new Runnable() {
public void run() {
demo.methodB();
}
},"线程2");
t1.start();
t2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.interrupt();
}
}
当去掉t2.interrupt时,运行结果
我是方法A:1507729561117
我是方法B:1507729563118
加上t2.interrupt时,运行结果
我是方法A:1507729665791
线程2:我被中断了1507729666795
注:线程1调用的是方法A,线程2调用的是方法B
结果分析
1.线程1和线程2调用两个同步方法,当调用到方法A时,会睡2秒,此时线程2就必须等待2秒才能执行方法B
2.在主线程睡了1秒发现还没获取到锁时,调用线程的中断方法,立即抛出异常,看后面的时间戳就知道(相差的1秒刚好是主线程睡眠的1秒)
synchronized是不可中断锁
public class NoInterruptLockDemo {
public synchronized void methodA() {
try {
System.out.println("我是方法A:" + System.currentTimeMillis());
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
public synchronized void methodB() {
System.out.println("我是方法B:" + System.currentTimeMillis());
}
public static void main(String[] args) {
final NoInterruptLockDemo demo = new NoInterruptLockDemo();
Thread t1 = new Thread(new Runnable() {
public void run() {
demo.methodA();
}
}, "线程1");
Thread t2 = new Thread(new Runnable() {
public void run() {
demo.methodB();
}
}, "线程2");
t1.start();
t2.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
t2.interrupt();
}
}
运行结果
我是方法A:1507730267055
我是方法B:1507730269059
结果分析
上述代码和Lock的一样,只是将lock换成了synchronized,就算调用了t2.interrupt,方法B和方法A执行时间仍然相差了2秒,因为synchronized方法无法抛出InterruptedException异常
5.3 公平锁
synchronized是非公平锁,而Lock可以选择公平锁,也可以选择费公平锁
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
在构造方法可以传入一个布尔值,true表示公平锁,false表示非公平锁
5.4 读写锁
ReadWriteLock就是读写锁,它是一个接口,ReentrantReadWriteLock实现了这个接口。
可以通过readLock()获取读锁,通过writeLock()获取写锁。
6. synchronized和Lock比较
-
存在层次
前者是关键字,后者是类 -
锁的获取
前者自动获取,后者调用lock方法 -
锁的释放
前者是在执行完同步块时自动释放
后者需要在finally里面主动调用unlock方法 -
锁类型
前者可重入,不可中断,非公平
后者可重入,可中断,可公平 -
锁状态
前者无法判断
后者可判断,通过tryLock方法的返回值来判断,false表示没拿到锁
tryLock()和tryLock(long time, TimeUnit unit)
前者是无论拿没拿到锁都立即返回结果
后者是在没拿到锁时会等待一段时间,在期限时间内如果还拿不到锁就返回false,如果在一开始就拿到锁,立即返回true -
性能
前者适合少量同步
后者适合大量同步
网友评论