一、CAS机制
什么是CAS(Compare and Swap)?
CAS就是一种乐观锁的实现方式,翻译过来就是“比较和交换”,是一种无锁的原子操作,具体的实现为Unsafe类 。
在并发编程中,i++(自增)操作时非线程安全的,是因为i++操作不是原子操作,那么怎么保证其原子性呢?
最常见的做法就是加锁。在Java中,synchronized关键字和CAS来实现加锁的效果。synchronized是悲观锁,随着版本迭代更新,synchronized关键字已经完成了“瘦身”,变成了轻量级的锁,但它依旧摆脱不了是悲观锁的命运,线程开始执行第一步的时候就要获取锁,一旦获取到锁,其他的线程进入后就会出现阻塞并等待锁。而CAS就不一样了,CAS是一种乐观锁的实现方式,线程执行的时候不会加上锁,而是在执行的时候默认为没有冲突,然后直到完成某项操作,如果因为冲突失败了就重试,直到成功为止。
乐观锁与悲观锁
悲观锁
对于悲观锁来说,它总是认为每次访问共享资源时会发生冲突,所以必须对每次数据操作加上锁,以保证临界区的程序同一时间只能有一个线程在执行。
乐观锁
乐观锁,顾名思义,它是乐观派。乐观锁总是假设对共享资源的访问没有冲突,线程可以不停地执行,无需加锁也无需等待。一旦多个线程发生冲突,乐观锁通常使用一种称为 CAS 的技术来保证线程执行的安全性。
由于乐观锁假想操作中没有锁的存在,因此不太可能出现死锁的情况,换句话说,乐观锁天生免疫死锁。
乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;
悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。
在 CAS 中,有这样三个值:
V:要更新的变量(var) E:预期值(expected) N:新值(new)
二、volitle关键字的使用
volatile
是Java中的一个关键字,用于修饰变量。它主要用于保证可见性和禁止指令重排序。
在多线程环境下,当一个线程修改了共享变量的值后,其他线程可能无法立即看到这个修改。这是因为每个线程都有自己的缓存,如果没有使用合适的同步机制,线程可能会读取到过期的缓存值,从而导致数据不一致的问题。
使用 volatile
关键字可以解决这个问题。当一个变量被声明为 volatile
时,每次对该变量的读写操作都会直接从内存中进行,而不是从线程的缓存中读取或写入。这样可以确保所有线程都能看到最新的值,实现了可见性。
此外,volatile
还禁止了指令重排序优化。在编译器和处理器对代码进行优化时,会对指令顺序进行重排序以提高性能。在某些情况下,这种重排序可能导致多线程程序出现异常行为。通过将变量声明为 volatile
,可以防止特定类型的指令重排序,从而避免了潜在的错误。
需要注意的是,volatile
并不能替代锁(synchronized)来实现原子性操作。它只能保证可见性和禁止指令重排序,不能保证复合操作的原子性。如果需要进行原子性操作,应该使用锁或者其他线程安全的方式。
三、线程的生命周期描述
线程的生命周期描述了一个线程从创建到终止的整个过程,它包含以下几个状态:
-
新建(New):当我们通过实例化
Thread
类或者实现Runnable
接口创建一个线程对象时,线程就处于新建状态。此时线程还没有开始执行。 -
就绪(Runnable):当调用线程对象的
start()
方法后,线程进入就绪状态。此时线程已经准备好运行,但还需要等待获取 CPU 时间片才能真正开始执行。多个线程被放入可运行线程池中,在操作系统调度之前,无法确定哪个线程会先执行。 -
运行(Running):当线程获得 CPU 时间片并开始执行时,线程进入运行状态。此时线程正在执行自己的任务代码。
-
阻塞(Blocked):在特定情况下,线程可能会由于某些原因而暂停执行。这种情况下线程进入阻塞状态。例如,线程可能因为等待某个资源、等待用户输入等而被阻塞。当阻塞条件解除时,线程会重新进入就绪状态,等待获取 CPU 时间片。
-
等待(Waiting):线程在等待某个特定条件的发生时进入等待状态。线程可以通过调用
Object
类的wait()
方法、Thread
类的join()
方法或者调用锁对象的wait()
方法来进入等待状态。当其他线程修改了特定条件并通知等待线程时,等待线程会被唤醒并进入就绪状态。 -
计时等待(Timed Waiting):与等待状态类似,但是在计时等待状态下,线程会等待一段指定的时间。线程可以通过调用
Thread
类的sleep()
方法、Object
类的wait(long timeout)
方法、Thread
类的join(long timeout)
方法或者调用锁对象的wait(long timeout)
方法来进入计时等待状态。 -
终止(Terminated):当线程执行完任务或者发生异常而终止时,线程进入终止状态。一个终止的线程不能再次进入任何其他状态。
需要注意的是,不同的操作系统和JVM实现可能存在差异,因此线程的具体行为可能会有所不同。