}
public static void main(String[] args) throws InterruptedException {
//开启三个线程 t1 t2 t3
Thread t1 = new Thread(() ->{
add();
});
Thread t2 = new Thread(() ->{
add();
});
Thread t3 = new Thread(() ->{
add();
});
long starTime = System.currentTimeMillis();
//启动三个线程
t1.start();
t2.start();
t3.start();
//让线程同步
t1.join();
t2.join();
t3.join();
long endTime = System.currentTimeMillis();
System.out.println(“累加完成,count:”+count);
System.out.println(“耗时:”+(endTime - starTime)+" ms");
}
}
执行结果
很明显,三个线程累加,由于cpu缓存的存在,导致结果远远小于30w,这个也是我们预期到的,所以才会出现后面两种解决方案。
2.使用synchronized
使用synchronized时需要注意,需求要求我们三个线程分别累加10W,所以synchronized锁定的内容就非常重要了,要么直接锁定类,要么三个线程使用同一把锁,关于synchronized的介绍以及锁定内容请参考:java并发编程之synchronized
第一种,直接锁定类,我这里采用锁定静态方法。
我们来改动一下代码,将add方法加上synchronized关键字即可,由于add方法已经时静态方法了,所以现在锁定的时整个CASTest类。
/**
- 累加10w
*/
private static synchronized void add(){
for (int i = 0; i< 100000; ++i){
count+=1;
}
}
运行结果
第一次:
第二次:
第三次:
这里就有意思了,加了锁的运行时间居然比不加锁的运行时间还少?是不是觉得有点不可思议了?其实这个也不难理解,这里就要牵扯到cpu缓存以及缓存与内存的回写机制了,感兴趣的小伙伴可以自行百度,今天的重点不在这里。
第二种:三个线程使用同一把锁
改造代码,去掉add方法的synchronized关键字,将synchronized写在add方法内,新建一把钥匙(成员变量:lock),让三个累加都累加操作使用这把钥匙,代码如下:
package com.ymy.test;
public class CASTest {
private static long count = 0;
private static final String lock = “lock”;
/**
- 累加10w
*/
private static void add() {
synchronized(lock){
for (int i = 0; i < 100000; ++i) {
count += 1;
}
}
}
public static void main(String[] args) throws InterruptedException {
//开启三个线程 t1 t2 t3
Thread t1 = new Thread(() -> {
add();
});
Thread t2 = new Thread(() -> {
add();
});
Thread t3 = new Thread(() -> {
add();
});
long starTime = System.currentTimeMillis();
//启动三个线程
t1.start();
t2.start();
t3.start();
//让线程同步
t1.join();
t2.join();
t3.join();
long endTime = System.currentTimeMillis();
System.out.println(“累加完成,count:” + count);
System.out.println(“耗时:” + (endTime - starTime) + " ms");
}
}
结果如下:
这两种加锁方式都能保证线程的安全,但是这里你需要注意一点,如果是在方法上加synchronized而不加static关键字的话,必须要保证多个线程共用这一个对象,否者加锁无效。
原子类
原子类工具有很多,我们举例的累加操作只用到其中的一种,我们一起看看java提供的原子工具有哪些:
工具类还是很丰富的,我们结合需求来讲解一下其中的一种,我们使用:AtomicLong。
AtomicLong提供了两个构造函数:
value:原子操作的初始值,调用无参构造value=0;调用有参构造value=指定值
其中value还是被volatile 关键字修饰,volatile可以保证变量的可见性,什么叫可见性?可见性有一条很重要的规则:Happens-Before 规则,意思:前面一个操作的结果对后续操作是可见的,线程1对变量A的修改其他线程立马可以看到,具体请自行百度。
我们接着来看累加的需求,AtomicLong提供了一个incrementAndGet(),源码如下:
/**
-
Atomically increments by one the current value.
-
@return the updated value
*/
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
Atomically increments by one the current value
《一线大厂Java面试题解析+后端开发学习笔记+最新架构讲解视频+实战项目源码讲义》
【docs.qq.com/doc/DSmxTbFJ1cmN1R2dB】 完整内容开源分享
:原子的增加一个当前值。好了,我们现在试着将互斥锁修改成原子类工具,改造代码:
**1.实例化一个Long类型的原子类工具;
2.再for循环中使用incrementAndGet()方法进行累加操作。**
改造后的代码:
package com.ymy.test;
import java.util.concurrent.atomic.AtomicLong;
public class CASTest {
// private static long count = 0;
//
// private static final String lock = “lock”;
private static AtomicLong atomicLong = new AtomicLong();
/**
- 累加10w
*/
private static void add() {
for (int i = 0; i < 100000; ++i) {
atomicLong.incrementAndGet();
}
}
public static void main(String[] args) throws InterruptedException {
//开启三个线程 t1 t2 t3
Thread t1 = new Thread(() -> {
add();
});
Thread t2 = new Thread(() -> {
add();
});
Thread t3 = new Thread(() -> {
add();
});
long starTime = System.currentTimeMillis();
//启动三个线程
t1.start();
t2.start();
t3.start();
//让线程同步
t1.join();
t2.join();
t3.join();
long endTime = System.currentTimeMillis();
//System.out.println(“累加完成,count:” + count);
System.out.println(“累加完成,count:” + atomicLong);
System.out.println(“耗时:” + (endTime - starTime) + " ms");
}
}
结果:
可以得到累加的结果也是:30w,但时间却比互斥锁要久,这是为什么呢?我们一起来解剖一下源码。
AtomicLong incrementAndGet()源码解析
/**
-
Atomically increments by one the current value.
-
@return the updated value
*/
public final long incrementAndGet() {
return unsafe.getAndAddLong(this, valueOffset, 1L) + 1L;
}
public final long getAndAddLong(Object var1, long var2, long var4) {
long var6;
do {
var6 = this.getLongVolatile(var1, var2);
} while(!this.compareAndSwapLong(var1, var2, var6, var6 + var4));
return var6;
}
我们来看看getAndAddLong方法,发现内部使用了一个 do while 循环,我们看看循环的条件是什么
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);