单例模式
特点:一个类只有一个实例
目的:
-
符合某些特定的业务需求,如:学校中只有一个校长
-
节约系统资源,如:Spring中的Bean默认是单例
实例步骤:
1. 把构造方法私有化
2. 定义静态的实例
3. 定义方法用于创建对象并返回实例
分类:
饿汉式
/**
* 饿汉式单例
*/
public class HungrySingleton {
//2. 定义静态的实例
private static HungrySingleton instance = new HungrySingleton();
//1. 私有化构造方法
private HungrySingleton(){
System.out.println("创建对象");
}
//3. 静态方法返回实例
public static HungrySingleton getInstance(){
return instance;
}
}
懒汉式
/**
* 懒汉式单例
*/
public class LazySingleton {
//2. 定义静态实例
private static LazySingleton instance = null;
//1. 私有构造方法
private LazySingleton(){
System.out.println("创建实例");
}
//3. 静态方法创建对象并返回
public static LazySingleton getInstance(){
if(instance == null){
instance = new LazySingleton();
}
return instance;
}
}
对比饿汉式和懒汉式:
饿汉式代码更加简洁
懒汉式更加节约内存(调用返回实例方式时才创建对象)
饿汉式没有线程安全问题
线程安全问题
多个线程同时访问一个资源(变量、代码块、数据等),可能出现线程同步问题
多个线程的执行是抢占式的,一个线程在执行一系列程序指令中途时,就被其它线程抢占,可能导致数据状态出现问题。
解决方式:锁机制
锁机制
synchronized 关键字
实现锁机制,可以用于修饰方法或代码块
提升数据的安全性,降低性能
同步方法
语法:
public synchronized 修饰符 方法名(...){
...
}
当第一个线程进入方法后,自动上锁,其它线程访问不了,前面线程执行完方法后,自动释放锁,其它线程就可以访问
同步代码块
语法:
synchronized(锁对象){
....
....
}
锁对象:
1. 任意的Java对象都可以作为锁
2. 不能是局部变量
相关问题
1.同步方法和同步代码块的区别
1) 语法不同,一个写在方法上,一个写在方法内部
2) 锁粒度不同,同步方法作用于整个方法,同步代码块作用于一段代码
3) 性能不同,同步方法低于同步块
4) 锁对象不同,同步方法是固定的,静态就是类.class,非静态的就是this
同步代码块可以指定锁对象
2.synchronized 锁机制的原理
通过监视器(monitor)完成
当对方法或一段代码上锁后,会启动监视器对这段代码监控,监视器中有计数器,当计数器为0时,允许线程进入,线程进入后,计数器加1,其它线程访问时,计数器不为0,不允许线程进入,线程执行完代码块后,计数器减1为0 ,监视器允许其它线程进入。
monitorenter
....
....
monitorexit
3.线程安全的单例模式
/**
* 懒汉式单例
*/
public class LazySingleton {
//2. 定义静态实例
private static LazySingleton instance = null;
//1. 私有构造方法
private LazySingleton(){
}
//3. 静态方法创建对象并返回
public static LazySingleton getInstance(){
//双检锁 DCL double check lock
//提升性能,不为空,就不执行同步块
if(instance == null) {
//同步代码块
synchronized (LazySingleton.class) {
//判断对象为空,再创建,整体执行
if (instance == null) {
instance = new LazySingleton();
}
}
}
return instance;
}
}
同步锁
在并发包 java.concurrent.lock下存在大量的锁
Lock接口
lock() 上锁
unlock() 解锁
常见的实现类:
-
ReentrantLock 重入锁
-
ReadLock 读锁
-
WriterLock 写锁
语法:
Lock lock = new ReentrantLock(); //成员变量
方法内:
try{
lock.lock();
同步的代码
}finally{
lock.unlock();
}
构造方法可以带参数
new ReentrantLock(是否是公平锁);
相关问题
1) 什么是公平锁和非公平锁?
公平锁:等待锁时间长的线程,更容器获得锁
非公平锁:等待锁时间长和短的线程,获得锁几率相同
2) synchronized和同步锁的区别?
上锁:synchronized是自动上锁和解锁,同步锁是手动完成的
性能:同步锁的性能高于synchronized
使用:synchronized使用简单,功能单一,同步锁提供更多方法,使用灵活
对比三种锁机制:
粒度: 同步方法 > 同步块/同步锁
性能: 同步锁 > 同步块 > 同步方法
方便: 同步方法 > 同步块 > 同步锁
volatile关键字
线程同步有三大特性:
-
原子性(一系列指令要么全部执行,要么全部不执行)
-
可见性(线程中的数据修改后,其它线程也可以读取最新的数据)
-
有序性(线程中的指令按顺序执行,不会出现乱序)
volatile用于修饰成员变量,保证变量的可见性和有序性
相关问题
1) 讲讲悲观锁和乐观锁
悲观锁:比较悲观,认为线程同步问题会经常出现,倾向于给资源上锁
乐观锁:比较乐观,认为线程同步问题不会常出现,不给资源上锁,通过其它方式解决
版本号机制,对数据设置版本,每次修改后版本会更新,修改后将版本和前面版本进行比较,相同就提交,不同就不提交
CAS机制,CompareAndSwap
通过内存偏移量获得数据的原始值,通过原始值计算出预期的值,将预期的值和实际的值进行比较,相同就更新,否则就不更新进入循环等待状态,直到比较相等
如果线程的竞争比较激烈,应该使用悲观锁;
线程竞争不强的时候,使用乐观锁。