多线程中的wait与sleep,synchronize与lock有啥子区别?生产消费模型是啥?死锁怎么解决?

wait与sleep

  1. 来自不同的类
    首先,wait和sleep都不是一个类下的方法:
    wait来自:Object
    sleep来自:Thread
    在这里插入图片描述
    在这里插入图片描述
    因为java中所有的类都是继承自object的,所以所有类都可以调用wait方法,这是一个final的方法,同时不是一个静态方法,所以调用该方法需要先实例化一个Object对象才可以

  2. 释放锁的不同
    wait 会释放锁,sleep 睡觉了,抱着锁睡觉,不会释放! 也就是说,如果有两个线程,其中一个锁住了某个对象时,中间sleep了,这时候另一个线程时拿不到该对象的锁的,得等第一个线程sleep完并释放锁才可。
    wait会释放这个锁,并把这个wait的线程加入到这个锁的等待队列中去

  3. 使用的范围不同
    wait必须在同步代码块中使用

  4. 使用sleep不需要被唤醒,但是wait是需要notify()或者notifyAll()去唤醒的,除了wait(1000)这种形式.

举例说明问题:

synchronized与lock

synchronized

        synchronized如果加在了非静态方法上,表示的是synchronized(调用方法的类的对象) {},如果加在了静态方法上,表示的是synchronized(类.class) {}

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公平锁与非公平锁源码实现

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 可以判断是否获取到了锁
Thread提供了holdLock()方法检测当前线程是否持有锁,注意,是当前线程

3.synchronized 会自动释放锁,lock 必须要手动释放锁!如果不释放锁,死锁
3. synchronized 线程 1(获得锁,阻塞)、线程2(只能等待);Lock锁就不一定会等待下去了,这里有个lock.tryLock()方法,尝试获取锁,可以做个判断让其尝试不到锁时不等待!!
4. synchronized 可重入锁,不可以中断的,非公平;Lock ,可重入锁,可以 判断锁,非公平(可以在构造ReentrantLock()中自行设置boolean fair,true为公平,默认flase非公平
5. synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码!

生产消费模型

        说白了就是多线程之间的通信,场景如下:有两个线程分别负责同一个资源类里变量的增加和减少,即生产与消费,对于增加和减少的逻辑:当资源中为0的时候,减少的方法就应该等待,不能再减少了;那么当资源不等于0 或者大于某个值时,增加方法就应该不再继续增加了。
        具体方法总结:判断等待–>业务–>通知

synchronized实现

/**
 * 线程之间的通信问题:生产者和消费者问题!  等待唤醒,通知唤醒
 * 线程交替执行  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之后,哪个线程来抢占呢?

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(); 来替换唤醒全部

// 判断等待,业务,通知
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

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 操作 ,这个时候其他线程就不能再操作这个资源对象了进入阻塞状态

/**
有两个资源对象
一个是碗  一个是米饭

有两个线程  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、写代码时候,尽量避免这种情况的写法 ,嵌套资源锁

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 块中的代码 执行

static Lock lock = new ReentrantLock();
try{
  lock.lock();
   
   
 }finally {
		lock.unlock();
}

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值