JUC并发实战
Synchonized与Lock
区别
- Synchronized是Java的关键字,由JVM层面实现的,Lock是一个接口,有实现类,由JDK实现。
- Synchronized无法获取锁的状态,Lock可以判断是否获取到了锁。
- Synchronized自动释放锁,lock一般在finally中手动释放,如果不释放锁,会死锁。
- Synchronized 线程1(获得锁,阻塞),线程2(等待,傻傻的等); lock锁不一定会等待下去(lock.tryLock())
- Synchronized是可重入的,不可中断的,非公平锁。Lock, 可重入锁,可以判断锁,非公平锁。
- Synchronized 适合锁少量的代码同步问题,Lock适合锁大量的同步代码。
代码示例
高铁票类synchronized实现TicketS.java,对于线程来说也属于资源类
package cn.itxs.synchronize;public class TicketS { private int quantify = 20; public synchronized void sale(){ if (quantify > 0) { System.out.println("当前线程"+Thread.currentThread().getName() + "卖出了第" + quantify-- + "张高铁票,剩余票数量为" + quantify); } }}
高铁票类Lock实现TicketL.java
package cn.itxs.synchronize;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class TicketL { private int quantify = 20; private Lock lock = new ReentrantLock(); public void sale(){ lock.lock(); try { if (quantify > 0) { System.out.println("当前线程"+Thread.currentThread().getName() + "卖出了第" + quantify-- + "张高铁票,剩余票数量为" + quantify); } }catch (Exception e) { e.printStackTrace(); }finally { lock.unlock(); } }}
测试类,下面线程的使用采用lambda表达式写法,属于JDK8的特性之一
package cn.itxs.synchronize;public class ThreadMain { public static void main(String[] args) { TicketS ticketS = new TicketS(); System.out.println("ticketS-----------"); new Thread(() -> { for (int i = 1; i < 30; i++) { ticketS.sale(); } },"第一个线程").start(); new Thread(() -> { for (int i = 1; i < 30; i++) { ticketS.sale(); } },"第二个线程").start(); new Thread(() -> { for (int i = 1; i < 30; i++) { ticketS.sale(); } },"第三个线程").start(); System.out.println("ticketL-----------"); TicketL ticketL = new TicketL(); new Thread(() -> { for (int i = 1; i < 30; i++) { ticketL.sale(); } },"第一个线程").start(); new Thread(() -> { for (int i = 1; i < 30; i++) { ticketL.sale(); } },"第二个线程").start(); new Thread(() -> { for (int i = 1; i < 30; i++) { ticketL.sale(); } },"第三个线程").start(); }}
虚假唤醒
概述
- 虚假唤醒是指当一定的条件触发时会唤醒很多在阻塞态的线程,但只有部分的线程唤醒是有用的,其余线程的唤醒是多余的;比如说卖货,如果本来没有货物,突然进了一件货物,这时所有的顾客都被通知了,但是只能一个人买,所以对其他人都是做了无用的通知。
代码示例
计算类,提供加一减一的0和1结果,Counter.java
package cn.itxs.counter;public class Counter { private int count = 0; public synchronized void addCount() throws InterruptedException { if (count > 0){ //线程开始等待 this.wait(); } count++; System.out.println("当前线程为" + Thread.currentThread().getName() + ",count=" + count); //通知其他线程 this.notifyAll(); } public synchronized void subtractCount() throws InterruptedException { if (count == 0){ //线程开始等待 this.wait(); } count--; System.out.println("当前线程为" + Thread.currentThread().getName() + ",count=" + count); //通知其他线程 this.notifyAll(); }}
测试类
package cn.itxs.counter;public class CounterMain { public static void main(String[] args) { Counter counter = new Counter(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { counter.addCount(); } catch (InterruptedException e) { e.printStackTrace(); } } },"第一个线程").start(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { counter.subtractCount(); } catch (InterruptedException e) { e.printStackTrace(); } } },"第二个线程").start(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { counter.addCount(); } catch (InterruptedException e) { e.printStackTrace(); } } },"第三个线程").start(); new Thread(() -> { for (int i = 1; i < 10; i++) { try { counter.subtractCount(); } catch (InterruptedException e) { e.printStackTrace(); } } },"第四个线程").start(); }}
从上面分析可以知道导致虚假唤醒的原因主要就是一个线程直接在if代码块中被唤醒了,这时它已经跳过了if判断。我们只需要将if判断改为while,这样线程就会被重复判断而不再会跳出判断代码块,从而不会产生虚假唤醒这种情况了。
Callable
Callable任务可拿到一个Future对象,表示异步计算的结果,它提供了检查是否计算完成的方法,以等待计算的完成,并检索计算的结果,通过Future对象可以了解任务执行情况,可以取消任务的执行,还可以获取执行结果。
- Runnable和Callable的区别Callable规定的方法是call(),Runnable规定的接口是run();Callable的任务执行后可返回值,而Runnable的任务是不能有返回值的;call方法可以抛出异常,run方法不可以
实现Callable接口资源类MessageThread.java
package cn.itxs.collection;import java.util.concurrent.Callable;import java.util.concurrent.TimeUnit;public class MessageThread implements Callable<Integer> { @Override public Integer call() throws Exception { System.out.println("hello callable!"); TimeUnit.SECONDS.sleep(3); return 100; }}
测试类
package cn.itxs.collection;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class CallableMain { public static void main(String[] args) throws ExecutionException, InterruptedException { MessageThread messageThread = new MessageThread(); FutureTask futureTask = new FutureTask(messageThread); new Thread(futureTask,"FutureTaskTest").start(); Integer res = (Integer)futureTask.get(); System.out.println(res); }}
异步回调
package cn.itxs.asyncall;import java.util.concurrent.CompletableFuture;import java.util.concurrent.ExecutionException;import java.util.concurrent.TimeUnit;public class AsynCallMain { public static void main(String[] args) throws ExecutionException, InterruptedException { CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ",async call void"); }); System.out.println("等待线程异步执行"); completableFuture.get(); CompletableFuture<String> completableFutureR = CompletableFuture.supplyAsync(() -> { try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } //int i = 1/0; //取消注释后下面e输出详细信息,返回值为bad System.out.println(Thread.currentThread().getName() + ",async call return"); return "good"; }); System.out.println("等待线程异步执行"); System.out.println(completableFutureR.whenComplete((s, e) -> { System.out.println("s=" + s + ",e=" + e); }).exceptionally((e) -> { System.out.println(e.getMessage()); return "bad"; }).get()); }}
Lock+Condition
代码示例
先将上一小节改造为Lock+Condition版本下CounterL.java
package cn.itxs.counter;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class CounterL { private int count = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); public void addCount() throws InterruptedException { lock.lock(); try { while (count > 0){ //线程开始等待 condition.await(); } count++; System.out.println("当前线程为" + Thread.currentThread().getName() + ",count=" + count); //通知其他线程 condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void subtractCount() throws InterruptedException { lock.lock(); try { while (count == 0){ //线程开始等待 condition.await(); } count--; System.out.println("当前线程为" + Thread.currentThread().getName() + ",count=" + count); //通知其他线程 condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } }}
和上面的执行结果是一样,但Lock+Condition可以实现精准的唤醒
CounterA.java
package cn.itxs.counter;import java.util.concurrent.locks.Condition;import java.util.concurr