什么是锁,为什么要用锁?
别着急,我们先看看下面的代码的执行结果:
这段代码的要求是输出0-999的数
package demo.test;
public class Test1 {
static Integer num=0;
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
while (num < 100) {
System.out.println("ThreadName:" + Thread.currentThread().getName() + "\tnum:" + num++);
}
}
});
thread.start();
}
}
}
RESULT:
可以看到两个线程都输出了0,这就代表他们竞争资源的时候同时拿到了资源进行操作,这表示线程是不安全的,那么如何准确无误的输出想要的结果呐?
我们就需要用到synchronized关键字,作用是同步线程
代码如下
package demo.test;
public class Test1 {
static Integer num=0;
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Test1.class){
while (num < 100) {
System.out.println("ThreadName:" + Thread.currentThread().getName() + "\tnum:" + num++);
}
}
}
});
thread.start();
}
}
}
那么sychronized是如何实现同步的呐?
这里先介绍一样新事物叫Monitor,它是基于操作系统的mutex lock实现的,你可以把想资源想象成一个房间,想要使用这个资源的线程为等待的客人,这个房间每次就只能进入一个客人,一个客人进入,那么其他客人只能等待,Monitor的翻译为监控器,你可以把它认为为一个房间,它严格的把控着这个房间只允许被一个人使用。
下图是sychronized的实现机制,客人们排队在Entry set等待,如果一个客人A进入了房间,但是需要他暂时腾出房间给客人B使用,那么他将进入wait set等待,等客人B用完exit后,客人B通过notify重新唤醒客人A继续完成任务.
但是由于java线程是映射的操作系统系统线程,每次挂起和唤醒都需要操作系统切换内核态,会影响的程序的性能,甚至有时候切换线程比任务本身的时间还长,所以这是一种重量级的锁机制,被称为重量级锁,是悲观锁,在java6以上引入进行了优化,引入了轻量级锁和偏向锁.
那有没有一种机制能够既能无锁又能实现同步呐?cas算法
以下是代码实现:
package demo.test;
import java.util.concurrent.atomic.AtomicInteger;
public class Test1 {
static AtomicInteger atomicInteger = new AtomicInteger(-1);
public static void main(String[] args) {
for (int i = 0; i < 2; i++) {
final Thread thread = new Thread(new Runnable() {
@Override
public void run() {
synchronized (Test1.class){
while (atomicInteger.get()+1 < 100) {
System.out.println("ThreadName:" + Thread.currentThread().getName() + "\tnum:" + atomicInteger.incrementAndGet());
}
}
}
});
thread.start();
}
}
}
cas是如何做到无锁同步的呐?
cas 其实是(compare and swap)比较和交换的缩写,举个例子,比如有两个人,他们分别是张三和李四,他们同时看到了一个美女都想和她约会,假设美女有一个牌子,当美女处于空闲状态为0,处于约会状态为1,那么张三和李四只能在美女处于状态0和她约会,现在美女刚好空闲牌子为0,张三和李四同时起跑,张三快李四一步,率先把牌子翻为1,李四看到就只能回去等待,等美女空闲了再去。
看完这个例子就很好理解cas了,假设有两个线程a,b同时受到资源的状态值变为0,那么他们会产生两个值,一个为old value 代表之前读到资源的值,new value为1
现在假设a线程率先到达,那么它会比较资源状态值和它的old value是否相同,如果相同就会把资源状态值改为它的new Value,当b到达时发现资源的状态值已经为1,和自己的old value不相等,于是自旋等待,自旋等待就是过一段时间B再去和其他线程竞争,直到成功或者超时放弃,关于自旋等待也有一种优化方式叫做适应性自旋,感兴趣的可以自己了解一下
那现在存在还一个一个问题,就是比如之前张三和李四如果同时到达把牌子翻为1怎么班,3个人一起约会岂不是很尴尬,所以对比并且翻牌的这个动作在同时只能由一个人完成,cas在对比和修改资源状态值同时也只能被一个线程完成,那cas是如何避免发生这种情况实现同步的呐,实现cas的原子性呐,难道还需要用锁嘛?不是,现在
不同的架构都提供指令级别的cas原子操作,比如在x86下,cmpxchg支持cas,arm的LL/SC来实现cas,cpu已经原生的支持cas,上层调用即可,上面代码即是java的调用来实现cas