提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档
文章目录
一、CAS
什么是CAS
1.cas叫做比较和交换
寄存器a和内存m的值进行比较
如果不相同则无事发生
相同,寄存器b,和m的值进行交换(不关心交换之后b的值,更关心交换之后m的值,此处的交换相当于是把b赋值给了m)
CAS应用:
实现原子类
前面讲过count++在多线程环境下,线程不安全,要想安全,加锁(加锁性能大大折扣)这个似乎基于cas操作来实现原子的++,保证线程安全,高校
先来看看伪代码实现:
这是一个原子类,getandincrement()方法相当于count++,oldValue相当于是寄存器a,暂且将oldValue+1理解成寄存器b的值,value是内存中的值
这段代码执行的逻辑是先看value内存中的值是否和寄存器a的值相同,
如果相同,把寄存器b的值设置到value中,同时cas返回true,结束循环
如果不相同,无事发生,cas返回false,进入循环,循环体里重新读取内存value到寄存器a中
画图演示:
上述过程抽象成当俩个线程同时调用getAndIncrement时
执行顺序:
load表示寄存区读取内存中数据,t1,t2是俩个线程,value是内存,a,b是俩个寄存器
假设t1,t2线程同时加载value到a,t1线程先判断寄存器a的值和value相同,于是b(oldvalue+1)值与内存value交换
t2线程执行cas时候发现,a和value不等,于是重新load,返回false重新进入循环如图:
执行完load,t2重新执行cas最终判决a==value,于是将计数器b(a+1)的值和value交换
这样俩个线程都完成了count++的操作,线程安全,最终内存中value置为2
实现自旋锁
竞争不激烈的时候很合适(第一时间拿到锁)
纯用户态的轻量级锁,当发现锁被其他线程持有的时候,另外的线程不会挂起等待,而是反复询问,看当前的锁是否被释放了(节省了进入内核和系统调度的开销)
伪代码:
先设置owner为空,表示无人获取的状态,owner表示当前的锁是被谁获取到的
如果owner和null相同,把当前调用lock的线程的值,设置到owner里(相当于加锁成功)同时结束循环
如果owner不为null,则cas不进行交换,返回false,会进入循环,又会立即再次发起判断
CAS的ABA问题
1.什么是aba问题:
举例说明:银行取款,a扣款的时候,b打钱给a,a的钱到底有没有发生变化(是否扣款成功)
2.解决方案:
只要有一个记录,能够记录内存中数据的变化
如何记录:
另外搞一个内存,保存m的修改次数(版本号)【只增不减】每次进行一次操作都增加版本号,
此时修改操作要先判断版本号是否相同,如果不相同则修改操作失败
二、Sychronized实现原理
加锁具体过程
锁升级
偏向锁
类似于懒汉模式,必要加,不必要不加
类似于count++
偏向锁不是真加锁,而是设置了一个状态,当t2lock时t1的锁才真生效
轻量级锁
重量级锁
通过自适应的自旋锁来转换,
总结:
无竞争,偏向锁,
有竞争,轻量级锁
竞争激烈,重量级锁
其他优化操作
锁消除
jvm自动判定,发现这个代码不需要加锁,写了synchronized就会自动把锁去掉
比如,你只有一个线程/虽然有多个线程不涉及修改同一个变量,如果代码写了synchronized此时加锁操作就会被synchronied被给jvm干掉
锁消除只是在编译器/jvm有十足的把握时候进行的
锁粗话:
三、Callable接口
1.callable接口类似于runnable
runnable描述的任务,不带返回值
callable描述的任务是带返回值的
如果你当前多线程完成任务,希望带上结果使用callable比较好
泛型结果是返回值的类型
2.代码演示
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//演示使用callable定义一个任务
public class demo29 {
//创建线程,通过线程来计算1+2+3+4+5+6+.....+100;
public static void main(String[] args) throws ExecutionException,InterruptedException {
Callable<Integer>callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i <=1000 ; i++) {
sum+=i;
}
return sum;
}
};
FutureTask<Integer>futureTask=new FutureTask<>(callable);
//创建线程,来执行上述任务
//thread的构造方法,不能直接传callable,还需要一个中间的类;
Thread t=new Thread(futureTask);
t.start();
//获取线程的计算结果
//get方法会阻塞,直到call方法计算完毕,get才能返回
System.out.println(futureTask.get());
}
}
futureTask
存在的意义就是让我们能够获取到结果(获取到结果的凭证)
2.RentrantLock
//演示ReentrantLock的加锁方式
public static void main(String[] args) {
ReentrantLock locker=new ReentrantLock(true);
try {
//加锁
locker.lock();
}finally {
//解锁
locker.unlock();
}
}
}
缺点,如果加锁解锁之间有return,或者异常,解锁执行不到,可以采取finally
说说ReentrantLock和sychroinized区别:
缺点:如果加锁解锁之间有return,或者异常,解锁执行不到
优势:
1.tryLock试试看能不能加上锁,式成功加锁成功,失败则放弃,并且还可以指定加锁的等待超时时间
实际开发中,使用这种死等的策略往往要慎重,trylock给我们提供了更多的可能
2.reentranlock可以实现公平锁,默认是公平的,构造的时候,传入一个简单的参数,就实现公平锁了
ReentrantLock locker=new ReentrantLock(true);
3.synchronized是搭配wait/notify实现等待通知机制,唤醒操作是随机唤醒一个等待的线程
reentrantLock是搭配Condition类实现,唤醒操作是可以指定唤醒哪个线程的
原子类
使用原子类,最常见的常见场景就是多线程计数
写了个服务器,服务器一共有多少并发量,可通过原子类来累加
package Threading;
import java.util.concurrent.atomic.AtomicInteger;
//演示原子类
public class demo31 {
public static void main (String[] args) throws InterruptedException {
AtomicInteger count=new AtomicInteger(0);
//相当于++count
// count.incrementAndGet();
//相当于count--
// count.getAndDecrement();
//相当于--count
// count.decrementAndGet();
Thread t1=new Thread(()->{
for (int i = 0; i <50000 ; i++) {
//相当于count++;
count.getAndIncrement();
}
});
Thread t2=new Thread(()->{
for (int i = 0; i <50000 ; i++) {
//相当于count++;
count.getAndIncrement();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
//get 获取到内部的值
System.out.println(count.get());
}
}
信号量
p 申请一个资源
v 释放一个资源
信号量本身是一个计数器,表示可用资源
p可用资源-1
v可用+1
当计数为0继续p,会阻塞等到v执行
信号量可用释放一个更广义的锁,锁就是一个特殊的信号量(可用资源只有1的信号量)
import java.util.concurrent.Semaphore;
//演示显示量p,v操作
public class demo32 {
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore=new Semaphore(4);
semaphore.acquire();
System.out.println("p操作");
semaphore.acquire();
System.out.println("p操作");
semaphore.acquire();
System.out.println("p操作");
semaphore.acquire();
System.out.println("p操作");
semaphore.acquire();
System.out.println("p操作");
semaphore.acquire();
System.out.println("p操作");
//这是v操作,释放资源,计数器-1;
semaphore.release();
}
}