锁是用来控制多个线程访问同一个共享资源的方式,通常来讲,一个锁能防止多个线程同时访问共享资源,在Lock接口出来以前,Java是经过synchronized关键字来实现锁的功能,而Java1.5以后,并发包新增了Lock接口(以及相关实现类)用来实现锁的功能,它提供了与synchronized关键字相似的同步功能,只是在使用方式上有所不一样,须要显式的获取锁和释放锁。虽然缺乏了隐式的便捷性,但却拥有了锁获取和释放的可操做性,可中断的获取因此及超时获取锁的的同步特性java
特性 | 描述 |
---|---|
尝试非阻塞式获取锁 | 当前线程尝试获取锁,若是这一刻没有被其余线程获取到,则成功获取并持有锁 |
能被中断的获取锁 | 与synchronized关键字不一样,获取到锁的线程可以响应中断,当获取到锁的线程被中断时,中断异常会被抛出,同时锁会被释放 |
超时获取锁 | 在指定的截止时间以前获取到锁,入伙截止时间到了仍旧没法获取锁,则返回 |
方法名称 | 描述 |
---|---|
void lock() | 获取锁,调用该方法当前线程将会获取锁,当锁获取到时,从该方法返回 |
void lockInterruptibly() throws InterruptedException() | 可中断的获取锁,和lock()方法的不一样之处在于该方法可响应中断,即在锁的获取中和中断当前线程 |
boolean tryLock() | 尝试非阻塞的获取锁,调用该方法马上返回,若是可以获取则返回true,不然返回false |
boolean tryLock(long time,TimeUnit unit) throws InterruptedException() | 超时的获取锁,当前线程在如下三种状况会返回:1.当前线程在超时时间内获取到锁 2. 当前线程在超时时间内被中断 3. 超时时间结束,返回false |
void unlock() | 释放锁 |
Condition newCondition() | 获取等待通知组件,该组件和当前的锁绑定,当前线程只有得到了锁,才能调用该组件的wait()方法,而调用后,当前线程释放锁 |
如下简称AQS AQS是用来构建锁和其余同步组件的基础框架,它使用了一个int成员变量表示同步状态,经过内置的FIFO队列来完成资源获取线程排队工做redis
AQS给予模板方法设计模式设计的,也就是说,使用者须要继承AQS并重写指定的方法进行实现算法
AQS提供以下三个方法来访问和修改同步状态:数据库
方法名称 | 描述 |
---|---|
protected boolean tryAcquire(int arg) | 独占式的获取同步状态,实现该方法须要查询当前状态并判断同步状态是否符合预期,而后再进行CAS设置同步状态 |
protected boolean tryRelease(int arg) | 独占式释放同步状态,等待获取同步状态的线程将有机会获取同步状态 |
protected int tryAcquireShared(int arg) | 共享式获取同步状态,返回大于等于0的值,表示获取成功,反之获取失败 |
protected boolean tryReleaseShared(int arg) | 共享式释放同步状态 |
protected boolean isHeldExclusively() | 当前同步器是否在独占模式下被线程占用,通常该方法表示是否被当前线程所独占 |
方法名称 | 描述 |
---|---|
void acquire(int arg) | 独占式获取同步状态,若是当前线程获取同步状态成功,则该方法返回,不然,将进入同步队列等待,该方法将会调用重写的tryAcquire(int arg)方法 |
void acquireInterruptibly(int arg) | 与acquire(int arg)相同,可是该方法响应中断,当前线程未获取到同步状态而进入同步队列中,若是当前线程被中断,则该方法抛出异常并返回 |
boolean tryAcquireNanos(int arg,long nanos) | 在acquireInterruptibly(int arg)基础上增长了超时限制,若是当前线程在超时时间内没有获取到同步状态,那么将会返回false,若是获取到了则返回true |
void acquireShared(int arg) | 共享式的获取同步状态,若是当前线程未获取到同步状态,将会进入同步队列中进行等待,与独占式的区别主要在于同一时刻能够有多个线程获取到同步状态 |
void acquireSharedInterruptibly(int arg) | 与acquireInterruptibly(int arg)相同,该方法可响应中断 |
boolean tryAcquireSharedNanos(int arg,long nanos) | 在acquireSharedInterruptibly(int arg)基础上增长了超时限制 |
boolean release(int arg) | 独占式的释放同步状态,该方法会在释放同步状态以后,将同步队列中的第一个节点包含的线程唤醒 |
boolean releaseShared(int arg) | 共享式的释放同步状态 |
Collection getQueueThreads() | 获取等待在同步队列上的线程集合 |
重入锁,也叫作递归锁,指的是同一线程外层函数得到锁以后,内层递归函数仍然有获取该锁的代码,但不受影响。编程
在JAVA环境下ReentrantLock和sypnchronized都是可重入锁设计模式
public class Test implements Runnable {
public synchronized void get() {
System.out.println("name:" + Thread.currentThread().getName() + " get();");
set();
}
public synchronized void set() {
System.out.println("name:" + Thread.currentThread().getName() + " set();");
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
复制代码
public class Test02 extends Thread {
ReentrantLock lock = new ReentrantLock();
public void get() {
lock.lock();
System.out.println(Thread.currentThread().getId());
set();
lock.unlock();
}
public void set() {
lock.lock();
System.out.println(Thread.currentThread().getId());
lock.unlock();
}
@Override
public void run() {
get();
}
public static void main(String[] args) {
Test ss = new Test();
new Thread(ss).start();
new Thread(ss).start();
new Thread(ss).start();
}
}
复制代码
相比Java中的锁(Locks in Java)里Lock实现,读写锁更复杂一些。假设你的程序中涉及到对一些共享资源的读和写操做,且写操做没有读操做那么频繁。在没有写操做的时候,两个线程同时读一个资源没有任何问题,因此应该容许多个线程能在同时读取共享资源。可是若是有一个线程想去写这些共享资源,就不该该再有其它线程对该资源进行读或写(译者注:也就是说:读-读能共存,读-写不能共存,写-写不能共存)。 这就须要一个读/写锁来解决这个问题。Java5在java.util.concurrent包中已经包含了读写锁。尽管如此,咱们仍是应该了解其实现背后的原理。缓存
public class Cache {
static Map<String, Object> map = new HashMap<String, Object>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
static Lock r = rwl.readLock();
static Lock w = rwl.writeLock();
// 获取一个key对应的value
public static final Object get(String key) {
r.lock();
try {
System.out.println("正在作读的操做,key:" + key + " 开始");
Thread.sleep(100);
Object object = map.get(key);
System.out.println("正在作读的操做,key:" + key + " 结束");
System.out.println();
return object;
} catch (InterruptedException e) {
} finally {
r.unlock();
}
return key;
}
// 设置key对应的value,并返回旧有的value
public static final Object put(String key, Object value) {
w.lock();
try {
System.out.println("正在作写的操做,key:" + key + ",value:" + value + "开始.");
Thread.sleep(100);
Object object = map.put(key, value);
System.out.println("正在作写的操做,key:" + key + ",value:" + value + "结束.");
System.out.println();
return object;
} catch (InterruptedException e) {
} finally {
w.unlock();
}
return value;
}
// 清空全部的内容
public static final void clear() {
w.lock();
try {
map.clear();
} finally {
w.unlock();
}
}
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.put(i + "", i + "");
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
Cache.get(i + "");
}
}
}).start();
}
}
复制代码
老是认为不会产生并发问题,每次去取数据的时候总认为不会有其余线程对数据进行修改,所以不会上锁,可是在更新时会判断其余线程在这以前有没有对数据进行修改,通常会使用版本号机制或CAS操做实现,本质没有锁,效率比较高,无阻塞,无等待,重试安全
实现方式服务器
update table set x=x+1, version=version+1 where id=#{id} and version=#{version};
复制代码
老是假设最坏的状况,每次取数据时都认为其余线程会修改,因此都会加锁(读锁、写锁、行锁等),当其余线程想要访问数据时,都须要阻塞挂起。能够依靠数据库实现,如行锁、读锁和写锁等,都是在操做以前加锁,在Java中,synchronized的思想也是悲观锁。属于重量级锁,会阻塞,会等待并发
若是想在不一样的jvm中保证数据同步,使用分布式锁技术。有数据库实现、缓存redis实现、Zookeeper分布式锁
所以咱们要慎重使用自旋锁,自旋锁只有在内核可抢占式或SMP的状况下才真正须要,在单CPU且不可抢占式的内核下,自旋锁的操做为空操做。自旋锁适用于锁使用者保持锁时间比较短的状况下。
至于自旋锁就主要用在临界区持锁时间很是短且CPU资源不紧张的状况下,自旋锁通常用于多核的服务器。
非公平锁:在等待锁的过程当中,若是有人以新的线程妄图获取锁,都是有很大概率直接获取到锁的。白话文:公平锁是先到先得,按序进行,非公平锁就是不排队直接拿,失败再说。
Compare and Swap,即比较再交换。jdk5增长了并发包java.util.concurrent.*,其下面的类使用CAS算法实现了区别于synchronouse同步锁的一种乐观锁。JDK 5以前Java语言是靠synchronized关键字保证同步的,这是一种独占锁,也是是悲观锁。
与锁相比,使用比较交换(下文简称CAS)会使程序看起来更加复杂一些。但因为其非阻塞性,它对死锁问题天生免疫,而且,线程间的相互影响也远远比基于锁的方式要小。更为重要的是,使用无锁的方式彻底没有锁竞争带来的系统开销,也没有线程间频繁调度带来的开销,所以,它要比基于锁的方式拥有更优越的性能。
java.util.concurrent.atomic包:原子类的小工具包,支持在单个变量上解除锁的线程安全编程原子变量类至关于一种泛化的 volatile 变量,可以支持原子的和有条件的读-改-写操做。AtomicInteger 表示一个int类型的值,并提供了 get 和 set 方法,这些 Volatile 类型的int变量在读取和写入上有着相同的内存语义。它还提供了一个原子的 compareAndSet 方法(若是该方法成功执行,那么将实现与读取/写入一个 volatile 变量相同的内存效果),以及原子的添加、递增和递减等方法。AtomicInteger 表面上很是像一个扩展的 Counter 类,但在发生竞争的状况下能提供更高的可伸缩性,由于它直接利用了硬件对并发的支持。
若是同一个变量要被多个线程访问,则可使用该包中的类 AtomicBoolean AtomicInteger AtomicLong AtomicReference