学习JavaEE的日子 Day31单例模式 ,生产者消费者模型,仓储模型

Day31

1.线程安全 -- 单例模式(懒汉式)

理解:该类的对象在整个项目中只创建一次(只实例化一次)

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		A a1 = A.getIntance();
		A a2 = A.getIntance();
		A a3 = A.getIntance();
		A a4 = A.getIntance();
		
		System.out.println(a1);
		System.out.println(a2);
		System.out.println(a3);
		System.out.println(a4);
	}
}
java 复制代码
public class A {
	
	private static A a;//私有化属性

	private A(){}
	
	public static A getIntance(){
		if(a == null){
			a = new A();
		}
		return a;
	}
}

注意:单例模式(懒汉式)不是线程安全的

java 复制代码
public class Test02 {
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程1:" + A.getIntance());
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程2:" + A.getIntance());
			}
		});
		
		t1.start();
		t2.start();
	}
}

2.线程安全 -- 枚举单例模式(饿汉式)

理解:该类的对象在整个项目中只创建一次(只实例化一次)

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		A a1 = A.getIntance();
		A a2 = A.getIntance();
		A a3 = A.getIntance();
		A a4 = A.getIntance();
		
		System.out.println(a1);
		System.out.println(a2);
		System.out.println(a3);
		System.out.println(a4);
	}
}
java 复制代码
public class A {
	
	private static A a = new A();

	private A(){}
	
	public static A getIntance(){
		return a;
	}
	
	public static void method(){
		System.out.println("好好学习");
	}
}

注意:枚举单例模式(饿汉式)是线程安全的

java 复制代码
public class Test02 {
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程1:" + A.getIntance());
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程2:" + A.getIntance());
			}
		});
		
		t1.start();
		t2.start();
	}
}

缺点:如果只调用了类里的静态方法,没用到单例对象,就是浪费空间

java 复制代码
public class Test03 {
	public static void main(String[] args) {
		
		A.method();
	}
}

注意:在工作中,都不会使用懒汉式,饿汉式

3.线程安全 -- 枚举单例模式(饿汉式)

理解:该类的对象在整个项目中只创建一次(只实例化一次)

把上面那个饿汉式的class改成enum

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		A a1 = A.getIntance();
		A a2 = A.getIntance();
		A a3 = A.getIntance();
		A a4 = A.getIntance();
		
		System.out.println(a1);
		System.out.println(a2);
		System.out.println(a3);
		System.out.println(a4);
	}
}
java 复制代码
public enum A {
	//public static final A a = new A();
	a;

	private A(){}
	
	public static A getIntance(){
		return a;
	}
	
	public static void method(){
		System.out.println("好好学习");
	}
	
	@Override
	public String toString() {
		return String.valueOf(a.hashCode());
	}
}

注意:枚举单例模式(饿汉式)是线程安全的

java 复制代码
public class Test02 {
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程1:" + A.getIntance());
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程2:" + A.getIntance());
			}
		});
		
		t1.start();
		t2.start();
	}
}

缺点:如果只调用了枚举里的静态方法,没用到单例对象,就是浪费空间

java 复制代码
public class Test03 {
	public static void main(String[] args) {
		
		A.method();
	}
}

4.线程安全 -- 双重检验锁的单例模式

理解:该类的对象在整个项目中只创建一次(只实例化一次)

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		A a1 = A.getIntance();
		A a2 = A.getIntance();
		A a3 = A.getIntance();
		A a4 = A.getIntance();
		
		System.out.println(a1);
		System.out.println(a2);
		System.out.println(a3);
		System.out.println(a4);
	}
}

volatile -- 防止指令重排
创建对象的过程:

a.开辟空间 ----- new 对象() -- 0x001

b.调用构造方法 -- 初始化数据

c.将空间赋值给引用 -- 类型 引用 = 0x001

创建对象的步骤:a/b/c 或 a/c/b
注意:如果创建对象的步骤是a/c/b,多线程的情况下可能会导致获取的属性为null

解决方案:使用volatile,防止指令重排,创建的步骤必须按照a/b/c

java 复制代码
public class A {
	private static volatile A a;

	private A(){}
	
	public static A getIntance(){
        //项目中一般使用这个方法
        //这个方法比下一个方法效率更高
        //检测是否为空
		if(a == null){
            //加锁
			synchronized (A.class) {
				if(a == null){
					a = new A();
				}
			}
		}
		return a;
	}

//	public static A getIntance(){
//		
//		if(a != null){
//			return a;
//		}
//		synchronized (A.class) {
//			if(a == null){
//				a = new A();
//			}
//		}
//		return a;
//	}
}

注意:双重检验锁的单例模式是线程安全的

java 复制代码
public class Test02 {
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程1:" + A.getIntance());
			}
		});
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				System.out.println("线程2:" + A.getIntance());
			}
		});
		
		t1.start();
		t2.start();
	}
}

5.线程安全 -- ArrayList

前言:ArrayList是线程不安全的集合
解决方案1:使用Vector -- synchronized锁(不可取)
解决方案2:使用Collections的synchronizedList方法将ArrayList转换为线程安全的集合 -- synchronized锁(不可取)

ArrayList list = new ArrayList<>();

List synchronizedList = Collections.synchronizedList(list);
解决方案3:使用CopyOnWriteArrayList -- lock锁

CopyOnWriteArrayList list = new CopyOnWriteArrayList<>();

list.add("aaa");

list.add("bbb");

list.add("ccc");

list.add("ddd");
总结:Vector和synchronizedList()底层使用synchronized(重量级锁),效率很低。项目中推荐使用CopyOnWriteArrayList

6.线程安全 -- 死锁

注意:多个线程中的多个锁对象被互相占用

死锁只是有概率发生
解决方案:尽可能的不要使用锁嵌套

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		Thread t1 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (KuaiZi.a) {
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (KuaiZi.b) {
						System.out.println("哲学家1吃饭饭");
					}
				}
			}
		}, "哲学家1");
		
		Thread t2 = new Thread(new Runnable() {
			@Override
			public void run() {
				synchronized (KuaiZi.b) {
					try {
						Thread.sleep(1);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					synchronized (KuaiZi.a) {
						System.out.println("哲学家2吃饭饭");
					}
				}
			}
		}, "哲学家2");
		
		t1.start();
		t2.start();
		
	}
}

class KuaiZi{ //筷子
	public static Object a = new Object();
	public static Object b = new Object();
}

7.线程安全 -- 可重入锁

理解:

就是一个线程不用释放,可以重复的获取一个锁n次,只是在释放的时候,也需要相应的释放n次。

简单来说:A线程在某上下文中获取了某锁,当A线程想要再次获取该锁时,不会因为锁已经被自己占用,而需要先等到锁的释放。
不是上第二次锁,而是在对象上做标记,哪个线程在用,如果还是这个线程就继续用注意:
synchronized同步代码块是可重入锁

synchronized同步方法是可重入锁

Lock锁是可重入锁

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
        //Task01 task = new Task01();
        //Task02 task = new Task02();
		Task03 task = new Task03();
		
		Thread t = new Thread(task);
		t.start();
	}
}
java 复制代码
public class Task01 implements Runnable{

    //同步代码块
	@Override
	public void run() {
        //this -->Task01 task = new Task01()里面的task
		synchronized (this) {
			System.out.println("获取到锁对象:" + this);
			//不是上第二次锁,而是在对象上做标记,哪个线程在用,如果还是这个线程就继续用
			synchronized (this) {
				System.out.println("获取到锁对象:" + this);
				
				System.out.println("释放锁对象:" + this);
			}
			
			System.out.println("释放锁对象:" + this);
		}
	}
}

方法

java 复制代码
public class Task02 implements Runnable{

	@Override
	public void run() {
		method1();
	}
	
    //方法
	public synchronized void method1(){
		System.out.println("获取到锁对象:" + this);
		method2();
		System.out.println("释放锁对象:" + this);
	}
	
	public synchronized void method2(){
		System.out.println("获取到锁对象:" + this);
		
		System.out.println("释放锁对象:" + this);
	}
}

lock锁

java 复制代码
public class Task03 implements Runnable{

	private Lock lock = new ReentrantLock();
	
	@Override
	public void run() {
		
		lock.lock();
		System.out.println("获取到锁对象:" + this);
		lock.lock();
		System.out.println("获取到锁对象:" + this);
		lock.unlock();
		System.out.println("释放锁对象:" + this);
		lock.unlock();
		System.out.println("释放锁对象:" + this);
	}
	
	public synchronized void method1(){
		System.out.println("获取到锁对象:" + this);
		method2();
		System.out.println("释放锁对象:" + this);
	}
	
	public synchronized void method2(){
		System.out.println("获取到锁对象:" + this);
		
		System.out.println("释放锁对象:" + this);
	}
}

8.生产者消费者模型

最终目的:生产一个消费一个
分析:

产品类 -- Phone(brand、price)

生产者线程 -- Produce

消费者线程 -- Consumer
为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库

生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为

消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为
Object类的等待和唤醒方法

wait():导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法

notify():唤醒正在等待对象监视器的单个线程

notifyAll():唤醒正在等待对象监视器的所有线程

8.1 一个生产者一个消费者的情况

注意:一个生产者一个消费者的情况
步骤:

1.生产者线程、消费者线程不断的操作同一个产品对象(死循环)

脏数据:

​ null -- 0.0

​ 华为 -- 0.0

2.两个产品之间来回切换(目的:放大步骤1的问题)

脏数据:

​ null -- 0.0

​ 华为 -- 0.0

​ 小米 -- 3999.0

​ 华为 -- 1999.0

脏数据出现原因:设置完品牌后,还没来得及设置价格,就被消费者线程抢到CPU资源

解决方案:生产者线程设置完品牌+价格后,消费者线程才能执行自己的代码 -- 加锁(这两个要互斥)

互斥:两个事件不能同时发生

(生产者,消费者都加锁,互斥住)

3.生产一个消费一个

产品类加一个库存的属性 -- boolean store = false

生产者线程在每次生产之前都得判断是否有库存,有就等待(等待消费者线程消费后再生产),没有就生产
消费者线程在每次消费之前都得判断是否有库存,有就消费,没有就等待(等待生产者线程生产后再消费)
生产者线程和消费者线程互相唤醒
wait()等待:

1.释放锁资源

2.在对象监视器(phone)中记录当前线程被等待(阻塞)

3.当前线程进入到阻塞状态
notify()唤醒:唤醒对象监视器中任意一个线程

java 复制代码
public class Phone {
	
	private String brand;
	private double price;
	private boolean store;//库存
	
	//无参构造,有参构造,get,set方法省略

	@Override
	public String toString() {
		return "Phone [brand=" + brand + ", price=" + price + "]";
	}
}

测试类

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		Phone phone = new Phone();
		
		Produce p = new Produce(phone);
		Consumer c = new Consumer(phone);
		
		p.start();
		c.start();
	}
}

生产者线程

第一次进入生产者线程,没有生产出产品,走不了上面的if循环;就只能走对象监视器,去生产一个产品;

第二次进入生产者线程,就有了产品,进入上面的if循环,就wait等待了,当前线程进入到阻塞状态,然后走消费者线程,有库存就唤醒对象监视器中任意一个线程,解锁

然后就是生产者线程,消费者线程去抢资源

java 复制代码
//生产者线程
public class Produce extends Thread{
	
	private Phone phone;
	
	public Produce(Phone phone) {
		this.phone = phone;
	}

	@Override
	public void run() {
		
		boolean flag = true;
		
		while(true){
			synchronized(phone){
				
				if(phone.isStore()){//true就等待,没有就生产
					try {
						/**
						 * 等待:
						 * 		1.释放锁资源
						 * 		2.在对象监视器(phone)中记录当前线程被等待(阻塞)
						 * 		3.当前线程进入到阻塞状态
						 */
						phone.wait();//phone记录生产者线程阻塞
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
                
				//对象监视器
				if(flag){
					phone.setBrand("华为");
					phone.setPrice(3999);
				}else{
					phone.setBrand("小米");
					phone.setPrice(1999);
				}
				flag = !flag;
				phone.setStore(true);
				
				phone.notify();//唤醒:唤醒对象监视器中任意一个线程
			}
		}
	}
}

消费者线程

java 复制代码
//消费者线程
public class Consumer extends Thread{
	
	private Phone phone;
	
	public Consumer(Phone phone) {
		this.phone = phone;
	}

	@Override
	public void run() {
		while(true){
			synchronized(phone){
				
				if(!phone.isStore()){ //有库存就不进入,没有库存就等待
					try {
						/**
						 * 等待:
						 * 		1.释放锁资源
						 * 		2.在对象监视器(phone)中记录当前线程被等待(阻塞)
						 * 		3.当前线程进入到阻塞状态
						 */
						phone.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				System.out.println(phone.getBrand() + " -- " + phone.getPrice());
				phone.setStore(false);
				
				phone.notify();//唤醒:唤醒对象监视器中任意一个线程
				
			}
		}
	}
}

多个线程去操作同一资源,多个线程的功能是一样的,就使用买票的案例去套

多个线程去操作同一资源,多个线程的功能是不一样的,就使用生产者消费者模型或仓储模型去套

8.2 多个生产者多个消费者的情况

注意:多个生产者多个消费者的情况
多个的情况下,不用if判断,而是用while判断
notifyAll():唤醒:唤醒对象监视器中所有线程

java 复制代码
public class Test01 {	
	public static void main(String[] args) {
		
		Phone phone = new Phone();
		
		Produce p1 = new Produce(phone);
		Produce p2 = new Produce(phone);
		Consumer c1 = new Consumer(phone);
		Consumer c2 = new Consumer(phone);
		
		p1.start();
		p2.start();
		c1.start();
		c2.start();
	}
}

手机类

java 复制代码
public class Phone {
	
	private String brand;
	private double price;
	private boolean store;//库存
	
	//无参构造,有参构造,get,set方法省略

	@Override
	public String toString() {
		return "Phone [brand=" + brand + ", price=" + price + "]";
	}
}

生产者线程

java 复制代码
//生产者线程
public class Produce extends Thread{
	
	private Phone phone;
	
	public Produce(Phone phone) {
		this.phone = phone;
	}
	
	private static boolean flag = true;
	@Override
	public void run() {
		//多个的情况下,不用if判断,而是用while判断
		while(true){
			synchronized(phone){
				
				while(phone.isStore()){
					try {
						/**
						 * 等待:
						 * 		1.释放锁资源
						 * 		2.在对象监视器(phone)中记录当前线程被等待(阻塞)
						 * 		3.当前线程进入到阻塞状态
						 */
						phone.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				if(flag){
					phone.setBrand("华为");
					phone.setPrice(3999);
				}else{
					phone.setBrand("小米");
					phone.setPrice(1999);
				}
				flag = !flag;
				phone.setStore(true);
				
				phone.notifyAll();//唤醒:唤醒对象监视器中所有线程
			}
		}
	}
}

消费者线程

java 复制代码
//消费者线程
public class Consumer extends Thread{
	
	private Phone phone;
	
	public Consumer(Phone phone) {
		this.phone = phone;
	}

	@Override
	public void run() {
		while(true){
			synchronized(phone){
				
				while(!phone.isStore()){//有库存就不进入
					try {
						/**
						 * 等待:
						 * 		1.释放锁资源
						 * 		2.在对象监视器(phone)中记录当前线程被等待(阻塞)
						 * 		3.当前线程进入到阻塞状态
						 */
						phone.wait();
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
				
				System.out.println(phone.getBrand() + " -- " + phone.getPrice());
				phone.setStore(false);
				
				phone.notifyAll();//唤醒:唤醒对象监视器中所有线程
				
			}
		}
	}
}

9.仓储模型

需求:

生产者线程不断的生产蛋糕(将蛋糕对象添加到仓库)

消费者线程不断的消费蛋糕(将蛋糕对象从仓库中取出)

先生产的蛋糕,先卖出
分析:

1.蛋糕类、仓库类、生产者线程、消费者线程

2.仓库类里有一个存放蛋糕对象的集合 -- LinkedList(队列模式--先进先出,removeFirst遍历)
仓储模型与消费者模型不同的地方:

生产者消费者模型:重点加锁的逻辑放在线程中

仓储模型:重点加锁的逻辑是放在线程外

9.1 一个生产者线程一个消费者线程的情况

注意:一个生产者线程一个消费者线程的情况
意识:多个线程抢资源势必会出现线程安全问题;多个线程使用同一资源,必须加锁!!!锁对象是谁,对象监视器就可以用谁

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		Store store = new Store(20);
		
		Produce p = new Produce(store);
		Consumer c = new Consumer(store);
		
		p.start();
		c.start();
	}
}

蛋糕类

java 复制代码
public class Cake {

	private String brand;
	private String datetime;
	
	//无参构造,有参构造,get,set方法省略

	@Override
	public String toString() {
		return "Cake [brand=" + brand + ", datetime=" + datetime + "]";
	}
}

仓库类

java 复制代码
//仓库类
public class Store {
	
	private int curCapacity;//当前容量
	private int maxCapacity;//最大容量
	private LinkedList<Cake> list;//蛋糕容器
	
	public Store(int maxCapacity) {
		this.maxCapacity = maxCapacity;
		list =  new LinkedList<>();//初始化蛋糕容器
	}
	
	//入库(此方法被生产者线程不断的调用)
	public synchronized void push(Cake cake){
		if(curCapacity >= maxCapacity){//当前容量>最大容量
			try {
               //锁对象是谁,对象监视器就可以用谁
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		list.add(cake);
		curCapacity++;
		System.out.println("入库 - 当前容量为:" + curCapacity);
		this.notify();
	}
	
	
	//出库(此方法被消费者线程不断的调用)
	public synchronized Cake pop(){//删除
		if(curCapacity <= 0){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		Cake cake = list.removeFirst();
		curCapacity--;
		System.out.println("出库 - 当前容量为:" + curCapacity + ",卖出的蛋糕为:" + cake);
		this.notify();
		return cake;
	}
	
}

生产者线程

java 复制代码
public class Produce extends Thread{

	private Store store;

	public Produce(Store store) {
		this.store = store;
	}
	
	@Override
	public void run() {//生产
		while(true){
			Cake cake = new Cake("桃李", LocalDateTime.now().toString());
			store.push(cake);
		}
	}
}

消费者线程

java 复制代码
public class Consumer extends Thread{
	
	private Store store;
	
	public Consumer(Store store) {
		this.store = store;
	}

	@Override
	public void run() {
		while(true){
			store.pop();
		}
	}
}

9.2 多个生产者线程多个消费者线程的情况

注意:多个生产者线程多个消费者线程的情况
多个的情况下,不用if判断,而是把其中的if判断换成while判断和使用notifyAll()

java 复制代码
public class Test01 {
	public static void main(String[] args) {
		
		Store store = new Store(20);
		
		Produce p1 = new Produce(store);
		Produce p2 = new Produce(store);
		Consumer c1 = new Consumer(store);
		Consumer c2 = new Consumer(store);
		
		p1.start();
		p2.start();
		c1.start();
		c2.start();
	}
}

蛋糕类

java 复制代码
public class Cake {

	private String brand;
	private String datetime;
	
	//无参构造,有参构造,get,set方法省略

	@Override
	public String toString() {
		return "Cake [brand=" + brand + ", datetime=" + datetime + "]";
	}
}

仓库类

java 复制代码
//仓库类
public class Store {
	
	private int curCapacity;//当前容量
	private int maxCapacity;//最大容量
	private LinkedList<Cake> list;//蛋糕容器
	
	public Store(int maxCapacity) {
		this.maxCapacity = maxCapacity;
		list =  new LinkedList<>();
	}
	
	//入库(此方法被生产者线程不断的调用)
	public synchronized void push(Cake cake){
		while(curCapacity >= maxCapacity){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		list.add(cake);
		curCapacity++;
		System.out.println("入库 - 当前容量为:" + curCapacity);
		this.notifyAll();
	}
	
	//出库(此方法被消费者线程不断的调用)
	public synchronized Cake pop(){
		while(curCapacity <= 0){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		Cake cake = list.removeFirst();
		curCapacity--;
		System.out.println("出库 - 当前容量为:" + curCapacity + ",卖出的蛋糕为:" + cake);
		this.notifyAll();
		return cake;
	}
	
}

生产者线程

java 复制代码
public class Produce extends Thread{

	private Store store;

	public Produce(Store store) {
		this.store = store;
	}
	
	@Override
	public void run() {
		while(true){
			Cake cake = new Cake("桃李", LocalDateTime.now().toString());
			store.push(cake);
		}
	}
}

消费者线程

java 复制代码
public class Consumer extends Thread{
	
	private Store store;
	
	public Consumer(Store store) {
		this.store = store;
	}

	@Override
	public void run() {
		while(true){
			store.pop();
		}
	}
}

加锁一般使用Lock,不用synchronized,因为效率低

简答题

volatile关键字的作用?

一旦一个共享变量(类的成员变量、类的静态成员变量)被volatile修饰之后,那么就具备了两层语义:

1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

2.禁止进行指令重排序。

volatile本质是在告诉jvm当前变量在寄存器(工作内存)中的值是不确定的,需要从主存中读取;synchronized则是锁定当前变量,只有当前线程可以访问该变量,其他线程被阻塞住。

volatile仅能使用在变量级别;synchronized则可以使用在变量、方法、和类级别的。

volatile仅能实现变量的修改可见性,并不能保证原子性;synchronized则可以保证变量的修改可

见性和原子性。

volatile不会造成线程的阻塞;synchronized可能会造成线程的阻塞。

volatile标记的变量不会被编译器优化;synchronized标记的变量可以被编译器优化。

使用 Volatile 一般用于 状态标记量 和 单例模式的双检锁
2.notify()和notifyAll()有什么区别?

notify可能会导致死锁,而notifyAll则不会任何时候只有一个线程可以获得锁,也就是说只有一个线程可以运行synchronized 中的代码 使用notifyall,可以唤醒 所有处于wait状态的线程,使其重新进入锁的争夺队列中,而notify只能唤醒一个。

notify() 是对notifyAll()的一个优化,但它有很精确的应用场景,并且要求正确使用。不然可能导致死锁。正确的场景应该是 WaitSet中等待的是相同的条件,唤醒任一个都能正确处理接下来的事项,如果唤醒的线程无法正确处理,务必确保继续notify()下一个线程,并且自身需要重新回到WaitSet中
3.为什么wait, notify和notifyAll这些方法不在thread类里面?

明显的原因是JAVA提供的锁是对象级的而不是线程级的,每个对象都有锁,通过线程获得。如果线程需要等待某些锁那么调用对象中的wait()方法就有意义了。如果wait()方法定义在Thread类中,线程正在等待的是哪个锁就不明显了。简单的说,由于wait,notify和notifyAll都是锁级别的操作,所以把他们定义在Object类中因为锁属于对象。
4.为什么wait和notify方法要在同步块中调用?

1.只有在调用线程拥有某个对象的独占锁时,才能够调用该对象的wait(),notify()和notifyAll()方法。

2.如果你不这么做,你的代码会抛出IllegalMonitorStateException异常。

3.还有一个原因是为了避免wait和notify之间产生竞态条件。

wait()方法强制当前线程释放对象锁。这意味着在调用某对象的wait()方法之前,当前线程必须已经获得该对象的锁。因此,线程必须在某个对象的同步方法或同步代码块中才能调用该对象的wait()方法。

在调用对象的notify()和notifyAll()方法之前,调用线程必须已经得到该对象的锁。因此,必须在某个对象的同步方法或同步代码块中才能调用该对象的notify()或notifyAll()方法。

调用wait()方法的原因通常是,调用线程希望某个特殊的状态(或变量)被设置之后再继续执行。调用 notify()或notifyAll()方法的原因通常是,调用线程希望告诉其他等待中的线程:"特殊状态已经被设置"。这个状态作为线程间通信的通道,它必须是一个可变的共享状态(或变量)。
5.乐观锁与悲观锁

1.何谓悲观锁与乐观锁

悲观锁:总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这

样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后 再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中 synchronized 和 ReentrantLock 等独占锁就是悲观锁思想的实现。

乐观锁:总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多 读的应用类型,这样可以提高吞吐量 ,像数据库提供的类似于write_condition机制,其实都是提供的 乐观锁。在Java中 java.util.concurrent.atomic 包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。

2.两种锁的使用场景

从上面对两种锁的介绍,我们知道两种锁各有优缺点,不可认为一种好于另一种,像乐观锁适用于写比 较少的情况下(多读场景) ,即冲突真的很少发生的时候,这样可以省去了锁的开销,加大了系统的整个吞吐量。但如果是多写的情况,一般会经常产生冲突,这就会导致上层应用会不断的进行retry,这样反倒是降低了性能,所以一般多写的场景下用悲观锁就比较合适。

总结

1.线程安全 --- 单例模式

懒汉式

饿汉式

枚举饿汉式

双重检测单例模式 --- (项目中使用,注意volatile的含义)
2.线程安全 --- ArrayList

Vector --- synchronized

Collections.synchronizedList() --- synchronized

CopyOnWriteArrayList --- Lock

3.可重入锁
4.死锁

5.生产者消费者模型 -- wait()/notify()/notifyAll()
6.仓储模型 -- wait()/notify()/notifyAll()
作业:

1.知识点梳理文档

2.代码3遍

3.生产者消费者模型、仓储模型默写一遍(拿一张纸来写,笔试考过)

4.提升作业(至少选择两道):

可重入锁和不可重入锁的md文档(概念、代码)

乐观锁和悲观锁的md文档(概念)

公平锁和非公平锁的md文档(概念)

互斥和共享锁的md文档(概念)

synchronized锁的升级和膨胀的md(概念、代码)

CAS和CAS的ABA问题(概念、代码)

AQS的md文档(概念、代码)

相关推荐
西岸行者5 天前
学习笔记:SKILLS 能帮助更好的vibe coding
笔记·学习
悠哉悠哉愿意5 天前
【单片机学习笔记】串口、超声波、NE555的同时使用
笔记·单片机·学习
别催小唐敲代码5 天前
嵌入式学习路线
学习
毛小茛6 天前
计算机系统概论——校验码
学习
babe小鑫6 天前
大专经济信息管理专业学习数据分析的必要性
学习·数据挖掘·数据分析
winfreedoms6 天前
ROS2知识大白话
笔记·学习·ros2
在这habit之下6 天前
Linux Virtual Server(LVS)学习总结
linux·学习·lvs
我想我不够好。6 天前
2026.2.25监控学习
学习
im_AMBER6 天前
Leetcode 127 删除有序数组中的重复项 | 删除有序数组中的重复项 II
数据结构·学习·算法·leetcode
CodeJourney_J6 天前
从“Hello World“ 开始 C++
c语言·c++·学习