wait与sleep
来自不同的类 首先,wait和sleep都不是一个类下的方法: wait来自:Object sleep来自:Thread
因为java中所有的类都是继承自object的,所以所有类都可以调用wait方法,这是一个final的方法,同时不是一个静态方法,所以调用该方法需要先实例化一个Object对象才可以
释放锁的不同 wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放! 也就是说,如果有两个线程,其中一个锁住了某个对象时,中间sleep了,这时候另一个线程时拿不到该对象的锁的,得等第一个线程sleep完并释放锁才可。 wait会释放这个锁,并把这个wait的线程加入到这个锁的等待队列中去
使用的范围不同 wait必须在同步代码块中使用
使用sleep不需要被唤醒,但是wait是需要notify()或者notifyAll()去唤醒的,除了wait(1000)这种形式.
举例说明问题:
synchronized与lock synchronized synchronized如果加在了非静态方法上,表示的是synchronized(调用方法的类的对象) {},如果加在了静态方法上,表示的是synchronized(类.class) {}
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 public class DifferSynchronizedAndLock { public static void main (String[] args) { Ticket ticket = new Ticket(); 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(); } static class Ticket { private int number = 30 ; public synchronized void sale () { if (number>0 ){ System.out.println(Thread.currentThread().getName()+"卖出了" +(number--)+"票,剩余:" +number); } }} }
此处如果不对方法加synchronized修饰(不加锁):
lock Lock是一个接口,实现类有一下几个:
先看可重入锁(ReentrantLock):
这里的可重入锁构造时候除非传入fair公平,否则默认为不公平锁。 公平锁:十分公平:可以先来后到 非公平锁:十分不公平:可以插队 (默认)深入剖析ReentrantLock公平锁与非公平锁源码实现
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 import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class DifferSynchronizedAndLock { public static void main (String[] args) { Ticket ticket = new Ticket(); 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(); } static class Ticket { private int number = 30 ; 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(); } }} }
synchronized 与 lock区别
synchronized 内置的Java关键字, Lock 是一个Java类
synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁1 Thread提供了holdLock()方法检测当前线程是否持有锁,注意,是当前线程
synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
synchronized 线程 1(获得锁,阻塞)、线程2(只能等待);Lock锁就不一定会等待下去了,这里有个lock.tryLock()方法,尝试获取锁,可以做个判断让其尝试不到锁时不等待!!
synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以在构造ReentrantLock()中自行设置boolean fair,true为公平,默认flase非公平
synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!生产消费模型 说白了就是多线程之间的通信,场景如下:有两个线程分别负责同一个资源类里变量的增加和减少,即生产与消费,对于增加和减少的逻辑:当资源中为0的时候,减少的方法就应该等待,不能再减少了;那么当资源不等于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 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(); } } class Data { private int number = 0 ; public synchronized void increment () throws InterruptedException { if (number!=0 ){ this .wait(); } number++; System.out.println(Thread.currentThread().getName()+"=>" +number); this .notifyAll(); } public synchronized void decrement () throws InterruptedException { if (number==0 ){ this .wait(); } number--; System.out.println(Thread.currentThread().getName()+"=>" +number); this .notifyAll(); } }
此处存在的问题 案例这样,如果只是两个线程通信,一个增加一个减少,必然不会出错,如果再增加几个呢?那notify之后,哪个线程来抢占呢?
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 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(); } }
通过结果可以看出,有个值变成了2,也就是两个加法被唤醒了,且使得值均加了1。 由于这里的资源类中增加和减少的方法使用的是if判断,所以也就只有一次判断,而wait之后,重新被唤醒要执行的是wait之后的语句,所以必须让他反复的判断一下值,这样才能保证线程安全!!将if改成while即可!! if在官方文档中存在一个虚假唤醒的问题
lock锁实现 Lock提供了condition.await(); 来替换等待,condition.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 class Data2 { private int number = 0 ; Lock lock = new ReentrantLock(); Condition condition = lock.newCondition(); public void increment () throws InterruptedException { lock.lock(); try { while (number!=0 ){ condition.await(); } number++; System.out.println(Thread.currentThread().getName()+"=>" +number); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public synchronized void decrement () throws InterruptedException { lock.lock(); try { while (number==0 ){ condition.await(); } number--; System.out.println(Thread.currentThread().getName()+"=>" +number); condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
Condition 前面实现的都是随机的状态,也就是没有人为的去控制线程执行顺序,Condition可以精准的通知和唤醒!! 这里提供一个场景:A 执行完调用B,B执行完调用C,C执行完调用A !! 这里如果num为1时,让A执行,num=2–>B, num=3–>C
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 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()+"=>AAAAAAA" ); 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()+"=>BBBBBBBBB" ); 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()+"=>BBBBBBBBB" ); number = 1 ; condition1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } }
输出结果即:A执行完通知B执行,B执行完通知C……..
死锁 为什么需要锁? 多线程操作共享资源,存在同时对资源的读写 ,会导致资源的原子性遭到不一致处理
需要锁操作来控制多个线程对象资源操作的原子性(安全性)
锁的工作模式: 对于一个共享资源,我们有 lock 与 unlock 两个原子操作
在一个线程对于一个资源进行操作的时候,实行lock 操作 ,这个时候其他线程就不能再操作这个资源对象了进入阻塞状态
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 package com.thread0622;public class ThreadTest { static String str1 = "米饭" ; static String str2 = "碗" ; public static void main (String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run () { synchronized (str1) { System.out.println("T1 持有 " + str1); try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (str2) { System.out.println("T1 想持有 " + str2); } System.out.println("T1 End...." ); } } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run () { synchronized (str2) { System.out.println("T2 持有 " + str2); synchronized (str1) { System.out.println("T2 想持有 " + str1); } System.out.println("T2 End...." ); } } }); t2.start(); } }
解决死锁的办法 1、在申请一个对象资源锁的时候 加入一个时间判断,释放手头所有的资源锁
2、写代码时候,尽量避免这种情况的写法 ,嵌套资源锁
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 package com.thread0622;public class ThreadTest { static String str1 = "米饭" ; static String str2 = "碗" ; public static void main (String[] args) { Thread t1 = new Thread(new Runnable() { @Override public void run () { synchronized (str1) { System.out.println("T1 持有 " + str1); } try { Thread.sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (str2) { System.out.println("T1 想持有 " + str2); } System.out.println("T1 End...." ); } }); t1.start(); Thread t2 = new Thread(new Runnable() { @Override public void run () { synchronized (str2) { System.out.println("T2 持有 " + str2); synchronized (str1) { System.out.println("T2 想持有 " + str1); } System.out.println("T2 End...." ); } } }); t2.start(); } }
3、finally 关键字 能够保证 finally 块中的代码 执行
1 2 3 4 5 6 7 8 static Lock lock = new ReentrantLock();try { lock.lock(); }finally { lock.unlock(); }