多个执行线程共享一个资源的情景,是最多见的并发编程情景之一。在并发应用中经常遇到这样的情景:多个线程读或者写相同的数据,或者访问相同的文件或者数据库链接。java
为了防止这些共享资源可能出现的错误或数据不一致,咱们必须实现一些机制来防止这些错误的发生。数据库
为了解决这些问题,人们引入了临界区概念。临界区是一个可用以访问共享资源的代码块,这个代码块在同一时间内只容许一个线程执行。编程
为了帮助编程人员实行这个临界区,java提供了同步机制。当一个线程试图访问一个临界区时,它将使用一种同步机制来查看是否是已经有其余线程进入临界区。若是没有其余线程进入临界区,它就能够进入临界区;若是已经有线程进入了临界区,它就被同步机制挂起,直到进入的线程离开这个临界区。若是在等待进入临界区的线程不止一个,JVM会选择其中一个,其他的将继续等待。安全
java语言提供了两种基本同步机制:数据结构
1.synchronized关键字机制多线程
2.Lock接口及其实现机制并发
1、synchronized关键字机制dom
1.同步方法
即有synchronized关键字修饰的方法。
因为java的每一个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法。在调用该方法前,须要得到内置锁,不然就处于阻塞状态。
代码如:
public synchronized void save(){}
对于实例的同步方法,使用this即当前实例对象。
对于静态的同步方法,使用当前类的字节码对象。
注: synchronized关键字也能够修饰静态方法,此时若是调用该静态方法,将会锁住整个类。
2.同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:
synchronized(object){}
Java中任意的对象均可以做为一个监听器(monitor),监听器能够被上锁和解锁,在线程同步中称为同步锁,且同步锁在同一时间只能被一个线程所持有。上面的obj对象就是一个同步锁,分析一下上面代码的执行过程:
1).一个线程执行到synchronized代码块,首先检查obj,若是obj为空,抛出NullPointerExpression异常;
2).若是obj不为空,线程尝试给监听器上锁,若是监听器已经被锁,则线程不能获取到锁,线程就被阻塞;
3).若是监听器没被锁,则线程将监听器上锁,而且持有该锁,而后执行代码块;
4).代码块正常执行结束或者非正常结束,监听器都将自动解锁;
线程同步锁对多个线程必须是互斥的,即多个线程须要使用同一个同步锁。代码中obj对象被多个线程共享,可以实现同步。
注:同步是一种高开销的操做,所以应该尽可能减小同步的内容。
一般没有必要同步整个方法,使用synchronized代码块同步关键代码便可。
二者的区别主要体如今同步锁上面。对于实例的同步方法,由于只能使用this来做为同步锁,若是一个类中须要使用到多个锁,为了不锁的冲突,必然须要使用不一样的对象,这时候同步方法不能知足需求,只能使用同步代码块(同步代码块能够传入任意对象);或者多个类中须要使用到同一个锁,这时候多个类的实例this显然是不一样的,也只能使用同步代码块,传入同一个对象。ide
2、Lock接口及其实现机制高并发
Lock接口及实现类提供了更多的好处:
1.支持更灵活的同步代码块结构。使用synchronized关键字时,只能在同一个synchronized块结构中获取和释放控制。Lock接口容许实现更复杂的临界区结构,即控制的获取和释放不出如今同一个块结构中。
2.相比synchronized关键字,Lock接口提供了更多的功能。其中一个新功能是tryLock()方法的实现。这个方法试图获取锁,若是锁已被其余线程获取,它将返回false,并继续往下执行代码。使用synchronized关键字时,若是线程A试图执行一个同步代码块,而线程B已在执行这个同步代码块,则线程A就会被挂起直到线程B运行完这个同步代码块。使用锁tryLock()方法,经过返回值将得知是否有其余线程正在使用这个锁保护的代码块。
3.Lock接口容许分离读和写操做,容许多个读线程和只有一个写线程。
4.相比synchronized关键字,Lock接口具备更好的性能。
锁的公平性
ReentrantLock和ReentrantReadWriteLock类的构造器都含有一个布尔参数fair,它容许你控制着两个类的行为。默认fair值为false,它称为非公平模式。
在非公平模式下,当有不少线程在等待锁时,锁将选择它们中的一个来访问临界区,这个选择是没有任何约束的。
若是fair值为true,则称为公平模式。
在公平模式下,当有不少线程在等待锁时,锁将选择它们中的一个来访问临界区,并且选择的是等待时间最长的。
这两种模式只适用于lock()和unlock()方法。而Lock接口的tryLock()方法没有将线程置于休眠,fair属性并不影响这个方法。
经常使用的线程类:
1)、ReentrantLock类:是一个可重入的互斥锁,重入锁是一种递归无阻塞的同步机制。ReentrantLock由最近成功获取锁,尚未释放的线程所拥有,当锁被另外一个线程拥有时,调用lock的线程能够成功获取锁。若是锁已经被当前线程拥有,当前线程会当即返回。
a、防止重复执行(忽略重复触发)
ReentrantLock lock = new ReentrantLock();
public void getObject(){
//若是已经被lock,则当即返回false不会等待,达到忽略操做的效果
if (lock.tryLock()) {
try {
//操做
} finally {
lock.unlock();
}
}
}
b、同步执行,相似synchronized
ReentrantLock lock = new ReentrantLock(); //参数默认false,不公平锁
//ReentrantLock lock = new ReentrantLock(true); //公平锁
public void getObject(){
//若是被其它资源锁定,会在此等待锁释放,达到暂停的效果
lock.lock();
try {
//操做
} finally {
lock.unlock();
}
}
c、尝试等待执行
ReentrantLock lock = new ReentrantLock(true); //公平锁
public void getObject(){
try {
if (lock.tryLock(5, TimeUnit.SECONDS)) {
//若是已经被lock,尝试等待5s,看是否能够得到锁,若是5s后仍然没法得到锁则返回false继续执行
try {
//操做
} finally {
lock.unlock();
}
}
} catch (InterruptedException e) {
e.printStackTrace(); //当前线程被中断时(interrupt),会抛InterruptedException
}
}
d、可中断锁的同步执行
ReentrantLock lock = new ReentrantLock(true); //公平锁
public void getObject(){
lock.lockInterruptibly();
try {
//操做
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
ReadWriteLock类:读写锁,维护了一对相关的锁,一个用于只读操做,一个用于写入操做。只要没有writer,读取锁能够由多个reader线程同时保持。写入锁是独占的。
ReentrantReadWriteLock类:可重入读写锁,会使用两把锁来解决问题,一个读锁,一个写锁。
线程进入读锁的前提条件:
1).没有其余线程的写锁,
2).没有写请求或者有写请求,但调用线程和持有锁的线程是同一个
线程进入写锁的前提条件:
1).没有其余线程的读锁
2).没有其余线程的写锁
ReentrantReadWriteLock和ReentrantLock的区别,它和后者都是单独的实现,彼此之间没有继承或实现的关系:
(a).重入方面其内部的WriteLock能够获取ReadLock,可是反过来ReadLock想要得到WriteLock则永远都不要想。
(b).WriteLock能够降级为ReadLock,顺序是:先得到WriteLock再得到ReadLock,而后释放WriteLock,这时候线程将保持Readlock的持有。反过来ReadLock想要升级为WriteLock则不可能。
(c).ReadLock能够被多个线程持有而且在做用时排斥任何的WriteLock,而WriteLock则是彻底的互斥。这一特性最为重要,由于对于高读取频率而相对较低写入的数据结构,使用此类锁同步机制则能够提升并发量。
(d).不论是ReadLock仍是WriteLock都支持Interrupt,语义与ReentrantLock一致。
(e).WriteLock支持Condition而且与ReentrantLock语义一致,而ReadLock则不能使用Condition,不然抛出UnsupportedOperationException异常。
读写锁的例子:
import java.util.Random;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class ReadWriteLockTest {
public static void main(String[] args) {
Queue3 q3 = new Queue3();
for(int i=0;i<3;i++){
new Thread(){
public void run(){
while(true){
q3.get();
}
}
}.start();
}
for(int i=0;i<3;i++){
new Thread(){
public void run(){
while(true){
q3.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue3{
private Object data = null;//共享数据,只能有一个线程能写该数据,但能够有多个线程同时读该数据。
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
public void get(){
rwl.readLock().lock();//上读锁,其余线程只能读不能写
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "have read data :" + data);
rwl.readLock().unlock(); //释放读锁,最好放在finnaly里面
}
public void put(Object data){
rwl.writeLock().lock();//上写锁,不容许其余线程读也不容许写
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
try {
Thread.sleep((long)(Math.random()*1000));
} catch (InterruptedException e) {
e.printStackTrace();
}
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
rwl.writeLock().unlock();//释放写锁
}
}
在锁中使用多条件
一个锁可能关联一个或多个条件,这些条件经过Condition接口声明。目的是容许线程获取锁而且查看等待的某一个条件是否知足,若是不知足就挂起直到某个线程唤醒它们。Condition接口提供了挂起线程和唤起线程的机制。
Condition是在java1.5中才出现的,它用来替代传统的Object的wait()、notify()实现线程间的协做,相比使用Object的wait()、notify(),使用Condition1的await()、signal()这种方式实现线程间协做更加安全和高效。Condition是个接口,基本的方法就是await()和signal()方法;Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition()调用Condition的await()和signal()方法,都必须在lock保护以内,就是说必须在lock.lock()和lock.unlock之间才可使用Conditon中的await()对应Object的wait();Condition中的signal()对应Object的notify();Condition中的signalAll()对应Object的notifyAll()。代码示例:import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class Main { public static void main(String[] args) { final ReentrantLock reentrantLock = new ReentrantLock(); final Condition condition = reentrantLock.newCondition(); new Thread(new Runnable() { @Override public void run() { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "拿到锁了"); System.out.println(Thread.currentThread().getName() + "等待信号"); try { condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "拿到信号"); reentrantLock.unlock(); } }, "线程1").start(); new Thread(new Runnable() { @Override public void run() { reentrantLock.lock(); System.out.println(Thread.currentThread().getName() + "拿到锁了"); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "发出信号"); condition.signalAll(); reentrantLock.unlock(); } }, "线程2").start(); } }