首先看下 饿汉式 单例模式
/**
* 饿汉式 单例模式
*/
public class MyThread {
//初始化就 new 一个
static final MyThread myThread = new MyThread();
//定义为私有,不允许再new
private MyThread(){}
//直接把生成好的给他
public static MyThread getInstance(){
return myThread;
}
public static void main(String[] args) {
MyThread m1 = MyThread.getInstance();
MyThread m2 = MyThread.getInstance();
System.out.println(m1 == m2);
}
}
打印结果为true。
使用volatile 和synchronized 修改
public class MyThread {
static volatile MyThread myThread;
//定义为私有,不允许再new
private MyThread(){}
/**
* 直接锁定该方法块
* 即使多线程并发,同时也只能有一个线程进入
* @return
*/
public static synchronized MyThread getInstance(){
if(myThread == null){
myThread = new MyThread();
}
return myThread;
}
/**
* double check 防止多线程并发同时进入
* @return
*/
public static MyThread getInstance2(){
if(myThread == null){
synchronized (MyThread.class){
if(myThread == null){
myThread = new MyThread();
}
}
}
return myThread;
}
new 对象的过程: 3 步。 至于为什么是3步,有兴趣的可以百度下汇编的 new 对象的代码。这里理解为汇编语言需要3步。
synchronized :原子性
volatile : 可见性
不管是new 对象还是 i++ 或者其它,都不是原子性的。加了synchronized 的方法或块,只能说方法或块中的代码是原子性的。
被synchronized 修饰的方法或块,同一时间不能被其它线程执行。但是其它线程可以执行其它方法,或者调用对象成员变量。
public boolean i = true;
public void setI100(){
while (i){
}
System.out.println(" end");
}
public static void main(String[] args) {
MyThread my = new MyThread();
new Thread(my::setI100,"my").start();
try {
Thread.sleep(1000);
my.i = false;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(my.i);
}
结果:不会打印end
如上代码,线程 运行 setI100 方法,由于取到的是true,所以一直在循环,而这时, main 线程中已经修改为false,但是setI100 方法不知道,还是之前的。所以会一直循环。
public volatile boolean i = true;
这时加上如上代码,就会打印出end。
volatile : 可见性,防止代码重排序。
可见性实现:缓存一致性协议 MSI 禁止指令重排实现:JMM,内存屏障
-------------------------------------------------
如果synchronized 修饰对象,被synchronized 锁定的对象必须是final 的。
public class MyThread {
public Object object = new Object();
void m(){
synchronized (object){
try {
TimeUnit.MICROSECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) {
MyThread my = new MyThread();
new Thread(my::m,"my").start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Thread tw = new Thread(my::m,"tw");
my.object = new Object();
tw.start();
}
}
如上代码,如果在main 中修改了 object 代码。
根据synchronized 锁的机制,锁的是 Object 对象这个 mark word 中的两位标识,但是main已经改变了object,所以锁的已经不同同一个对象了。
这时候应该在object 前加一个 final ,禁止被修改。
CAS 自旋锁(无锁优化,自旋)
compare and set:比较和设值。保存旧值,更改后保存前,对比下,如果值不一致,则进行再循环。
CAS 是cpu 原语支持的,指令级操作,不能被打断。
1.synchronized 内置的java关键字。Lock是一个java接口,有三个实现类,ReentrantLock、ReentrantReadWriteLock(读写锁)、ReentrantReadWriteLock.WriteLock(写锁)
2.synchronized无法判断获取锁的状态,Lock可以判断是否获取到了锁
3.synchronized 会自动释放锁,lock必须手动释放
4.Synchronized 适合锁少量的代码同步问题,lock适合锁大量的同步代码。