wait与sleep

  1. 来自不同的类
    首先,wait和sleep都不是一个类下的方法:
    wait来自:Object
    sleep来自:Thread

因为java中所有的类都是继承自object的,所以所有类都可以调用wait方法,这是一个final的方法,同时不是一个静态方法,所以调用该方法需要先实例化一个Object对象才可以

  1. 释放锁的不同
    wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放! 也就是说,如果有两个线程,其中一个锁住了某个对象时,中间sleep了,这时候另一个线程时拿不到该对象的锁的,得等第一个线程sleep完并释放锁才可。
    wait会释放这个锁,并把这个wait的线程加入到这个锁的等待队列中去
  2. 使用的范围不同
    wait必须在同步代码块中使用
  3. 使用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 {
//synchronized
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;
// 卖票的方式
// synchronized 本质: 队列,锁
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 {
//synchronized
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();
// 卖票的方式
// synchronized 本质: 队列,锁

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区别

  1. synchronized 内置的Java关键字, Lock 是一个Java类
  2. synchronized无法判断获取锁的状态,Lock 可以判断是否获取到了锁
    1
    Thread提供了holdLock()方法检测当前线程是否持有锁,注意,是当前线程
  3. synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
  4. synchronized 线程 1(获得锁,阻塞)、线程2(只能等待);Lock锁就不一定会等待下去了,这里有个lock.tryLock()方法,尝试获取锁,可以做个判断让其尝试不到锁时不等待!!
  5. synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以在构造ReentrantLock()中自行设置boolean fair,true为公平,默认flase非公平
  6. 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
    /**
    * 线程之间的通信问题:生产者和消费者问题! 等待唤醒,通知唤醒
    * 线程交替执行 A B 操作同一个变量 num = 0
    * A num+1
    * B num-1
    */
    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;

    //+1
    public synchronized void increment() throws InterruptedException {
    if (number!=0){ //0
    // 等待
    this.wait();
    }
    number++;
    System.out.println(Thread.currentThread().getName()+"=>"+number);
    // 通知其他线程,我+1完毕了
    this.notifyAll();
    }

    //-1
    public synchronized void decrement() throws InterruptedException {
    if (number==0){ // 1
    // 等待
    this.wait();
    }
    number--;
    System.out.println(Thread.currentThread().getName()+"=>"+number);
    // 通知其他线程,我-1完毕了
    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();

//condition.await(); // 等待
//condition.signalAll(); // 唤醒全部
//+1
public void increment() throws InterruptedException {
lock.lock();
try {
// 业务代码
while (number!=0){ //0
// 等待
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我+1完毕了
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}

}

//-1
public synchronized void decrement() throws InterruptedException {
lock.lock();
try {
while (number==0){ // 1
// 等待
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName()+"=>"+number);
// 通知其他线程,我-1完毕了
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{ // 资源类 Lock

private Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private int number = 1; // 1A 2B 3C

public void printA(){
lock.lock();
try {
// 业务,判断-> 执行-> 通知
while (number!=1){
// 等待
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"=>AAAAAAA");
// 唤醒,唤醒指定的人,B
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");
// 唤醒,唤醒指定的人,c
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");
// 唤醒,唤醒指定的人,c
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
/**
有两个资源对象
一个是碗 一个是米饭

有两个线程 T1 T2

T1 拿了一个碗 想去盛饭

T2 拿了一份米饭 想拿这个碗来装

T1 可以拿到饭吗? 不能
T2 可以拿到碗吗? 不能

**/
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() {
/**
* 线程1 持有米饭的监视器锁
*/
synchronized (str1) {
System.out.println("T1 持有 " + str1);

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
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() {
// 线程2 持有碗 的监视器锁
synchronized (str2) {
System.out.println("T2 持有 " + str2);
// 线程2 持有碗 的监视器锁 的情况下想获取 米饭的锁
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() {
/**
* 线程1 持有米饭的监视器锁
*/
synchronized (str1) {
System.out.println("T1 持有 " + str1);
}// 使用完一个资源之后就立即释放锁

try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
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() {
// 线程2 持有碗 的监视器锁
synchronized (str2) {
System.out.println("T2 持有 " + str2);
// 线程2 持有碗 的监视器锁 的情况下想获取 米饭的锁
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();
}