学习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文档(概念、代码)

相关推荐
南宫生4 小时前
力扣-图论-17【算法学习day.67】
java·学习·算法·leetcode·图论
sanguine__4 小时前
Web APIs学习 (操作DOM BOM)
学习
数据的世界016 小时前
.NET开发人员学习书籍推荐
学习·.net
四口鲸鱼爱吃盐6 小时前
CVPR2024 | 通过集成渐近正态分布学习实现强可迁移对抗攻击
学习
OopspoO8 小时前
qcow2镜像大小压缩
学习·性能优化
A懿轩A9 小时前
C/C++ 数据结构与算法【栈和队列】 栈+队列详细解析【日常学习,考研必备】带图+详细代码
c语言·数据结构·c++·学习·考研·算法·栈和队列
居居飒9 小时前
Android学习(四)-Kotlin编程语言-for循环
android·学习·kotlin
kkflash39 小时前
提升专业素养的实用指南
学习·职场和发展
1 9 J10 小时前
数据结构 C/C++(实验五:图)
c语言·数据结构·c++·学习·算法
天使day11 小时前
SpringMVC
java·spring·java-ee