线程安全
线程安全测试:
@Slf4j
public class ConcurrencyTest {
public static int clientTotal = 5000;//请求总数
public static int threadTotal = 200;//允许并发线程数
public static int count = 0;//
public static void main(String[] args) throws Exception{
//定义线程池
ExecutorService executorService = Executors.newCachedThreadPool();
//定义信号量
final Semaphore semaphore = new Semaphore(threadTotal);
//计数器
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for(int i = 0; i < clientTotal; i++){
executorService.execute(()->{
try {
semaphore.acquire();//判断进程是否允许被执行
add();
semaphore.release();
}catch (Exception e){
log.error("Exception--- " + e.getMessage());
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}" + count);
}
private static void add(){
count++;
}
}
线程安全性:
原子性:同一时刻只能有一个线程来操作。
可见性:一个线程对主内存的修改可以及时的被其他线程观察到。
有序性:
原子性 Atomic包:
public static AtomicInteger count = new AtomicInteger(0);//
private static void add(){
count.incrementAndGet();
}
incrementAndGet 实现:
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
unsafe.getAndAddInt(this, valueOffset, 1) 实现:
public final int getAndAddInt(Object var1, long var2, int var4) {
int var5;
do {
var5 = this.getIntVolatile(var1, var2);
} while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));
return var5;
}
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5); Java底层方法
当前值和底层的值相同,则更新值。
CAS:compareAndSwap
AtomicLong是作用是对长整形进行原子操作,显而易见,在java1.8中新加入了一个新的原子类LongAdder,该类也可以保证Long类型操作的原子性,相对于AtomicLong,
LongAdder有着更高的性能和更好的表现,可以完全替代AtomicLong的来进行原子操作。
在32位操作系统中,64位的long 和 double 变量由于会被JVM当作两个分离的32位来进行操作,所以不具有原子性。而使用AtomicLong能让long的操作保持原子型。
AtomicStampReference : CAS的ABA问题
原子性对比:
synchronized:不可中断锁,适合竞争不激烈,可读性好;
Lock:可中断锁,多样化同步,竞争激烈时能维持常态;
Atomic:竞争激烈时能维持常态,比Lock性能好,只能同步一个值。
可见性:
- 线程交叉执行
- 重排序结合线程交叉执行;
- 共享变量更新后的值没有在工作内存与主存间及时更新。
可见性 - synchronized
- 线程解锁前,必须把共享变量的最新值刷新到主内存;
- 线程加锁时,将清空工作内存中共享变量的值,使用共享变量时需要从主内存中重新读取最新的值。
可见性 - volatile
通过加入内存屏障和禁止重排序优化来实现
- 对volatile变量写操作时,会在操作后加入一条store屏障指令,将本地内存的共享变量值刷新到主内存;
- 对volatile变量读操作时,会在操作前加入一条load屏障指令,从主内存总读取共享变量。
volatile不具有原子性,适合做为状态标记量。
发布对象
使一个对象能够被当前范围之外的代码所使用
对象溢出
一种错误的发布,当一个对象还没有构造完成时,就使他被其他线程所见。
线程不安全,当两个线程同时拿到Null的时候,就会出现问题。
加锁的情况下也有可能出现线程不安全:
因为JVM和cpu的优化,发生了指令重排
1.memory = allocate() 分配对象的内存空间;
2.instance = memory 设置instance指向刚分配的内存;
3.ctorInstance() 初始化对象
使用volatile关键字来禁止指令重排
使用枚举
安全发布对象的方法
- 在静态初始化函数中初始化一个对象引用;
- 将对象的引用保存到volatile类型域或者AtomicReference对象中;
- 将对象的引用保存到某个正确构造对象的final类型域中;
- 将对象的引用保存到一个由锁保护的域中。
final关键字:
- 修饰类:不能被继承;
- 修饰方法:1.锁定方法不能被继承类修改;2.效率()
- 修饰变量:基本数据类型变量,引用数据类型变量
final如果修饰引用类型的变量时,只是不允许指向另一个对象,但是变量的值可以修改。
不可变对象
线程封闭
- Ad-hoc线程封闭:程序控制实现,最糟糕
- 堆栈封闭:局部变量,无并发问题;
- ThreadLocal 线程封闭:非常好的方法。
String类:String对象是不可改变的(引用类型)。字符串一旦创建,内容不能再改变。
当对字符串进行修改的时候,需要使用 StringBuffer 和 StringBuilder 类。
Java8的DateTimeFormatter是线程安全的,而SimpleDateFormat并不是线程安全。
同步容器
- ArrayList:Vector 、Stack
- HashMap:HashTable
- Collections.synchronizedXXX(List,Set,Map)