四、常见的锁
一、悲观锁
概念:在获取数据时会先加锁,确保数据不会被别的线程修改。
场景:适合写操作多的场景,先加锁可以保证写操作时数据正确。
实现:synchronized关键字和Lock的实现类都是悲观锁。
二、乐观锁
概念:获取数据时认为不会有别的线程修改数据,所以不会上锁,但是在更新时会判断有没有别的线程更新了这个数据。
场景:适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升。
实现:使用无锁编程来实现,常见的有CAS算法和版本号机制。(Java原子类中的递增操作就通过CAS自旋实现)
三、公平锁和非公平锁
概念:公平锁遵循FIFO(先进先出)原则的,先到的线程会优先获取资源,后到的会进行排队等待。非公平锁不遵循FIFO。
区别:
1、公平锁可以保证“雨露均沾”。
2、非公平锁有着更高的性能。
源码上看,公平锁的比非公平锁的多一步判断。
恢复挂起的线程到真正锁的获取有一定时间差,从开发人员来看这个时间微乎其微,但从CPU角度来看,这个时间差可以被非公平锁抢占,可以尽量减少CPU空闲时间。
非公平锁减少线程的开销,当1个线程请求锁获取同步状态,然后释放同步状态,因为不需要考虑是否还有前驱节点,所以刚释放锁的线程在此刻更容易再次获取同步状态。
四、可重入锁
概念:同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提,锁的对象是同一个对象)。不会因为之前已经获取过还没释放锁而阻塞。
实现:ReentrantLock、synchronized
五、死锁
概念:两个或两个以上的线程再执行过程中,因争夺资源而造成的一种互相等待的现象。
排查死锁:jps、jstack、jconsole
六、自旋锁(CAS)
概念:是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,当线程发现锁被占用时,会不断循环判断锁的状态,直到获取。这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。
七、偏向锁
概念:
1、在没有其他线程竞争的时候,一直偏向偏心当前线程,当前线程可以一直执行。
2、当一段同步代码一直被同一个线程多次访问,由于只有一个线程那么该线程在后续访问时便会自动获得锁。它的出现是为了解决只有在一个线程执行同步时提高性能。
技术实现:一个synchronized方法被一个线程抢到了锁时,那这个方法所在的对象就会在其所在的Mark Word中将偏向锁修改状态位,同时还占用前54位来存储线程指针作为标识。若该线程再次访问同一个synchronized方法时,该线程只需去对象头的Mark Word 中去判断一下是否有偏向锁指向本身的ID,无需再进入Monitor去竞争对象了。
八、轻量锁
概念:有线程来参与锁的竞争,但是获取锁的冲突时间极短。其本质就是自旋锁。