回顾
与 synchronized 类似的,lock 也能够达到同步的效果。
首先回忆一下 synchronized 同步对象的方式:
当一个线程占用 synchronized 同步对象,其他线程就不能占用了,知道释放这个同步对象为止
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| public class SynchronizedTest {
public static String now() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); }
public static void main(String[] args) { final Object someObject = new Object();
Thread t1 = new Thread() { @Override public void run() { try { System.out.println(now() + " " + getName() + " 试图占有对象:someObject"); synchronized (someObject) { System.out.println(now() + " " + getName() + " 占有对象:someObject"); Thread.sleep(5000); System.out.println(now() + " " + getName() + " 释放对象:someObject"); } System.out.println(now() + " " + getName() + " 线程结束"); } catch (InterruptedException e) { e.printStackTrace(); } } }; t1.setName("t1"); t1.start();
Thread t2 = new Thread() { @Override public void run() { try { System.out.println(now() + " " + getName() + " 试图占有对象:someObject"); synchronized (someObject) { System.out.println(now() + " " + getName() + " 占有对象:someObject"); Thread.sleep(5000); System.out.println(now() + " " + getName() + " 释放对象:someObject");
} System.out.println(now() + " " + getName() + " 线程结束"); } catch (InterruptedException e) { e.printStackTrace(); } } }; t2.setName("t2"); t2.start(); } }
|
一、使用 Lock 对象实现同步效果
Lock 是一个接口,为了使用 Lock 对象,需要用到:
1
| Lock lock = new ReentrantLock();
|
与 synchronized(someObject) 类似,lock() 方法表示当前线程占用了 lock 对象,一旦占用,其他线程就不能占用了。
与 synchronized 不同的是,一旦 synchronized 块结束,就会自动释放 someObject 的占用。lock 却必须调用 unlock() 方法进行手动释放,为了保证释放的执行,往往会把 unlock() 放在 finally 中进行。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| public class LockTest {
public static String now() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); }
public static void main(String[] args) { Lock lock = new ReentrantLock();
Thread t1 = new Thread() { @Override public void run() { try { System.out.println(now() + " " + getName() + " 试图占有对象:lock"); lock.lock(); System.out.println(now() + " " + getName() + " 占有对象:lock"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(now() + " " + getName() + " 释放对象:lock"); lock.unlock(); } System.out.println(now() + " " + getName() + " 线程结束"); } }; t1.setName("t1"); t1.start();
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
Thread t2 = new Thread() { @Override public void run() { try { System.out.println(now() + " " + getName() + " 试图占有对象:lock"); lock.lock(); System.out.println(now() + " " + getName() + " 占有对象:lock"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println(now() + " " + getName() + " 释放对象:lock"); lock.unlock(); } System.out.println(now() + " " + getName() + " 线程结束"); } }; t2.setName("t2"); t2.start(); } }
15:49:39 t1 试图占有对象:lock 15:49:39 t1 占有对象:lock 15:49:39 t2 试图占有对象:lock 15:49:44 t1 释放对象:lock 15:49:44 t1 线程结束 15:49:44 t2 占有对象:lock 15:49:49 t2 释放对象:lock 15:49:49 t2 线程结束
|
二、trylock 方法
synchronized 是不占用到手誓不罢休,会一直试图占用下去。
与 synchronized的钻牛角尖不一样,Lock 接口还提供了一个 trylock 方法。
trylock 会在指定的时间范围内试图占用,占用成功了,就可以干活了。如果时间到了,还占用不成功,扭头就走。
注意: 因为使用 trylock 有可能成功,有可能失败,所以后面 unlock 释放锁的时候,需要判断是否是占用成功了,如果没占用成功也 unlock ,就会抛出异常。
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86
| public class LockTest {
public static String now() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); }
public static void main(String[] args) { Lock lock = new ReentrantLock();
Thread t1 = new Thread() { @Override public void run() { boolean locked = false; try { System.out.println(now() + " " + getName() + " 试图占有对象:lock"); locked = lock.tryLock(1, TimeUnit.SECONDS); if(locked){ System.out.println(now() + " " + getName() + " 占有对象:lock"); System.out.println(now() + " " + getName() + " 进行 5 秒的业务操作"); Thread.sleep(5000); }else { System.out.println(now() + " " + getName() + " 经过 1 秒钟的努力,还没有占有对象,放弃占有"); } } catch (InterruptedException e) { e.printStackTrace(); } finally { if(locked){ System.out.println(now() + " " + getName() + " 释放对象:lock"); lock.unlock(); } } System.out.println(now() + " " + getName() + " 线程结束"); } }; t1.setName("t1"); t1.start();
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
Thread t2 = new Thread() { @Override public void run() { boolean locked = false; try { System.out.println(now() + " " + getName() + " 试图占有对象:lock"); locked = lock.tryLock(1,TimeUnit.SECONDS); if(locked){ System.out.println(now() + " " + getName() + " 占有对象:lock"); System.out.println(now() + " " + getName() + " 进行 5 秒的业务操作"); Thread.sleep(5000); }else { System.out.println(now() + " " + getName() + " 经过 1 秒钟的努力,还没有占有对象,放弃占有"); }
} catch (InterruptedException e) { e.printStackTrace(); }finally { if(locked){ System.out.println(now() + " " + getName() + " 释放对象:lock"); lock.unlock(); } } System.out.println(now() + " " + getName() + " 线程结束"); } }; t2.setName("t2"); t2.start(); } }
17:00:47 t1 试图占有对象:lock 17:00:47 t1 占有对象:lock 17:00:47 t1 进行 5 秒的业务操作 17:00:49 t2 试图占有对象:lock 17:00:50 t2 经过 1 秒钟的努力,还没有占有对象,放弃占有 17:00:50 t2 线程结束 17:00:52 t1 释放对象:lock 17:00:52 t1 线程结束
|
三、线程交互
使用 synchronized 方式进行线程交互,用到的是同步对象的 wait,notify,notifyAll 方法。
Lock 也提供了类似的解决办法,首先通过 lock 对象得到一个 Condition 对象,然后分别调用这个 Condition 对象的:await,signal,signalAll 方法。
注意: 不是 Condition 对象的 wait,notify,notifyAll 方法,是 await,signal,signalAll 方法
代码实现:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83
| public class LockTest {
public static String now() { return new SimpleDateFormat("HH:mm:ss").format(new Date()); }
public static void main(String[] args) { final Lock lock = new ReentrantLock(); final Condition condition = lock.newCondition();
Thread t1 = new Thread() { @Override public void run() { try { System.out.println(now() + " " + getName() + " 试图占有对象:lock"); lock.lock(); System.out.println(now() + " " + getName() + " 占有对象:lock"); System.out.println(now() + " " + getName() + " 进行 5 秒的业务操作"); Thread.sleep(5000); System.out.println(now() + " " + getName() + " 临时释放对象 lock,并等待"); condition.await(); System.out.println(now() + " " + getName() + " 重新占有对象 lock,并进行 5 秒的业务操作"); Thread.sleep(5000); } catch (InterruptedException e) { e.printStackTrace(); } finally { System.out.println(now() + " " + getName() + " 释放对象:lock"); lock.unlock(); } System.out.println(now() + " " + getName() + " 线程结束"); } }; t1.setName("t1"); t1.start();
try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); }
Thread t2 = new Thread() { @Override public void run() { try { System.out.println(now() + " " + getName() + " 试图占有对象:lock"); lock.lock(); System.out.println(now() + " " + getName() + " 占有对象:lock"); System.out.println(now() + " " + getName() + " 进行 5 秒的业务操作"); Thread.sleep(5000); System.out.println(now() + " " + getName() + " 唤醒等待中的线程"); condition.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally { System.out.println(now() + " " + getName() + " 释放对象:lock"); lock.unlock(); } System.out.println(now() + " " + getName() + " 线程结束"); } }; t2.setName("t2"); t2.start(); } }
17:18:59 t1 试图占有对象:lock 17:18:59 t1 占有对象:lock 17:18:59 t1 进行 5 秒的业务操作 17:19:01 t2 试图占有对象:lock 17:19:04 t1 临时释放对象 lock,并等待 17:19:04 t2 占有对象:lock 17:19:04 t2 进行 5 秒的业务操作 17:19:09 t2 唤醒等待中的线程 17:19:09 t2 释放对象:lock 17:19:09 t2 线程结束 17:19:09 t1 重新占有对象 lock,并进行 5 秒的业务操作 17:19:14 t1 释放对象:lock 17:19:14 t1 线程结束
|
四、总结
本篇文章我们介绍了另外一种锁 Lock,以及相关交互的实现,它与 synchronized 区别:
1、Lock 是一个接口,而 synchronized 是 Java 中的关键字,synchronized 是内置的语言实现,Lock 是代码层面的实现。
2、 Lock 可以选择性的获取锁,如果一段时间获取不到,可以放弃。synchronized 不行,会一根筋一直获取下去。 借助 Lock 的这个特性,就能够规避死锁,synchronized 必须通过谨慎和良好的设计,才能减少死锁的发生。
3、 synchronized 在发生异常和同步块结束的时候,会自动释放锁。而 Lock 必须手动释放, 所以如果忘记了释放锁,一样会造成死锁。
好了,本篇文章到这里就结束了,感谢你的阅读🤝