volatile关键字
意思是可变的
作用:
- 保证线程可见性(每次写都会被线程读到)
- MESI:使用CPU的缓存一致性协议
- 禁止指令重排序(CPU)
- DCL单例: Double Check Lock
单例模式加载类对象
饿汉式
package singleton;
/**
* 饿汉式
* 类加载到内存后,就实例化一个实例,JVM保证线程安全
* 简单实用,推荐使用
* 唯一缺点:不管用不用到,类装载的过程就完成了实例化
* Class.forName("")
* @author laimouren
*/
public class singleton01 {
private static final singleton01 INSTANCE = new singleton01();
//构造函数被设置为私有,不允许其他方法实例化该类
private singleton01(){};
public static singleton01 getInstance(){
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
singleton01 s1 = singleton01.getInstance();
singleton01 s2 = singleton01.getInstance();
System.out.println(s1 == s2);
}
}
懒汉式
package singleton;
/**
* 懒加载
* 也称懒汉式
* 用到的时候再加载
*但是线程不安全
* 可以通过synchronized解决,但是效率会下降
* @author laimouren
*/
public class singleton02 {
private static singleton02 INSTANCE;
private singleton02(){};
public static /*synchronized*/ singleton02 getInstance(){
if (INSTANCE == null){
try{
Thread.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
INSTANCE = new singleton02();
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
for (int i = 0; i < 100;i++){
new Thread(()->{
System.out.println(singleton02.getInstance().hashCode());
}).start();
}
}
}
优化:双重检查
package singleton;
/**
* 懒加载
* 也称懒汉式
* 用到的时候再加载
*但是线程不安全
* 可以通过synchronized解决,但是效率会下降
* @author laimouren
*/
public class singleton03 {
private static /*volatile*/ singleton03 INSTANCE;
private singleton03(){};
public static singleton03 getInstance(){
//业务逻辑代码省略
if (INSTANCE == null) {
//双重检查
synchronized (singleton03.class) {
if (INSTANCE == null){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new singleton03();
}
}
}
return INSTANCE;
}
public void m(){
System.out.println("m");
}
public static void main(String[] args) {
for (int i = 0; i < 100;i++){
new Thread(()->{
System.out.println(singleton03.getInstance().hashCode());
}).start();
}
}
}
问题:双重检查需不需要加volatile?
要,因为不加会产生指令重排序,
对象初始化一半的情况下,instance对象有默认值,这时候另一个线程返回了instance,导致出现instance还是默认值的情况
(因为JVM去new一个对象,首先要申请内存(成员变量默认值),然后成员变量初始化,最后将内存地址赋给对象)
volatile能否代替synchronized
不能!
package character02;
import java.util.ArrayList;
import java.util.List;
/**
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是volatile不能替代synchronized
* @author laimouren
*/
public class volatile02 {
volatile int count = 0;
/*synchronized*/ void m(){
for (int i = 0; i < 100000; i++) {
count++;
}
}
public static void main(String[] args) {
volatile02 v = new volatile02();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(v::m,"thread-"+i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try{
o.join();
}catch (InterruptedException e){
e.printStackTrace();
}
});
System.out.println(v.count);
}
}
锁优化
细锁(FineCoarseLock)
同步代码块的语句越少越好
锁细化
package character02;
import java.util.concurrent.TimeUnit;
/**
* 锁细化
* @author laimouren
*/
public class Synchronzied {
private int count = 0;
synchronized void m1(){
try{
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
count++;
try{
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
}
void m2(){
try{
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
//业务逻辑只有下面一句话需要sync,这时候不应该给整个方法上锁
//采用细粒度的锁,可以使线程争用时间变短,从而提高效率
synchronized(this) {
count++;
}
try{
TimeUnit.SECONDS.sleep(2);
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
但是如果太多线程同时争用锁,可以将锁粗化来减少争用
锁对象需加final修饰
package character02;
import java.util.concurrent.TimeUnit;
/**
* 锁定某个对象o,如果o的属性发生改变,不影响锁的使用
* 但是如果o变成了另一个对象,则锁定的对象发生改变
* 应该避免将锁定对象的引用变成另外的对象
* @author laimouren
*/
public class Synchronzied02 {
//使用final来保证对象不可变
/*final*/ Object o = new Object();
void m(){
synchronized (o){
while (true){
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
}
}
}
public static void main(String[] args) {
Synchronzied02 s = new Synchronzied02();
//启动第一个线程
new Thread(s::m,"s1").start();
try {
TimeUnit.SECONDS.sleep(1);
}catch (InterruptedException e){
e.printStackTrace();
}
//创建第二个线程
Thread t2 = new Thread(s::m,"s2");
//锁对象发生改变,所以t2线程能够执行,如果注释掉,t2永远不能执行
s.o = new Object();
t2.start();
}
}
CAS(无锁优化 自旋)
Compare And Swap
cas(V,Expected,NewValue)
- if v == E
V = new
otherwise try again or fail - CPU原语支持
解释:如果v值等于期望(expected)的,则将v修改为newValue值
是cpu指令级别的操作,不能被打断
java.util.concurrent.atomic.Atomic*的类都是使用CAS来保证线程安全
package character02;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 解决同样问题的更高效的方法,使用AtomXX类
* AtomXX类本身方法都是原子性的,但不能保证多个方法连续调用是原子性
* @author laimouren
*/
public class CAS01 {
/*volatile*/ //int count = 0;
AtomicInteger count = new AtomicInteger(0);
/*synchronized*/void m(){
for (int i = 0; i < 10000; i++) {
count.incrementAndGet(); //相当于count++,只是它是线程安全的
}
}
public static void main(String[] args) {
CAS01 c = new CAS01();
List<Thread> threads = new ArrayList<>();
for (int i = 0; i < 10; i++) {
threads.add(new Thread(c::m,"thread-"+i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try{
o.join();
}catch (InterruptedException e){
e.printStackTrace();
}
});
System.out.println(c.count);
}
}
内部调用unsafe类里面的CompareAndSet方法
ABA问题
如果基本类型,无所谓
如果引用类型,就像你女朋友跟你复合,已经是别人的形状了
解释:
两个线程t1,t2;
t1执行cas(Object,1,2),t2先将Object改成了2,再改回1,这时候不影响t1的cas操作,但实际上已经有东西发生了修改
解决:给Object加上一个版本号,修改一次版本号加一
A 1.0
B 2.0
A 3.0
可以使用AtomicStampedReference解决ABA问题
Unsafe类(类似c c++的指针)
不能直接使用
功能:
- 直接操作内存
- 直接生成类实例
- 直接操作类或类变量
- CAS相关操作