CAS
概念
CAS:compare and swap 比较和交换。有一个寄存器A、寄存器B和一个内存M,寄存器A和内存M的值进行比较,如果不相同则无事发生,如果相同则把寄存器A的值和M进行交换(此时,我们不关心寄存器B中的值,只关心内存M的值,相当于赋值操作)
应用场景
最常用的两个场景:
-
实现原子类
伪代码实现原子类中count++操作
class AtomicInteger{
private int value;
public int getAndIncrement(){
int oldvalue = value;//LOAD
while(CAS(value,oldvalue,oldvalue+1) != true){
oldvalue = value;
}
return oldvalue;
}
}
这其实是标准库中已经封装好了的一个类。
package threading;
import java.util.concurrent.atomic.AtomicInteger;
// 原子类(底层基于CAS实现)java已经封装好了可以直接使用
public class Demo31 {
public static void main(String[] args) throws InterruptedException {
// //相当于++count
// count.incrementAndGet();
// //相当于count--
// count.getAndDecrement();
// //相当于--count
// count.decrementAndGet();
AtomicInteger count = new AtomicInteger(0);
Thread t1 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
//相当于count++
count.getAndIncrement();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 5000; i++) {
//相当于count++
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count.get());
}
}
-
实现自旋锁
跟实现原子类差不多
ABA问题
在CAS中,进行比较的时侯,寄存器A和内存M的值相同,但是你无法判定M始终没变,还是变了又变回来了。
只要有一个记录,能够记录上内存中数据的变化,就能解决ABA问题了。
如何进行记录呢?
另外搞一个内存保存M的"修改次数"[版本号]{只增不减}或者是上次修改的时间[只增不减],通过这个方法都能解决ABA问题。此时当我们修改操作时,寄存器A就不只是读取内存M的值了,进行比较的时候也不是比较寄存器A和内存M的值,而比较两个的版本号/上次修改时间。
synchronized的优化
synchronized加锁的具体过程
锁升级
synchronized为了减轻使用者的负担,内部就实现了自适应这样的操作。
- 偏向锁: 设置一个状态,只在必要的时候加锁,能不加就不加
- 轻量级锁:如果当前场景中,锁竞争不激烈,则以轻量级锁状态工作。
- 重量级锁:如果当前场景中,锁竞争不激烈,则以重量级锁状态工作。
锁消除
编译器优化操作,当不需要加锁时但是你的代码中加上了synchronized,jvm就自动把锁干掉,不过这个编译器优化操作是在它有十足把握时才会这样做。
锁粗化
锁的粒度就是synchronized的对应代码块包含多少代码,包含的代码少,粒度就细,包含的代码多,粒度就粗。锁粗化就是把细粒度的锁->粗粒度的锁。
JUC
java.util.concurrent (并发)这个包中都是和多线程有关的
Callable
概念
类似于Runnable任务,但是是具有返回值的任务,Runnable是没有返回值的。不过Callable任务不能直接传给线程,要通过中间类FutureTask,FutureTask存在的意义就是为了让我们能够获取结果(相当于小票)。
代码实例
public class Demo9 {
//创建一个线程求 1+2+3+···+1000的值
public static void main(String[] args) throws ExecutionException, InterruptedException {
//使用Callable创建任务
Callable<Integer> callable = new Callable<Integer>() {
@Override
public Object call() throws Exception {
int count = 0;
for (int i = 1; i <= 1000; i++) {
count += i;
}
return count;
}
};
FutureTask<Integer> future = new FutureTask<>(callable);
Thread t = new Thread(future);
t.start();
//通过future获取结果
System.out.println(future.get());
}
}
ReentrantLock
概念
是一种可重入锁,synchronized也是可重入锁,但是synchronized也有一些功能做不到,而ReentrrantLock就是对synchronized的补充。
有三种核心方法:
- lock()
- unlock()
- trylock()
缺点:由于1,2需要代码中显式的加锁解锁,可能会出现加锁了却没解锁的情况,比如lock和unlock中间return了,所以一般他们需要搭配try,finally来使用。
优点:
- trylock可以试着看能不能加上锁
- 可以搭配condition类实现的,可以指定唤醒莫个线程
代码实例
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class Demo30 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock(true);
try {
//加锁
reentrantLock.lock();
}finally {
//如果两者之间有return unlock可能执行不到 所以一般配合try finally使用
//解锁
reentrantLock.unlock();
}
}
}
CountDownLatch
概念
同时等待 N 个任务执行结束.
代码实例
- 构造 CountDownLatch 实例, 初始化 4 表示有 4 个任务需要完成.
- 每个任务执行完毕, 都调用 latch.countDown() . 在 CountDownLatch 内部的计数器同时自减.
- 主线程中使用 latch.await(); 阻塞等待所有任务执行完毕. 相当于计数器为 0 了.
import java.util.concurrent.CountDownLatch;
public class Demo32 {
//CountDownLatch 模拟跑步比赛
public static void main(String[] args) throws InterruptedException {
CountDownLatch count = new CountDownLatch(4);
for (int i = 0; i < 4; i++) {
Thread t = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + " 起跑");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 撞线");
count.countDown();
});
t.start();
}
count.await();
System.out.println("比赛结束");
}
}