说到了锁咱们常常会联想到生活中的锁,在咱们平常中咱们常常会接触到锁。好比咱们的手机锁,电脑锁,再好比咱们生活中的门锁,这些都是锁。java
说了这么多仍是不清楚锁到底有什么用处?这一点就要深思咱们为何要使用锁,咱们用手机锁是为了保障咱们的隐私安全,使用门锁是为了保障咱们的财产安全,准确的来讲咱们使用锁就是为了安全。
那么在生活中咱们能够加锁来保障本身的隐私和财产安全,那Java中的锁有什么用处呢?算法
Java中的锁准确的来讲也是为了保证安全,不过不一样的是Java中的锁是为了保证并发所须要的。因此在Java中加锁准确的来讲是为了保证并发安全,同时也是为了解决内存中的一致性,原子性,有序性三种问题。在Java中提供了各式各样的锁,每种锁都有其自身的特色和适用范围。因此咱们都要熟悉锁的区别和原理才能正确的使用。数据库
乐观锁和悲观锁的话在以前我刚刚开始写的时候就写过相关的文章,在这里就从新介绍一下吧。
悲观锁如其名它是悲观的,它以为每次访问数据均可能被其余人(线程)修改,因此在访问资源的时候就会对资源进行加锁,用这种方式来保证资源在访问的时候不会被其余线程修改。这样的话其余线程想要获取资源的话就只能阻塞,等到当前线程释放锁后在获取。在Java中悲观锁的实现有synchronized关键字
和Lock
的实现类都是悲观锁。咱们来看一下悲观锁究竟是怎么执行的。
线程A抢占到资源后线程B就陷入了阻塞中,而后就等待线程A释放资源。安全
当线程A释放完资源后线程B就去获取锁开始操做资源˛悲观锁保证了资源同时只能一个线程进行操做。多线程
与悲观锁相反,乐观锁并不会以为访问数据的时候会有人修改(因此它是乐观的),因此在访问资源的时候并不会上锁,可是在提交的时候回去判断一下是否有人修改了当前数据,在数据库中咱们可使用version
版本号去实现。在Java中咱们是使用CSA来实现。咱们看一下乐观锁的执行过程并发
CAS(Compare And Swap)算法是一种无锁算法,是Java提供的非阻塞原子性操做。在不使用锁的状况下实现多线程下的同步。在并发包中(java.util.concurrent)原子性类都是使用CAS来实现乐观锁的。CAS经过硬件保证了比较更新的原子性,在JDK中Unsafe提供了一系列的compareAndSwap*方法,这里就不深究Unsafe这个类了。
CAS操做过程就是将内存中的将要被修改的数据与预期的值进行比较,若是这两个值相等就修改值为新值,不然就不作操做也就是说CAS须要三个操做值:性能
简单的来讲CAS就是一个死循环,在循环中判断预期的值和内存中的值是否相等,若是相等的话就执行修改,若是若是不相等的话就继续循环,直到执行成功后退出。
CAS虽然很牛逼可是它也存在一些问题好比ABA问题,举个例子,如今有内存中有一个共享变量X的值为A,这个时候出现一个变量想要去修改变量X的值,首先会获取X的值这个时候获取的是A,而后使用CAS操做把X变量修改为B。这样看起来是没有问题,那若是在线程1获取变量X以后,执行CAS以前出现一个线程2把X的值修改为B而后CAS操做执行又修改为了了A,虽然最后执行的结果共享变量的值为A可是此A已经不是线程1获取的A了。
这就是经典的ABA问题。产生ABA问题是由于变量的状态值发生了环形转换,A能够到B,B能够到A,若是A到B,B到C就不会发生这种问题。spa
解决办法:在JDK1.5后加入了AtomicStampedReference方法给每一个变量加入了一个时间戳来避免ABA问题。
同时CAS还有循环开销大的问题,由于会一直循环直到预期和内存相等修改为功。同时还有只能保证一个共享变量的原子性的问题不过在JDK1.5以后加入了AtomicReference类来保证引用对象之间的原子性。
可使用synchronized关键字来实现悲观锁,乐观锁可使用并法包下提供的原子类。线程
上面说了悲观锁和乐观锁,如今来看公平锁和非公平锁。在锁中也是有公平和不公平滴,公平锁如其名讲究的是一个公平,因此多个线程同时申请申请锁的话,线程会放入一个队列中,在队列中第一个进入队列的线程才能获取锁资源,讲究的是先到先得。就好比咱们在学校食堂打饭的时候,那个时候记得我同窗一放学就赶快去食堂排队这样的话才能尽快的打上饭,并且在排队的过程当中并不会有人吃不到饭,这个时候食堂阿姨是公平的每一个人排队的话都能吃到饭,线程也是如此。非公平锁能够这样理解,我那个同窗去食堂排队打饭了可是有人却插队,食堂阿姨却不公平直接给插队的人打饭却不给他打,你说气不气是否是很不公平,划重点非公平锁先到不必定先得。不过公平锁也是有缺点的,当一个线程获取资源后在队列中的其余的线程就只能在阻塞,CPU的因此公平锁比非公平锁的效率要低不少。由于CPU唤醒阻塞线程的开销比非公平锁大。咱们来看一个一个例子:3d
在Java中ReentrantLock提供了公平锁和非公平锁的实现。看一下ReentrantLock怎么实现公平锁和非公平锁
ReentrantLock默认就是非公平的锁,咱们来看一下公平锁的例子:
看一下输出结果:
咱们能够看到公平锁的输出结果是按照顺序来的,先到先得。
在看一下非公平锁的例子:
输出结果:
咱们能够看到若是使用非公平锁的话最后输出的结果是彻底没有顺序的,先到不必定先得。
因此在使用公平锁的时候线程1获取到锁以后线程2在请求锁的话就会挂起等待线程1释放锁,而后线程2才能获取锁。若是再有一个线程3想要请求锁的话,这时候若是使用的是非公平锁,那么线程2和线程3中两个有一个会获取到锁,公平锁的状况下线程3只能先挂起,等待线程2获取锁资源释放后在获取。
在须要公平资源的场景下使用公平锁,若是不须要特殊的公平对待的话尽可能使用非公平锁,由于公平锁会带来性能的开销。
看到独占和共享会联想到什么,对的独占锁就是每次只有一个线程能霸占这个锁资源,而其余线程就只能等待当前获取锁资源的线程释放锁才能再次获取锁,刚刚上面的ReentrantLock就是独占锁,那这样看来独占锁不也就是悲观锁吗?由于悲观锁抢占资源后就只能等待释放其余线程才能再次获取到锁资源。其实准确的说独占锁也是悲观锁。
在谈共享锁,共享锁其实也是乐观锁它放宽了锁的策略容许多个线程同时获取锁。在并发包中ReadWriteLock就是一个典型的共享锁。它容许一个资源能够被多个读操做访问,或者被一个 写操做访问,但二者不能同时进行。
什么是自旋锁,自旋锁其实就是当一个线程获取锁的时候,这个锁已经被其余人获取到了那么这个线程不会立马挂起,反而在不放弃CPU使用权的状况下会尝试再次获取锁资源,默认次数是10次,可使用-XX: PreBlockSpinsh来设置次数。若是自旋锁获取锁的时间太长,会形成后面的线程CPU资源耗尽释放。而且自旋锁是不公平的。
自旋锁不会使线程状态发生切换,一直处于用户态,即线程一直都是active的;不会使线程进入阻塞状态,减小了没必要要的上下文切换,执行速度快。
生活中有各类意想不到的情况,Java中也有各类意想不到的异常,下次咱们聊聊Java中的异常,欢迎转发关注