高并发与多线程学习笔记二
文章目录
前言
高并发与多线程学习笔记系列之二
一、自旋与重量级锁
自旋是积极的排列,是占用cpu时间的
重量级锁是占用等待队列,不占cpu时间
二、Volatile
线程之间不可见,加了Volatile就保证一个线程发生改变,另外一个线程马上就能见到。
Volatile的作用有两点:
1.保证线程可见性
- MESI
- 缓存一致性协议
2.禁止指令重排序(指令是并发执行的)
关于单例模式
饿汉式
/**
* 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用!
* 唯一缺点:不管用到与否,类装载时就完成实例化
* Class.forName("")
* (话说你不用的,你装载它干啥)
*/
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01() {};
public static Mgr01 getInstance() {
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
}
懒汉式
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
*/
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03() {
}
public static Mgr03 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr03();
}
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(Mgr03.getInstance().hashCode())
).start();
}
}
}
加上Synchronized
/**
* lazy loading
* 也称懒汉式
* 虽然达到了按需初始化的目的,但却带来线程不安全的问题
* 可以通过synchronized解决,但也带来效率下降
*/
public class Mgr04 {
private static Mgr04 INSTANCE;
private Mgr04() {
}
public static synchronized Mgr04 getInstance() {
if (INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
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(Mgr04.getInstance().hashCode());
}).start();
}
}
}
双重检查
public class Mgr06 {
private static volatile Mgr06 INSTANCE; //JIT
private Mgr06() {
}
public static Mgr06 getInstance() {
if (INSTANCE == null) {
//双重检查
synchronized (Mgr06.class) {
if(INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
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(Mgr06.getInstance().hashCode());
}).start();
}
}
}
加Volatile
双重检查是不够的,还需要加上Volatile。
双重检查不加Volatile的危害。
问题在于指令重排序上。
Volatile不能保证原子性
/**
* volatile并不能保证多个线程共同修改running变量时所带来的不一致问题,也就是说volatile不能替代synchronized
* 运行下面的程序,并分析结果
* @author mashibing
*/
package com.mashibing.juc.c_012_Volatile;
import java.util.ArrayList;
import java.util.List;
public class T04_VolatileNotSync {
volatile int count = 0;
void m() {
for(int i=0; i<10000; i++) count++;
}
public static void main(String[] args) {
T04_VolatileNotSync t = new T04_VolatileNotSync();
List<Thread> threads = new ArrayList<Thread>();
for(int i=0; i<10; i++) {
threads.add(new Thread(t::m, "thread-"+i));
}
threads.forEach((o)->o.start());
threads.forEach((o)->{
try {
o.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
System.out.println(t.count);
}
}
Synchronized的优化
锁的粗化和锁的细化
/**
* synchronized优化
* 同步代码块中的语句越少越好
* 比较m1和m2
* @author mashibing
*/
package com.mashibing.juc.c_016_LockOptimization;
import java.util.concurrent.TimeUnit;
public class FineCoarseLock {
int count = 0;
synchronized void m1() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
count ++;
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
void m2() {
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
//业务逻辑中只有下面这句需要sync,这时不应该给整个方法上锁
//采用细粒度的锁,可以使线程争用时间变短,从而提高效率
synchronized(this) {
count ++;
}
//do sth need not sync
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
不要以字符串常量作为锁定对象
/**
* 不要以字符串常量作为锁定对象
* 在下面的例子中,m1和m2其实锁定的是同一个对象
* 这种情况还会发生比较诡异的现象,比如你用到了一个类库,在该类库中代码锁定了字符串“Hello”,
* 但是你读不到源码,所以你在自己的代码中也锁定了"Hello",这时候就有可能发生非常诡异的死锁阻塞,
* 因为你的程序和你用到的类库不经意间使用了同一把锁
*
* jetty
*
* @author mashibing
*/
package com.mashibing.juc.c_017_MoreAboutSync;
public class DoNotLockString {
String s1 = "Hello";
String s2 = "Hello";
void m1() {
synchronized(s1) {
}
}
void m2() {
synchronized(s2) {
}
}
}
锁定某对象o,如果o的属性发生改变,不影响锁的使用,但是如果o变成另外一个对象,则锁定的对象发生改变,应该避免将锁定对象的引用变成另外的对象
加final是一个很好的选择
ASM是什么?
/**
* 锁定某对象o,如果o的属性发生改变,不影响锁的使用
* 但是如果o变成另外一个对象,则锁定的对象发生改变
* 应该避免将锁定对象的引用变成另外的对象
* @author mashibing
*/
package com.mashibing.juc.c_017_MoreAboutSync;
import java.util.concurrent.TimeUnit;
public class SyncSameObject {
/*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) {
SyncSameObject t = new SyncSameObject();
//启动第一个线程
new Thread(t::m, "t1").start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
//创建第二个线程
Thread t2 = new Thread(t::m, "t2");
t.o = new Object(); //锁对象发生改变,所以t2线程得以执行,如果注释掉这句话,线程2将永远得不到执行机会
t2.start();
}
}
二、CAS(无锁优化、自旋锁、乐观锁)
(compare and set/swap)
什么是CAS
CAS(V,Expected,NewValue)
if V=Excepted,v=NewValue
otherwise try again or fail .
CAS的操作CPU原语支持,中间不会被打断
ABA问题
线程1操作A,在比较之前,线程2将A改成了B又改回了A,这时线程1(A=A(Excepted,A->B->A)
解决方法:任何一个值的修改,修改完版本号+1.如:A1.0 B2.0 A3.0.
可以用atomicStampedReference来解决。
如果是基础类型的话,ABA问题没什么事,但是如果是对象的话,引用的对象可能内存地址不变,但是这个对象的某些内容发生了改变。