生产消费者问题
Sychronized,wait,notify
/** * @author hresh * @date 2020/2/16 21:19 * @description * 线程之间的通信问题:生产者和消费者问题 * 传统解决方法,Sychronized,wait,notify三者结合使用 */ public class A { public static void main(String[] args) { Data data = new Data(); new Thread(()->{ for (int i=0;i<20;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.decrement(); } catch (InterruptedException e) { e.printStackTrace(); } } },"C").start(); } } class Data { private int num = 0; //判断等待,业务,通知 public synchronized void increment() throws InterruptedException { //注意这里使用的是while判断,而非if判断,防止虚假唤醒 while (num != 0){ //等待 this.wait(); } num++; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,我+1完毕了 this.notifyAll(); } public synchronized void decrement() throws InterruptedException { while (num == 0){ this.wait(); } num--; System.out.println(Thread.currentThread().getName()+"=>"+num); //通知其他线程,我-1完毕了 this.notifyAll(); } } 复制代码
关于 wait 方法的判断,必须使用 while 条件,官方文档对此是这样描述的。
Lock,await,signal
public class LockDemo { public static void main(String[] args) { Data2 data = new Data2(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (Exception e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.decrement(); } catch (Exception e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.increment(); } catch (Exception e) { e.printStackTrace(); } } },"C").start(); new Thread(()->{ for (int i=0;i<10;i++){ try { data.decrement(); } catch (Exception e) { e.printStackTrace(); } } },"D").start(); } } class Data2 { private int num = 0; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void increment() { 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(); } } public void decrement() { 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(); } } } 复制代码
从结果中可以发现这种实现方式和 Synchronized 关键字效果一致,每当生产者生产完毕,都有一个消费者可以获取到来消费。此时如果有这么一个需求:消费者B获取生产者A的内容,D获取C的内容,实现精准获取,有序执行。采用 Synchronized 是无法满足该需求的,但是 Lock 锁有方法可以实现。
定义3个线程,A执行完该B执行,B执行完该C执行,之后再从A开始。类似案例:比如说在生产线中:下单-》支付-》交易-》物流
public class NewLockDemo { public static void main(String[] args) { Data3 data3 = new Data3(); new Thread(()->{ for(int i=0;i<10;i++){ try { data3.printA(); } catch (Exception e) { e.printStackTrace(); } } },"A").start(); new Thread(()->{ for(int i=0;i<10;i++){ try { data3.printB(); } catch (Exception e) { e.printStackTrace(); } } },"B").start(); new Thread(()->{ for(int i=0;i<10;i++){ try { data3.printC(); } catch (Exception e) { e.printStackTrace(); } } },"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 num = 1; public void printA(){ lock.lock(); try { while (num != 1){ condition1.await(); } System.out.println(Thread.currentThread().getName()+"AAAAA"); num = 2; condition2.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printB(){ lock.lock(); try { while (num != 2){ condition2.await(); } System.out.println(Thread.currentThread().getName()+"BBBBBBBB"); num = 3; condition3.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } public void printC(){ lock.lock(); try { while (num != 3){ condition3.await(); } System.out.println(Thread.currentThread().getName()+"CCCCCCCC"); num = 1; condition1.signal(); } catch (InterruptedException e) { e.printStackTrace(); }finally { lock.unlock(); } } } 复制代码
通过声明3个 Condition 对象,每次调用方法,指定某个 Condition 等待,然后释放某个准确的 Condition。
8锁问题
Synchronized 锁实例对象
如下案例,phone 对象调用 sendSms 和 call 方法,先执行哪个方法,就意味着该方法获取了 phone 对象的锁,另外一个方法就必须等待锁被释放后,才可以执行。
/** * @author hresh * @date 2020/2/16 22:02 * @description * 8锁,关于锁的8个问题 * 1.标准情况下,先打印发短信还是打电话?答:1、发短信;2、打电话 * 2.sendSms延时3m,先打印哪个?答:1、发短信;2、打电话 */ public class Test1 { public static void main(String[] args) { Phone phone = new Phone(); new Thread(()->{ phone.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone.call(); },"B").start(); } } class Phone{ // Synchronized 锁的对象是方法的调用者,即new出来的对象 //以下两个方法共用同一把锁 public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } } 复制代码
当对象中除了同步方法外,还有一个普通方法,执行顺序又将变成什么样子?
//3.一个对象,添加一个普通方法,先打印hello还是发短信? public class Test2 { public static void main(String[] args) { Phone2 phone1 = new Phone2(); new Thread(()->{ phone1.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.hello(); },"B").start(); } } class Phone2{ //Sychronized锁的对象是方法的调用者 public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public void hello(){ System.out.println("hello"); } } 复制代码
执行结果为:
hello 发短信 复制代码
在第一个案例中,我们说过 Synchronized 锁的对象是 phone 对象,这是针对同步方法而言,但是普通方法的调用并不需要获取锁,所以当同步方法在延时等待时,普通方法就可以正常执行。
当锁对象有两个时,分别调用一个方法,结果又是怎样?
//两个对象,分别调用发短信和打电话,先打印打电话 public class Test2 { public static void main(String[] args) { //两个对象 Phone2 phone1 = new Phone2(); Phone2 phone2 = new Phone2(); new Thread(()->{ phone1.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } class Phone2{ //Sychronized锁的对象是方法的调用者 public synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } } 复制代码
执行结果:
打电话 发短信 复制代码
因为有两个锁对象,所以大家各自都有一把锁,不需要等待锁释放,sendSms 方法因为要延迟等待,所以就是 call 方法先执行。
Synchronized 锁Class对象
被 static 修饰的方法为静态方法,随着类的加载而加载,只加载一次,不需要实例化对象,可以通过类直接进行调用,所以此时锁的对象是 Phone3.class。
//5.一个对象,两个方法都声明为静态的,分别调用发短信和打电话,先打印发短信 public class Test3 { public static void main(String[] args) { Phone3 phone1 = new Phone3(); new Thread(()->{ phone1.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.call(); },"B").start(); } } class Phone3{ //Sychronized锁的对象是方法的调用者 //static 静态方法 //类一加载就有了!Class模版 public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public static synchronized void call(){ System.out.println("打电话"); } } 复制代码
如果我们声明两个对象,分别调用这两个方法,结果又是怎样的呢?
//6.两个对象,同样都是静态的,先打印发短信,因为虽然对象不同,但是锁的是同一个类模版 public class Test3 { public static void main(String[] args) { Phone3 phone1 = new Phone3(); Phone3 phone2 = new Phone3(); new Thread(()->{ phone1.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } 复制代码
结果与上面一样,都是先打印发短信。原因在于 Phone.class 只有一份,关于它的锁谁先拿到就先执行,与 new 多少个对象无关。
Synchronized 多锁
当代码中存在多个锁时,也即上面提到的两种锁,一种是实例对象锁,一种是类对象锁。
//7.一个对象,一个静态同步,一个普通同步,先打印打电话 public class Test4 { public static void main(String[] args) { Phone4 phone1 = new Phone4(); new Thread(()->{ phone1.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.call(); },"B").start(); } } class Phone4{ //静态同步方法 public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ System.out.println("打电话"); } } 复制代码
结果中先打印打电话,原因在于这两个方法需要拿到的锁不一样,所以并不存在等待锁释放。
即使声明两个对象,一个调用静态同步方法,一个调用普通同步方法,结果与上述一致。
//8.两个对象,一个静态同步,一个普通同步,先打印打电话 public class Test4 { public static void main(String[] args) { Phone4 phone1 = new Phone4(); Phone4 phone2 = new Phone4(); new Thread(()->{ phone1.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone2.call(); },"B").start(); } } 复制代码
Synchronized 锁代码块
Synchronized 关键字还可以用来修饰同步代码块,在某些情况下,我们可能只需要同步一部分代码,没必要对整个方法进行同步操作,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了,同步代码块的使用示例如下:
//this,当前实例对象锁 synchronized(this){ for(int j=0;j<1000000;j++){ i++; } } //class对象锁 synchronized(AccountingSync.class){ for(int j=0;j<1000000;j++){ i++; } } 复制代码
我们可以使用 this 对象(代表当前实例)或者当前类的 class 对象作为锁。以下案例来比较这三种方式的运行结果:
//9.一个对象,一个静态同步,一个普通同步方法,一个同步代码块,先打印打电话,最后发短信 public class Test5 { public static void main(String[] args) { Phone5 phone1 = new Phone5(); new Thread(()->{ phone1.sendSms(); },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ phone1.call(); },"B").start(); new Thread(()->{ phone1.test(); },"C").start(); } } class Phone5{ //静态同步方法 public static synchronized void sendSms(){ try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("发短信"); } public synchronized void call(){ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("打电话"); } public void test(){ synchronized (this){ System.out.println("synchronized code"); } } } 复制代码
执行结果为:
打电话 synchronized code 发短信