为了解决线程同步问题我们经常使用synchronized关键字和Lock接口下的锁来解决同步问题。但是这两者有什么区别之处呢?首先我们分别使用synchronized和Lock来解决生产者消费者问题。
生产者消费者问题 当生产者生产数量为1的时候,通知消费者消费。当生产者产品数量为0的时候,生产产品。
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 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 public class A { public static void main (String[] args) { Data data = new Data(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "A" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "B" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { try { data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "C" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { try { data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } }, "D" ).start(); } } class Data { private int number = 0 ; public synchronized void increment () throws InterruptedException { while (number != 0 ) { this .wait(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); this .notifyAll(); } public synchronized void decrement () throws InterruptedException { while (number == 0 ) { this .wait(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); this .notifyAll(); } }
Lock 要想使用Lock达到线程阻塞唤醒,我们必须要使用到Condition
Condition是在java 1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协作,相比使用Object的wait()、notify(),使用Condition的await()
、signal()
这种方式实现线程间协作更加安全和高效。因此通常来说比较推荐使用Condition,阻塞队列实际上是使用了Condition来模拟线程间协作。
Condition是个接口,基本的方法就是await()和signal()方法。
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()。调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用
Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()
运行查看结果,符合预期!
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 87 public class B { public static void main (String[] args) { Data2 data = new Data2(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { data.increment(); } }, "A" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { data.decrement(); } }, "B" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { data.increment(); } }, "C" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { data.decrement(); } }, "D" ).start(); } } class Data2 { private int number = 0 ; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void increment () { lock.lock(); try { while (number != 0 ) { condition.await(); } number++; System.out.println(Thread.currentThread().getName() + "=>" + number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement () { lock.lock(); try { while (number == 0 ) { condition.await(); } number--; System.out.println(Thread.currentThread().getName() + "=>" + number); condition.signalAll(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }
两者对应关系
synchroized和Lock的区别 原始构成 synchronized是java关键字属于JVM层面,底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象。只有在同步代码块中或方法中才能调用wait/notify等方法。
Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁。
获取锁的状态 synchornized无法判断是否获取锁的状态
Lock可以通过·tryLock()
判断是否获取到锁。
使用方法 synchronized不需要用户去手动释放锁,当synchronized代码执行完成后系统会自动让线程释放对锁的占用。
Lock则需要用户去手动释放锁,否则可能出现没有主动释放锁而导致的死锁现象。所以lock()
和unlock()
方法配合try/finally
语句块来完成。
异常是否释放锁 synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁
lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)
等待是否可中断 synchronized不可中断,除非抛出异常或者正常运行完成。
Lock可中断
通过设置超时方法中断tryLock(long timeout,TimeUnit unit)
通过lockInterruptibly()
加锁是否公平 synchronized非公平锁
ReentrantLock两者都可以,默认非公平锁,构造方法传入boolean值,true为公平锁,false为非公平锁。
是否一直等待 使用synchronized关键字的两个线程1和线程2,如果当前线程1获得锁,线程2线程等待。如果线程1阻塞,线程2则会一直等待下去。(不可中断)
Lock锁就不一定会等待下去,如果尝试获取不到锁,线程可以不用一直等待就结束啦。(可以中断)
能否精确唤醒线程 synchronized不能,只能随机唤醒一个(notify)或者唤醒全部等待线程(notifyall)
Lock可以使用condition达到精确唤醒
性能问题 在性能上来说,如果竞争资源不够激烈,两者的性能是差不多的,而当竞争资源非常激烈是(有大量线程同时竞争),此时Lock的性能远远优于synchronzed。
synchronized适合锁代码少量的同步问题
Lock锁适合大量同步的代码的同步问题。
精准唤醒 使用Lock加Condition精准唤醒,完成A执行完调用B,B执行完调用C,C执行完调用A。
运行测试。查看结果
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 87 88 89 90 91 92 93 94 95 96 public class C { public static void main (String[] args) { Data3 data = new Data3(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { data.printA(); } }, "A" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { data.printB(); } }, "B" ).start(); new Thread(() -> { for (int i = 0 ; i < 10 ; i++) { data.printC(); } }, "C" ).start(); } } class Data3 { private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int number = 1 ; public void printA () { lock.lock(); try { while (number != 1 ) { condition1.await(); } System.out.println(Thread.currentThread().getName() + "=>AAAAAAAA" ); number = 2 ; condition2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printB () { lock.lock(); try { while (number != 2 ) { condition2.await(); } System.out.println(Thread.currentThread().getName() + "=>BBBBBBBB" ); number = 3 ; condition3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void printC () { lock.lock(); try { while (number != 3 ) { condition3.await(); } System.out.println(Thread.currentThread().getName() + "=>CCCCCCCC" ); number = 1 ; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }