併發編程的核心簡單表述就是一下三點:
1.原子性
所謂原子性是指在一次的操作或者多次的操作中,要麼所有的操作全部都得到了執行並且不會受到任何因素的干擾而中斷,要麼所有的操作都不執行。i++不能保證原子性。synchronized關鍵字保證
多個原子性操作合在一起就不是原子性操作了
簡單的讀取和賦值操作是原子性的,將一個變量賦值給另外一個變量的操作不是原子性的
由於synchronized是一種排他機制,因此被他修飾的同步代碼是無法被中途打斷的,因此其能夠保證代碼的原子性。
2.可見性
可見性是指當一個線程對共享變量進行了修改,那麼另外的線程可以立即看到修改後的最新值
synchronized和Lock保證可見性,他們會在(monitor exit)鎖釋放之前,會將對變量(共享資源)的修改刷新到主內存中
3.有序性
有序性是指程序代碼在執行過程中的先後順序,由於Java在編譯器以及運行期的優化,導致了代碼的執行順序未必就是開發者編寫代碼時的順序。
ReentrantLock
ReentrantLock和synchronized在功能上ReentrantLock要更爲豐富更加靈活。性能上ReentrantLock也要略微由於synchronized
//ReentrantLock 簡單實現同步鎖
public class ReentrantLockTest extends Thread {
public static ReentrantLock lock = new ReentrantLock();
public static int i = 0;
public ReentrantLockTest(String name) {
super.setName(name);
}
@Override
public void run() {
for (int j = 0; j < 300000; j++) {
lock.lock();
try {
System.out.println(this.getName() + " " + i);
i++;
} finally {
lock.unlock();
}
}
}
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
ReentrantLockTest test1 = new ReentrantLockTest("thread1");
ReentrantLockTest test2 = new ReentrantLockTest("thread2");
test1.start();
test2.start();
test1.join();
test2.join();
//最後的結果是 600000;如果去掉鎖,那麼輸出結果是一個小於600000的不確定的數
System.out.println(i);
}
}
公平鎖
ReentrantLock的有參構造可簡單實現公平鎖,採用的策略是等待時長越長的優先執行
/**
* 公平鎖
*/
public class ReentrantLockTest01 {
public static void main(String[] args) {
SysTest s = new SysTest();
// UnSysTest s = new UnSysTest();
Thread thread1 = new Thread(s);
Thread thread2 = new Thread(s);
thread1.start();
thread2.start();
}
}
class SysTest extends Thread{
public static ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
for(int i = 0; i<5 ;i++){
lock.lock();
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
System.out.println(Thread.currentThread().getName()+" get lock ");
}finally {
lock.unlock();
}
}
}
}
class UnSysTest extends Thread{
@Override
public void run() {
for(int i = 0; i<5 ;i++){
synchronized (this){
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" get lock ");
}
}
}
}
可響應中斷
當使用synchronized實現鎖時,阻塞在鎖上的線程除非獲得鎖否則將一直等待下去,也就是說這種無限等待獲取鎖的行爲無法被中斷。而ReentrantLock給我們提供了一個可以響應中斷的獲取鎖的方法lockInterruptibly()。該方法可以用來解決死鎖問題。
public class ReentrantLockTest {
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ThreadDemo(lock1, lock2));//該線程先獲取鎖1,再獲取鎖2
Thread thread1 = new Thread(new ThreadDemo(lock2, lock1));//該線程先獲取鎖2,再獲取鎖1
thread.start();
thread1.start();
thread.interrupt();//是第一個線程中斷
}
static class ThreadDemo implements Runnable {
Lock firstLock;
Lock secondLock;
public ThreadDemo(Lock firstLock, Lock secondLock) {
this.firstLock = firstLock;
this.secondLock = secondLock;
}
@Override
public void run() {
try {
firstLock.lockInterruptibly();
TimeUnit.MILLISECONDS.sleep(10);//更好的觸發死鎖
secondLock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
firstLock.unlock();
secondLock.unlock();
System.out.println(Thread.currentThread().getName()+"正常結束!");
}
}
}
}
嘗試鎖,獲取鎖時限時等待
ReentrantLock還給我們提供了獲取鎖限時等待的方法tryLock(),可以選擇傳入時間參數,表示等待指定的時間,無參則表示立即返回鎖申請的結果:true表示獲取鎖成功,false表示獲取鎖失敗。我們可以使用該方法配合失敗重試機制來更好的解決死鎖問題。
public class ReentrantLockTest {
static Lock lock1 = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new ThreadDemo(lock1, lock2));//該線程先獲取鎖1,再獲取鎖2
Thread thread1 = new Thread(new ThreadDemo(lock2, lock1));//該線程先獲取鎖2,再獲取鎖1
thread.start();
thread1.start();
}
static class ThreadDemo implements Runnable {
Lock firstLock;
Lock secondLock;
public ThreadDemo(Lock firstLock, Lock secondLock) {
this.firstLock = firstLock;
this.secondLock = secondLock;
}
@Override
public void run() {
try {
while(!firstLock.tryLock()){
TimeUnit.MILLISECONDS.sleep(10);
}
while(!secondLock.tryLock()){
lock1.unlock();
TimeUnit.MILLISECONDS.sleep(10);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
firstLock.unlock();
secondLock.unlock();
System.out.println(Thread.currentThread().getName()+"正常結束!");
}
}
}
}
Condition
Condition由ReentrantLock對象創建,並且可以同時創建多個
static Condition notEmpty = lock.newCondition();
static Condition notFull = lock.newCondition();
Condition接口在使用前必須先調用ReentrantLock的lock()方法獲得鎖。之後調用Condition接口的await()將釋放鎖,並且在該Condition上等待,直到有其他線程調用Condition的signal()方法喚醒線程(signalAll()喚醒所有)。使用方式和wait,notify類似。
阻塞隊列是一種特殊的先進先出隊列,它有以下幾個特點
1.入隊和出隊線程安全
2.當隊列滿時,入隊線程會被阻塞;當隊列爲空時,出隊線程會被阻塞。
public class TestContainer<E> {
private final LinkedList<E> list = new LinkedList();
private final int MAX =10;
private int count;
private ReentrantLock lock = new ReentrantLock();
private Condition producer = lock.newCondition();
private Condition constumer =lock.newCondition();
public int getCount(){
return count;
}
public void put(E e){
lock.lock();
try {
while (list.size() == MAX){
System.out.println(Thread.currentThread().getName()+" 等待,,,");
//藉助條件生產者進入等待隊列,釋放鎖
producer.await();
}
count++;
System.out.println(Thread.currentThread().getName()+" put,,,");
list.add(e);
//藉助條件喚醒所有消費者
constumer.signalAll();
}catch (InterruptedException e1) {
e1.printStackTrace();
}finally {
lock.unlock();
}
}
public E get(){
E e = null;
lock.lock();
try {
while (list.size() == 0){
System.out.println(Thread.currentThread().getName()+" 等待,,,");
//藉助條件消費者進入等待隊列,釋放鎖
constumer.await();
}
count++;
System.out.println(Thread.currentThread().getName()+" get,,,");
e = list.remove();
//藉助條件喚醒所有生產者
producer.signalAll();
}catch (InterruptedException e1) {
e1.printStackTrace();
}finally {
lock.unlock();
}
return e;
}
public static void main(String[] args) {
TestContainer<String> c = new TestContainer<>();
for (int i= 0 ; i <3 ; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 0;j < 3;j++ ){
System.out.println(c.get());
}
}
},"constumer"+i).start();
}
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i= 0 ; i < 2 ; i++){
new Thread(new Runnable() {
@Override
public void run() {
for (int j = 1;j <= 5;j++ ){
c.put("Container value "+j);
}
}
},"producer"+1).start();
}
}
}
打印結果:
constumer0 等待,,,
constumer1 等待,,,
constumer2 等待,,,
producer1 put,,,
producer1 put,,,
constumer0 get,,,
Container value 1
constumer1 get,,,
Container value 2
constumer2 等待,,,
producer1 put,,,
producer1 put,,,
producer1 put,,,
producer1 put,,,
producer1 put,,,
producer1 put,,,
producer1 put,,,
producer1 put,,,
constumer0 get,,,
Container value 1
constumer1 get,,,
Container value 2
constumer2 get,,,
Container value 3
constumer0 get,,,
Container value 4
constumer1 get,,,
Container value 5
constumer2 get,,,
Container value 3
constumer2 get,,,
Container value 4
ReentrantLock是可重入的獨佔鎖。比起synchronized功能更加豐富,支持公平鎖實現,支持中斷響應以及限時等待等等。可以配合一個或多個Condition條件方便的實現等待通知機制。
ReentrantLock還有很多豐富的api,比如可以獲取一些線程隊列信息owner和monitor 。