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

Day31

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

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

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);
	}
}
public class A {
	
	private static A a;//私有化属性

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

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

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.线程安全 – 枚举单例模式(饿汉式)

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

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);
	}
}
public class A {
	
	private static A a = new A();

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

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

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();
	}
}

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

public class Test03 {
	public static void main(String[] args) {
		
		A.method();
	}
}

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

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

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

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

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);
	}
}
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());
	}
}

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

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();
	}
}

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

public class Test03 {
	public static void main(String[] args) {
		
		A.method();
	}
}

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

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

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

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;
//	}
}

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

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.线程安全 – 死锁

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

死锁只是有概率发生

解决方案:尽可能的不要使用锁嵌套

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锁是可重入锁

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();
	}
}
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);
		}
	}
}

方法

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锁

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()唤醒:唤醒对象监视器中任意一个线程

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

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

测试类

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等待了,当前线程进入到阻塞状态,然后走消费者线程,有库存就唤醒对象监视器中任意一个线程,解锁

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

//生产者线程
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();//唤醒:唤醒对象监视器中任意一个线程
			}
		}
	}
}

消费者线程

//消费者线程
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():唤醒:唤醒对象监视器中所有线程

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();
	}
}

手机类

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

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

生产者线程

//生产者线程
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();//唤醒:唤醒对象监视器中所有线程
			}
		}
	}
}

消费者线程

//消费者线程
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 一个生产者线程一个消费者线程的情况

注意:一个生产者线程一个消费者线程的情况

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

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();
	}
}

蛋糕类

public class Cake {

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

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

仓库类

//仓库类
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;
	}
	
}

生产者线程

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);
		}
	}
}

消费者线程

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()

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();
	}
}

蛋糕类

public class Cake {

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

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

仓库类

//仓库类
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;
	}
	
}

生产者线程

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);
		}
	}
}

消费者线程

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修饰之后,那么就具备了两层语义:
禁止进行指令重排序。

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.synchronized 和 Lock 有什么区别?

synchronized 可以给类、方法、代码块加锁;而 lock 只能给代码块加锁。

synchronized 不需要手动获取锁和释放锁,使用简单,发生异常会自动释放锁,不会造成死锁;而 lock 需要自己加锁和释放锁,如果使用不当没有 unLock()去释放锁就会造成死锁。

通过 Lock 可以知道有没有成功获取锁,而 synchronized 却无法办到。

6.在 Java 程序中怎么保证多线程的运行安全?

一:使用安全类,比如 Java. util. concurrent 下的类。

二:使用自动锁 synchronized。

三:使用手动锁 Lock。

7.请说出与线程同步以及线程调度相关的方法。

(1) wait():使一个线程处于等待(阻塞)状态,并且释放所持有的对象的锁;

(2)sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此 方法要处理InterruptedException 异常;

(3)notify():唤醒一个处于等待状态的线程,当然在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由 JVM 确定唤醒哪个线程,而且与 优先级无关;

(4)notityAll():唤醒所有处于等待状态的线程,该方法并不是将对象的锁给所 有线程,而是让它们竞争,只有获得锁的线程才能进入就绪状态;

8.同步方法和同步块,哪个是更好的选择?

同步块是更好的选择,因为它不会锁住整个对象(当然你也可以让它锁住整个对象)。同步方法会锁住整个对象,哪怕这个类中有多个不相关联的同步块,这通常会导致他们停止执行并需要等待获得这个对象上的锁。

同步块更要符合开放调用的原则,只在需要锁住的代码块锁住相应的对象,这样从侧面来说也可以避免死锁。

请知道一条原则:同步的范围越小越好。

总结

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

  • 35
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

A 北枝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值