解决并发问题,一般有两种办法,悲观方式,乐观方式,其实还有一种办法,就是串行话(队列)。
悲观方式
悲观方式,比如JAVA里的同步关键字synchronized,它是一种独占锁,将对象或者Class加锁,用来防止两个线程同时访问一个数据。另外ReenTrantLock同synchronized,功能类似。ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。
ReenTrantLock同synchronized区别:
1.便利性:synchronized的使用比较方便简洁,ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。
2.灵活度:ReenTrantLock优于Synchronized。
ReenTrantLock独有的能力:
1. ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。2. ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。3.ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。还有Semaphore也能实现synchronized功能,使用起来复杂一些,但信号量可以支持多个并发(在new的时候申明并发数)
乐观方式
乐观方式,比如JAVA的原子变量,采用CAS,CAS有3个操作数,内存值V,旧的预期值 A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。串行话
另外一种就是串行话,将所有操作串行执行,这样就避免了并发问题。Redis就是这种模式,redis利用队列技术将并发访问变为串行访问,消除了传统数据库串行控制的开销。所以Redis是单进程单线程的(网络请求)。volatile
还有个,就是多线程编程中经常提到的volatile。并发编程中有三个概念,原子性,可见性,有序性。被volatile声明的变量,在值被修改后会直接写入内存中,并且让高速缓存(线程)中的值失效,所以volatile能保证可见性。
但volatile不能保证原子性,也就是说被volatile声明的变量,像自增这种操作,并不是线程安全的。我们只能在有限的一些情形下使用 volatile 变量替代锁:
- 对变量的写操作不依赖于当前值。
- 该变量没有包含在具有其他变量的不变式中
所以volatile变量的使用范围实在太小,也许volatile变量的规范使用仅仅是使用一个布尔状态标志,用于指示发生了一个重要的一次性事件,例如完成初始化或请求停机。
CountDownLatch
CountDownLatch是一个同步工具类,用来协调多个线程之间的同步,或者说起到线程之间的通信(而不是用作互斥的作用)。CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。使用一个计数器进行实现。计数器初始值为线程的数量。当每一个线程完成自己任务后,计数器的值就会减一。当计数器的值为0时,表示所有的线程都已经完成了任务,然后在CountDownLatch上等待的线程就可以恢复执行任务。