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