真正的多线程(公司)开发,不会去创建一个类,用该类去继承Thread类或实现Runnable接口,线程就是一个单独的资源类,没有任何附属的操作!
Synchronized传统锁public class SynchronizedTest { public static void main(String[] args) { //创建资源类 Ticket ticket = new Ticket(); //函数式接口使用lambda表达式 new Thread(()->{ for(int i=1;i<40;i++){ ticket.sale(); } },"A").start(); new Thread(()->{ for(int i=1;i<40;i++){ ticket.sale(); } },"B").start(); new Thread(()->{ for(int i=1;i<40;i++){ ticket.sale(); } },"C").start(); }}//资源类,只有属性和方法class Ticket{ private int number = 30; //买票 synchronized 本质:队列,锁 public synchronized void sale(){ if(number > 0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number+"张票"); } }}
测试结果:
A卖出了第30张票,剩余29张票B卖出了第29张票,剩余28张票B卖出了第28张票,剩余27张票B卖出了第27张票,剩余26张票B卖出了第26张票,剩余25张票B卖出了第25张票,剩余24张票B卖出了第24张票,剩余23张票B卖出了第23张票,剩余22张票B卖出了第22张票,剩余21张票B卖出了第21张票,剩余20张票B卖出了第20张票,剩余19张票B卖出了第19张票,剩余18张票B卖出了第18张票,剩余17张票B卖出了第17张票,剩余16张票B卖出了第16张票,剩余15张票B卖出了第15张票,剩余14张票B卖出了第14张票,剩余13张票B卖出了第13张票,剩余12张票B卖出了第12张票,剩余11张票B卖出了第11张票,剩余10张票B卖出了第10张票,剩余9张票B卖出了第9张票,剩余8张票B卖出了第8张票,剩余7张票B卖出了第7张票,剩余6张票B卖出了第6张票,剩余5张票B卖出了第5张票,剩余4张票B卖出了第4张票,剩余3张票B卖出了第3张票,剩余2张票B卖出了第2张票,剩余1张票B卖出了第1张票,剩余0张票
Lock锁点击ReentrantLock查看源码可以看到
public ReentrantLock() { sync = new NonfairSync(); } public ReentrantLock(boolean fair) { // 公平锁 非公平锁 sync = fair ? new FairSync() : new NonfairSync(); }
公平锁: 十分公平,可以先来后到
非公平锁: 十分不公平,可以插队(ReentrantLock默认非公平锁)
使用Lock锁三步骤:
创建lock对象 Lock lock = new ReentrantLock();加锁 lock.lock();解锁 lock.unlock();
import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class LockTest { public static void main(String[] args) { //创建资源类 Ticket2 ticket = new Ticket2(); //函数式接口使用lambda表达式 new Thread(()->{ for(int i=1;i<40;i++) ticket.sale();},"A").start(); new Thread(()->{ for(int i=1;i<40;i++) ticket.sale();},"B").start(); new Thread(()->{ for(int i=1;i<40;i++) ticket.sale();},"C").start(); }}//资源类,只有属性和方法class Ticket2{ private int number = 30; //买票 使用lock锁 Lock lock = new ReentrantLock(); public void sale(){ //加锁 lock.lock(); try{ if(number > 0){ System.out.println(Thread.currentThread().getName()+"卖出了第"+(number--)+"张票,剩余"+number+"张票"); } }catch (Exception e){ e.printStackTrace(); }finally { //解锁 lock.unlock(); } }}
测试结果:
A卖出了第30张票,剩余29张票A卖出了第29张票,剩余28张票C卖出了第28张票,剩余27张票C卖出了第27张票,剩余26张票C卖出了第26张票,剩余25张票C卖出了第25张票,剩余24张票B卖出了第24张票,剩余23张票B卖出了第23张票,剩余22张票B卖出了第22张票,剩余21张票B卖出了第21张票,剩余20张票B卖出了第20张票,剩余19张票B卖出了第19张票,剩余18张票B卖出了第18张票,剩余17张票B卖出了第17张票,剩余16张票B卖出了第16张票,剩余15张票B卖出了第15张票,剩余14张票B卖出了第14张票,剩余13张票B卖出了第13张票,剩余12张票B卖出了第12张票,剩余11张票B卖出了第11张票,剩余10张票B卖出了第10张票,剩余9张票B卖出了第9张票,剩余8张票B卖出了第8张票,剩余7张票B卖出了第7张票,剩余6张票B卖出了第6张票,剩余5张票B卖出了第5张票,剩余4张票B卖出了第4张票,剩余3张票B卖出了第3张票,剩余2张票B卖出了第2张票,剩余1张票B卖出了第1张票,剩余0张票
Synchronized和Lock区别区别:
Synchronized是关键字;Lock是一个java类Synchronized无法判断锁的状态;Lock是可以判断是否获取到了锁Synchronized会自动释放锁;Lock必须要手动释放锁,如果不释放锁会出现 死锁Synchronized 线程1(获取锁,阻塞) 线程2(一直等待);Lock锁不会一直等待Synchronized可重入锁,不可以中断的,非公平;Lock可重入锁,可以判断锁,默认非公平(可以手动设置)Synchronized适合锁少量的代码同步问题;Lock适合锁大量的同步代码 生产者和消费者
Synchronized版解决:
public class A { public static void main(String[] args) { Data data = new Data(); new Thread(()->{ try { for(int i=0;i<10;i++)data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(()->{ try { for(int i=0;i<10;i++)data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } },"B").start(); }}//资源类class Data{ private int num = 0; //+1 public synchronized void increment() throws InterruptedException { //等待 if(num != 0) { this.wait(); } num ++; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,+1完毕 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException { if(num == 0){ //等待 this.wait(); } num --; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,-1完毕 this.notify(); }}
执行结果:
A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0
存在问题:如果此时在添加两个线程 C,D,再次查看结果
Data data = new Data(); new Thread(()->{ try { for(int i=0;i<10;i++)data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(()->{ try { for(int i=0;i<10;i++)data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } },"B").start(); new Thread(()->{ try { for(int i=0;i<10;i++)data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } },"C").start(); new Thread(()->{ try { for(int i=0;i<10;i++)data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } },"D").start(); }
执行结果:
A=>1B=>0A=>1B=>0C=>1B=>0C=>1B=>0C=>1B=>0C=>1B=>0C=>1B=>0C=>1B=>0C=>1B=>0C=>1D=>0B=>-1D=>-2D=>-3D=>-4D=>-5D=>-6D=>-7D=>-8D=>-9D=>-10A=>-9C=>-8A=>-7C=>-6A=>-5
可以看到数据存在一些问题,原因就是我们在判断等待的时候使用的if
在官方文档的wait()方法解释中可以找到:
线程也可以唤醒,而不会被通知,中端或超时,即所谓的虚假唤醒。虽然这在实践中很少会发生,但应用程序必须通过测试应该是线程被唤醒的条件来防范,并且如果条件不满足则继续等待。换句话说,等待应该总是出现在循环中,可以使用while
解决办法:将判断等待条件用的if修改为在while循环中判断等待条件
//+1 public synchronized void increment() throws InterruptedException { //等待 while (num != 0) { this.wait(); } num ++; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,+1完毕 this.notify(); } //-1 public synchronized void decrement() throws InterruptedException { while (num == 0){ //等待 this.wait(); } num --; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,-1完毕 this.notify(); }
执行结果:
A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0
JUC版解决:
import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class B { public static void main(String[] args) { Data data = new Data(); new Thread(()->{ try { for(int i=0;i<10;i++)data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } },"A").start(); new Thread(()->{ try { for(int i=0;i<10;i++)data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } },"B").start(); new Thread(()->{ try { for(int i=0;i<10;i++)data.increment(); } catch (InterruptedException e) { e.printStackTrace(); } },"C").start(); new Thread(()->{ try { for(int i=0;i<10;i++)data.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } },"D").start(); }}//资源类class Data{ private int num = 0; //使用lock锁 Lock lock = new ReentrantLock(); //JUC中配合lock提供的用于做现成通讯的接口 Condition condition = lock.newCondition(); //+1 public void increment() throws InterruptedException { //加锁 lock.lock(); try { while (num != 0) { //等待 condition.await(); } num ++; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,+1完毕 condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { //解锁 lock.unlock(); } } //-1 public void decrement() throws InterruptedException { //加锁 lock.lock(); try { while (num == 0){ //等待 condition.await(); } num --; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,-1完毕 condition.signalAll(); }catch (Exception e){ e.printStackTrace(); }finally { //解锁 lock.unlock(); } }}
执行结果:
A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1B=>0A=>1D=>0A=>1D=>0A=>1D=>0A=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1D=>0C=>1B=>0C=>1B=>0C=>1B=>0C=>1B=>0
从上述代码测试结果可以发现lock+condition使用和synchronized+wait效果一样,但任何一个新的技术,绝对不是仅仅只是覆盖原来的技术,一定会有优势和补充。
condition的优势和补充: 精准的通知和唤醒线程
//A执行完调用B,B执行完调用C,C执行完调用Apublic class C { public static void main(String[] args) { Data data = new Data(); 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 Data{ private Lock lock = new ReentrantLock(); private Condition condition1 = lock.newCondition(); private Condition condition2 = lock.newCondition(); private Condition condition3 = lock.newCondition(); private int num = 1; //1的时候A执行,2的时候B执行,3的时候C执行 public void printA(){ lock.lock(); try{ while (num != 1){ //等待 condition1.await(); } System.out.println(Thread.currentThread().getName()+"执行"); num = 2; //唤醒指定的人B condition2.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void printB(){ lock.lock(); try{ while (num != 2){ //等待 condition2.await(); } System.out.println(Thread.currentThread().getName()+"执行"); num = 3; //唤醒C condition3.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } } public void printC(){ lock.lock(); try{ while (num != 3){ //等待 condition3.await(); } System.out.println(Thread.currentThread().getName()+"执行"); num = 1; //唤醒A condition1.signal(); }catch (Exception e){ e.printStackTrace(); }finally { lock.unlock(); } }}
执行结果:
A执行B执行C执行A执行B执行C执行A执行B执行C执行A执行B执行C执行A执行B执行C执行A执行B执行C执行A执行B执行C执行A执行B执行C执行A执行B执行C执行A执行B执行C执行