31_2线程安全 【单例模式:懒汉式、饿汉式、枚举饿汉式、双重检测单例模式(volatile)】、ArrayList、死锁、可重入锁

本文详细介绍了Java中的单例模式(懒汉式、饿汉式和双重检查锁定),强调了线程安全的重要性,并探讨了ArrayList、Vector和CopyOnWriteArrayList在多线程环境下的应用,以及可重入锁的概念和死锁问题的预防。
摘要由CSDN通过智能技术生成

day31下

线程安全

单例模式

概念

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

懒汉式
public class A {
	
	private static A a;

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

任意输出:(可见是同一个对象)

com.qf.thread03_type01.A@15db9742
com.qf.thread03_type01.A@15db9742
com.qf.thread03_type01.A@15db9742
com.qf.thread03_type01.A@15db9742

单例模式(懒汉式)不是线程安全的
public class A {
	
	private static A a;

	private A(){}
	
	public static A getIntance(){
		if(a == null){
//         t1抢到cpu资源,还未new对象,时间片到,退出;t2抢到资源,new对象,时间片到,退出   
//         t1再次抢到cpu资源,又new了一个对象,导致不是同一个对象
			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();
	}
}

多运行几次,任意输出:(可见不一定同一个对象)

线程1:com.qf.thread03_type01.A@3e9041fa
线程2:com.qf.thread03_type01.A@962cd83

饿汉式

加载class文件,静态属性在静态区开空间,初始化new对象,就已经是一个对象了

public class A {
	
	private static A a = new A();

	private A(){}
	
	public static A getIntance(){
		return a;
	}
}
单例模式(饿汉式)是线程安全的

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

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 Test03 {
	public static void main(String[] args) {
		
		A.method();
	}
}

单例模式举例

枚举单例模式(饿汉式)

是线程安全的

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

回顾:枚举知识点

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());
	}
}
双重检验锁的单例模式

(项目中使用的单例模式!!!)

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

public class A {

	//volatile -- 防止指令重排
	
	/**
	 * 创建对象的过程:
	 * 		a.开辟空间  ----- new 对象() -- 0x001
	 * 		b.调用构造方法 -- 初始化数据
	 * 		c.将空间赋值给引用 -- 类型 引用 = 0x001
	 * 
	 * 创建对象的步骤:a/b/c 或  a/c/b
	 * 
	 * 注意:如果创建对象的步骤是a/c/b,多线程的情况下可能会导致获取的属性为null
	 * 解决方案:使用volatile,防止指令重排,创建的步骤必须按照a/b/c
	 */
	
	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;
//	}
}

ArrayList

前言:ArrayList是线程不安全的集合

解决方案1:

使用Vector – synchronized锁

缺点:synchronized锁升级到重量级锁后不可降

解决方案2:

使用Collections的synchronizedList方法将ArrayList转换为线程安全的集合 – synchronized锁

ArrayList<Object> list = new ArrayList<>();

List<Object> synchronizedList = Collections.synchronizedList(list);

解决方案3:

使用CopyOnWriteArrayList – lock锁

底层简述:将数据读到新数组,处理好再返回到原数组

CopyOnWriteArrayList<Object> list = new CopyOnWriteArrayList<>();
	list.add("aaa");
	list.add("bbb");
	list.add("ccc");
	list.add("ddd")

小结:

Vector和synchronizedList()底层使用synchronized(重量级锁),效率很低。项目中推荐使用CopyOnWriteArrayList

死锁

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

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

案例:哲学家吃饭

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

可重入锁

理解:

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

简单来说:A线程在某上下文中获取了某锁,当A线程想要再次获取该锁时,不会因为锁已经被自己占用,而需要先等到锁的释放

注意:

synchronized同步代码块是可重入锁

synchronized同步方法是可重入锁

Lock锁是可重入锁

提升练习:如何实现不可重入锁

//synchronized
public class Task01 implements Runnable{

	@Override
	public void run() {
		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);
	}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值