Java高新技術——多線程與併發庫(上)

本系列文章旨在分享Java5多線程與並法庫的高級應用示例,所用到的大多數類均在java.util.concurrent包下。

傳統線程技術回顧

package ustc.lichunchun.thread;

/*
 * 創建線程的兩種傳統方式
 */
public class TraditionalThread {

	public static void main(String[] args) {
		
		//在Thread子類覆蓋的run方法中編寫運行代碼
		Thread t1 = new Thread(){
			public void run(){
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("1: " + Thread.currentThread().getName());
					System.out.println("2: " + this.getName());
				}
			}
		};
		t1.start();

		//-----------------------------------------------------------
		
		//在傳遞給Thread對象的Runnable對象的run方法中編寫代碼
		Thread t2 = new Thread(new Runnable(){

			@Override
			public void run() {
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("1: " + Thread.currentThread().getName());
				}
			}
			
		});
		t2.start();
		
		//-----------------------------------------------------------
		
		//涉及知識點:匿名內部類對象的構造方法如何調用父類的非默認構造方法
		new Thread(new Runnable(){

			@Override
			public void run() {
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("Runnable: " + Thread.currentThread().getName());
				}
			}
			
		}){
			public void run(){
				while(true){
					try{
						Thread.sleep(1000);
					}catch(InterruptedException e){
						e.printStackTrace();
					}
					System.out.println("Thread: " + Thread.currentThread().getName());
				}
			}
		}.start();//Thread: Thread-2
	}

}

生產者消費者模式回顧

package ustc.lichunchun.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Res{
	private String name;
	private int count = 1;
	private boolean flag;
	
	private Lock lock = new ReentrantLock();
	private Condition producer_con = lock.newCondition(); 
	private Condition consumer_con = lock.newCondition();
	
	public void set(String name){
		lock.lock();
		try{
			while(flag){
				try{
					producer_con.await();
				}catch(InterruptedException e){
					//e.printStackTrace();
				}
			}
			this.name = name + "-" + count;
			count++;
			System.out.println(Thread.currentThread().getName()+ "......生產者 ...... " + this.name);
			this.flag = true;
			consumer_con.signal();
		}finally{
			lock.unlock();
		}
	}
	
	public void get(){
		lock.lock();
		try{
			while(!flag){
				try{
					consumer_con.await();
				}catch(InterruptedException e){
					//e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName()+ "...消費者 ... " + this.name);
			this.flag = false;
			producer_con.signal();
		}finally{
			lock.unlock();
		}
	}
}

class Producer implements Runnable{
	private Res r;
	Producer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
		while(true){
			r.set("小龍蝦");
		}
	}
	
}

class Consumer implements Runnable{
	private Res r;
	Consumer(Res r){
		this.r = r;
	}
	@Override
	public void run() {
		while(true){
			r.get();
		}
	}
	
}

public class ProducerConsumerDemo {

	public static void main(String[] args) {
		Res r = new Res();
		Producer pro = new Producer(r);
		Consumer con = new Consumer(r);
		Thread t0 = new Thread(pro);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		Thread t3 = new Thread(con);
		t0.start();
		t1.start();
		t2.start();
		t3.start();
	}

}

傳統定時器技術回顧

package ustc.lichunchun.thread;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

//完成一個定時調度的程序,每個2秒打印一次時間

class MyTask extends TimerTask{	//任務調度類都要繼承TimerTask

	@Override
	public void run() {
		SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS");
		System.out.println("當前的系統時間爲:" + sdf.format(new Date()));
	}
	
}

public class TraditionalTimerDemo {

	public static void main(String[] args) {
		Timer t = new Timer();	//建立Timer類對象
		MyTask mytask = new MyTask();	//定義任務
		t.schedule(mytask, 1000, 2000);	//設置任務的執行,1秒後開始,每2秒重複
	}

}
schedule()與scheduleAtFixedRate()方法的區別:
兩者的區別在於重複執行任務時,對於時間間隔出現延遲的情況處理。
|--schedule()方法的執行時間間隔永遠是固定的,如果之前出現了延遲的情況,之後也會繼續按照設定好的間隔時間來執行
|--scheduleAtFixedRate()方法可以根據出現的延遲時間自動調整下一次間隔的執行時間
一般在web的開發中此內容比較有用,因爲要維護一個容器不關閉纔可以一直定時操作下去。

package ustc.lichunchun.thread;

import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;

public class TraditionalTimerTest {

	private static int count = 0;
	
	class MyTimerTaskA extends TimerTask{
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + "......Bombing!");
			new Timer().schedule(new MyTimerTaskB(), 4000);
		}
	}
	
	class MyTimerTaskB extends TimerTask{
		@Override
		public void run() {
			System.out.println(Thread.currentThread().getName() + "......Bombing!");
			new Timer().schedule(new MyTimerTaskA(), 2000);
		}
	}
	
	public static void main(String[] args) {
		
		//1.主線程每個1秒打印一次當前秒數,定時器Timer線程設定爲10秒後執行單次bombing打印
		new Timer().schedule(new TimerTask() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "......Bombing!");
			}
		}, 10000);
		
		while(true){
			System.out.println(Thread.currentThread().getName() + "..." + new Date().getSeconds());
			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		//--------------------------------------------------------------------
		
		//2.Timer線程10秒後, 每隔2秒打印一次bombing
		new Timer().schedule(new TimerTask() {
			
			@Override
			public void run() {
				System.out.println(Thread.currentThread().getName() + "......Bombing!");
			}
		}, 10000, 2000);
		
		//--------------------------------------------------------------------
		
		//3.1.Timer線程每2秒、4秒各炸一次,循環往復(方法一)
		new Timer().schedule(new TraditionalTimerTest().new MyTimerTaskA(), 2000);
		while(true){
			System.out.println(Thread.currentThread().getName() + "..." + new Date().getSeconds());
			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		

		//--------------------------------------------------------------------
		
		//3.2.Timer線程每2秒、4秒各炸一次,循環往復(方法二)
		class MyTimerTaskC extends TimerTask{
			@Override
			public void run() {
				count = (count + 1) % 2;
				System.out.println(Thread.currentThread().getName() + "......Bombing!");
				new Timer().schedule(new MyTimerTaskC(), 2000 + 2000*count);
			}
		}
		new Timer().schedule(new MyTimerTaskC(), 2000);
		while(true){
			System.out.println(Thread.currentThread().getName() + "..." + new Date().getSeconds());
			try{
				Thread.sleep(1000);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
	}

}

傳統線程互斥技術


package ustc.lichunchun.thread;

import java.util.concurrent.locks.ReentrantLock;

/*
 * 線程要運行的代碼就相當於共享資源廁所的一個坐席,互斥鎖就相當於廁所坐席裏的門閂。
 */
public class TraditionalThreadSynchronized {

	public static void main(String[] args) {
		new TraditionalThreadSynchronized().init();
		
		/*
		final ReentrantLock lock = new ReentrantLock();
		Thread t = new Thread(){
			public void run() {
				lock.lock();
				System.out.println("thread t execute");
				lock.unlock();
			};
		};
		lock.lock();
		lock.lock();
		t.start();
		Thread.sleep(200);
		System.out.println("release one once");
		lock.unlock();
		上面的代碼會出現死鎖,因爲主線程2次獲取了鎖,但是卻只釋放1次鎖,導致線程t永遠也不能獲取鎖。
		*/
	}
	private void init(){
		final Outputer outputer = new Outputer();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output("lichunchun");
				}
			}
		}).start();
		new Thread(new Runnable() {
			@Override
			public void run() {
				while(true){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					outputer.output2("alibaba");
				}
			}
		}).start();
	}
	static class Outputer{
		public void output(String name){
			int len = name.length();
			synchronized (Outputer.class) {	//this<-->output1() || Outputer.class<-->output2()
				for(int i = 0; i < len; i++){
					System.out.print(name.charAt(i));
				}
				System.out.println();
				//output2(name);//synchronized也是一把可重入鎖,
			}
		}
		public synchronized void output1(String name){
			int len = name.length();
			for(int i = 0; i < len; i++){
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}
		public static synchronized void output2(String name){
			int len = name.length();
			for(int i = 0; i < len; i++){
				System.out.print(name.charAt(i));
			}
			System.out.println();
		}
	}

}

傳統線程同步通信技術

這裏,我們通過一道面試題來講解。

子線程循環10次,接着主線程循環100次,接着又回到子線程循環10次,接着再回到主線程又循環100次,如此循環50次。

方法一:

package ustc.lichunchun.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Business{
	private boolean bShouldSub = true;
	public synchronized void sub(int i){
		while(!bShouldSub){	//線程有可能在沒有被通知的時候"僞喚醒",所以用while判斷更加可靠
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j = 1; j <= 10; j++){
			System.out.println("sub thread sequence of " + j + ", loop of " + i);
		}
		bShouldSub = false;
		this.notify();
	}
	public synchronized void main(int i){
		while(bShouldSub){
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		for(int j = 1; j <= 100; j++){
			System.out.println("main thread sequence of " + j + ", loop of " + i);
		}
		bShouldSub = true;
		this.notify();
	}
}
public class TraditionalThreadCommunication {
	public static void main(String[] args) {
		final Business business = new Business();
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 1; i <= 50; i++){
					business.sub(i);
				}
			}
		}).start();
		for(int i = 1; i <= 50; i++){
			business.main(i);
		}
	}
}
方法二:

/*
 * 下面使用jdk5中的併發庫來實現
 */
class Business{
	private boolean bShouldSub = true;
	private Lock lock = new ReentrantLock();
	private Condition condition = lock.newCondition();
	public void sub(int i){
		lock.lock();
		try{
			while(!bShouldSub){
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			for(int j = 1; j <= 10; j++){
				System.out.println("sub thread sequence of " + j + ", loop of " + i);
			}
			bShouldSub = false;
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
	public void main(int i){
		lock.lock();
		try{
			while(bShouldSub){
				try {
					condition.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			for(int j = 1; j <= 100; j++){
				System.out.println("main thread sequence of " + j + ", loop of " + i);
			}
			bShouldSub = true;
			condition.signal();
		}finally{
			lock.unlock();
		}
	}
}
public class TraditionalThreadCommunication{
	public static void main(String[] args) {
		final Business business = new Business();
		new Thread(new Runnable(){
			@Override
			public void run() {
				for(int i = 1; i <= 50; i++){
					business.sub(i);
				}
			}
		}).start();
		for(int i = 1; i <= 50; i++){
			business.main(i);
		}
	}
}

線程範圍內的共享變量原理

package ustc.lichunchun.thread;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;

/*
 * 線程範圍內的共享變量,各線程之間相互獨立
 */
public class ThreadScopeShareData {
	//private static int data = 0;
	private static Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();
	public static void main(String[] args) {
		for(int i = 0; i < 2; i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data: " + data);
					threadData.put(Thread.currentThread(), data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	static class A{
		public void get(){
			int data = threadData.get(Thread.currentThread());
			System.out.println("A from " + Thread.currentThread().getName() + " get data: " + data);
		}
	}
	static class B{
		public void get(){
			int data = threadData.get(Thread.currentThread());
			System.out.println("B from " + Thread.currentThread().getName() + " get data: " + data);
		}
	}
}


ThreadLocal類實現線程範圍內共享變量

    線程範圍內共享變量的應用:ThreadLocal類的實用技巧

    由上一節的原理代碼示例和插圖可以知道,ThreadLocal類的作用和目的:

    用於實現線程內的數據共享,即對於相同的程序代碼,多個模塊在同一個線程中運行時要共享一份數據,而在另外線程中運行時又共享另外一份數據。

    每個線程調用全局ThreadLocal對象的set方法,就相當於往其內部的map中增加一條記錄,key分別是各自的線程,value是各自的set方法傳進去的值。在線程結束時可以調用ThreadLocal.clear()方法,這樣會更快釋放內存,不調用也可以,因爲線程結束後也可以自動釋放相關的ThreadLocal變量。

    實現對ThreadLocal變量的封裝,讓外界不要直接操作ThreadLocal變量。
        ----對基本類型的數據的封裝,這種應用相對很少見。
        ----對對象類型的數據的封裝,比較常見,即讓某個類針對不同線程分別創建一個獨立的實例對象。

實驗案例:定義一個全局共享的ThreadLocal變量,然後啓動多個線程向該ThreadLocal變量中存儲一個隨機值,接着各個線程調用另外其他多個類的方法,這多個類的方法中讀取這個ThreadLocal變量的值,就可以看到多個類在同一個線程中共享同一份數據。

package ustc.lichunchun.thread;

import java.util.Random;
/*
定義一個全局共享的ThreadLocal變量,然後啓動多個線程向該ThreadLocal變量中存儲一個隨機值,
接着各個線程調用另外其他多個類的方法,這多個類的方法中讀取這個ThreadLocal變量的值,
就可以看到多個類在同一個線程中共享同一份數據。
*/
public class ThreadLocalTest{
	private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
	private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
	public static void main(String[] args) {
		for(int i = 0; i < 2; i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data: " + data);
					x.set(data);
					MyThreadScopeData myData = new MyThreadScopeData();
					myData.setName("name:"+data);
					myData.setAge(data);
					myThreadScopeData.set(myData);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	static class A{
		public void get(){
			int data = x.get();
			System.out.println("A from " + Thread.currentThread().getName() 
					+ " get data :" + data);
			MyThreadScopeData myData = myThreadScopeData.get();
			System.out.println("A from " + Thread.currentThread().getName() 
					+ " getMyData: " + myData.getName() + "," +
					myData.getAge());
		}
	}
	static class B{
		public void get(){
			int data = x.get();
			System.out.println("B from " + Thread.currentThread().getName() 
					+ " get data :" + data);
			MyThreadScopeData myData = myThreadScopeData.get();
			System.out.println("B from " + Thread.currentThread().getName() 
					+ " getMyData: " + myData.getName() + "," +
					myData.getAge());
		}
	}
}
進階版案例:

package ustc.lichunchun.thread;

import java.util.Random;

public class ThreadLocalTest {

	public static void main(String[] args) {
		for (int i = 0; i < 2; i++){
			new Thread(new Runnable(){
				@Override
				public void run() {
					int data = new Random().nextInt();
					System.out.println(Thread.currentThread().getName() + " has put data: " + data);
					MyThreadScopeData.getInstance().setName("name:" + data);
					MyThreadScopeData.getInstance().setAge(data);
					new A().get();
					new B().get();
				}
			}).start();
		}
	}
	static class A{
		public void get() {
			MyThreadScopeData myData = MyThreadScopeData.getInstance();
			System.out.println("A from " + Thread.currentThread().getName() 
					+ " getMyData: " + myData.getName() + ", age:" +
					myData.getAge());
		}
	}
	static class B{
		public void get() {
			MyThreadScopeData myData = MyThreadScopeData.getInstance();
			System.out.println("B from " + Thread.currentThread().getName() 
					+ " getMyData: " + myData.getName() + ", age:" +
					myData.getAge());
		}
	}
}
//基於餓漢單例模式的改造,每個線程對應一個MyThreadScopeData實例
class MyThreadScopeData{
	private MyThreadScopeData(){}
	
	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
	public static MyThreadScopeData getInstance(){
		MyThreadScopeData instance = map.get();
		if(instance == null){
			instance = new MyThreadScopeData();
			map.set(instance);
		}
		return instance;
	}
	
	private String name;
	private int age;
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}
}
ThreadLocal的應用場景
1.訂單處理包含一系列操作:減少庫存量、增加一條流水臺賬、修改總賬,這幾個操作要在同一個事務中完成,通常也即同一個線程中進行處理,
如果累加公司應收款的操作失敗了,則應該把前面的操作回滾,否則,提交所有操作,這要求這些操作使用相同的數據庫連接對象,而這些操作的代碼分別位於不同的模塊類中。
2.銀行轉賬包含一系列操作: 把轉出帳戶的餘額減少,把轉入帳戶的餘額增加,這兩個操作要在同一個事務中完成,
它們必須使用相同的數據庫連接對象,轉入和轉出操作的代碼分別是兩個不同的帳戶對象的方法。
3.例如Strut2的ActionContext,同一段代碼被不同的線程調用運行時,該代碼操作的數據是每個線程各自的狀態和數據,
對於不同的線程來說,getContext方法拿到的對象都不相同,對同一個線程來說,不管調用getContext方法多少次和在哪個模塊中getContext方法,拿到的都是同一個。

多個線程之間共享數據的方式探討

1. 如果每個線程執行的代碼相同,可以使用同一個Runnable對象,這個Runnable對象中有那個共享數據。

例如,四個窗口售100張票:

package ustc.lichunchun.thread;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MultiThreadShareData {

	public static void main(String[] args) {
		ShareData1 data1 = new ShareData1();
		new Thread(data1).start();
		new Thread(data1).start();
		new Thread(data1).start();
		new Thread(data1).start();
	}

}
class ShareData1 implements Runnable{
	private int count = 100;
	@Override
	public void run() {
		while(true){
			synchronized(this){
				if(this.count > 0){
					try {
						Thread.sleep(10);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()+": "+count--);
				}else{
					break;
				}
			}
		}
		
	}
}

2. 如果每個線程執行的代碼不同,這時候需要用不同的Runnable對象,有如下三種方式來實現這些Runnable對象之間的數據共享:

(1)將共享數據封裝在另外一個對象中,然後將這個對象逐一傳遞給各個Runnable對象。每個線程對共享數據的操作方法也分配到那個對象身上去完成,這樣容易實現針對該數據進行的各個操作的互斥和通信。

public class MultiThreadShareData{
	public static void main(String[] args) {
		ShareData data = new ShareData();//含有共享數據的對象只有一個,並作爲參數傳遞給各個Runnable線程對象作爲其各自的任務
		for(int i = 0; i < 2; i++){
			new Thread(new Inc(data)).start();
			new Thread(new Dec(data)).start();
		}
	}
}
class ShareData{
	private int j = 0;//共享數據
	private Lock lock = new ReentrantLock();
	//private Condition con = lock.newCondition();//可用於等待喚醒機制,配上flag標誌
	
	//下面是對共享數據進行要進行的兩個操作,定義在這個對象中,可以方便的實現互斥
	public void inc(){
		lock.lock();
		j++;
		System.out.println("j="+(this.j-1)+" increase by "+Thread.currentThread().getName()+": j="+this.j);
		lock.unlock();
	}
	public void dec(){
		lock.lock();
		j--;
		System.out.println("j="+(this.j+1)+" decrease by "+Thread.currentThread().getName()+": j="+this.j);
		lock.unlock();
	}
}
class Inc implements Runnable{//對共享數據進行加法的線程任務
	private ShareData data;
	public Inc(ShareData data){
		this.data = data;
	}
	@Override
	public void run() {
		while(true){
			data.inc();
		}
	}
}
class Dec implements Runnable{//對共享數據進行減法的線程任務
	private ShareData data;
	public Dec(ShareData data){
		this.data = data;
	}
	@Override
	public void run() {
		while(true){
			data.dec();
		}
	}
}

(2)將共享數據封裝在另外一個對象中,每個線程對共享數據的操作方法也分配到那個對象身上去完成,對象作爲這個外部類中的成員變量或方法中的局部變量,每個線程的Runnable對象作爲外部類中的成員內部類或局部內部類。

public class MultiThreadShareData{
	private static ShareData data = new ShareData();
	public static void main(String[] args) {
		//final ShareData data = new ShareData();//方法的局部final變量也可以
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(true){
					data.inc();
				}
			}
		}).start();
		new Thread(new Runnable(){
			@Override
			public void run() {
				while(true){
					data.dec();
				}
			}
		}).start();
	}
}
class ShareData{
	private int j = 0;
	public synchronized void inc(){
		j++;
		System.out.println("j="+(this.j-1)+" increase by "+Thread.currentThread().getName()+": j="+this.j);
	}
	public synchronized void dec(){
		j--;
		System.out.println("j="+(this.j+1)+" decrease by "+Thread.currentThread().getName()+": j="+this.j);
	}
}

(3)將這些Runnable對象作爲某一個類中的內部類,共享數據作爲這個外部類中的成員變量(內部類可以直接操作外部類成員變量),每個線程對共享數據的操作方法也分配給外部類,以便實現對共享數據進行的各個操作的互斥和通信,作爲內部類的各個Runnable對象調用外部類的這些方法。

public class MultiThreadShareData{
	private int j = 0;
	public static void main(String[] args) {
		MultiThreadShareData data = new MultiThreadShareData();
		Inc inc = data.new Inc();
		Dec dec = data.new Dec();
		for(int i = 0; i < 2; i++){
			new Thread(inc).start();
			new Thread(dec).start();
		}
	}
	private synchronized void inc(){
		j++;
		System.out.println(Thread.currentThread().getName()+" inc: "+j);
	}
	private synchronized void dec(){
		j--;
		System.out.println(Thread.currentThread().getName()+" dec: "+j);
	}
	class Inc implements Runnable{
		@Override
		public void run() {
			for(int i = 0; i < 100; i++){
				inc();
			}
		}
	}
	class Dec implements Runnable{
		@Override
		public void run() {
			for(int i = 0; i < 100; i++){
				dec();
			}
		}
	}
}

總結:要同步互斥的幾段代碼最好是分別放在幾個獨立的方法中,這些方法再放在同一個類中,這樣比較容易實現它們之間的同步互斥和通信。

原子操作

1. 何謂原子操作?

    Atomic一詞跟原子有點關係,後者曾被人認爲是最小物質的單位。計算機中的Atomic是指不能分割成若干部分的意思。如果一段代碼被認爲是Atomic,則表示這段代碼在執行過程中,是不能被中斷的。通常來說,原子指令由硬件提供,供軟件來實現原子方法(某個線程進入該方法後,就不會被中斷,直到其執行完成)

    在x86 平臺上,CPU提供了在指令執行期間對總線加鎖的手段。CPU芯片上有一條引線#HLOCK pin,如果彙編語言的程序中在一條指令前面加上前綴"LOCK",經過彙編以後的機器代碼就使CPU在執行這條指令的時候把#HLOCK pin的電位拉低,持續到這條指令結束時放開,從而把總線鎖住,這樣同一總線上別的CPU就暫時不能通過總線訪問內存了,保證了這條指令在多處理器環境中的原子性。

2. JDK1.5的原子包:java.util.concurrent.atomic

    這個包裏面提供了一組原子類。其基本的特性就是在多線程環境下,當有多個線程同時執行這些類的實例包含的方法時,具有排他性,即當某個線程進入方法,執行其中的指令時,不會被其他線程打斷,而別的線程就像自旋鎖一樣,一直等到該方法執行完成,才由JVM從等待隊列中選擇一個另一個線程進入,這只是一種邏輯上的理解。實際上是藉助硬件的相關指令來實現的,不會阻塞線程(或者說只是在硬件級別上阻塞了)。其中的類可以分成4組

  • AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • AtomicIntegerArray,AtomicLongArray
  • AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • AtomicMarkableReference,AtomicStampedReference,AtomicReferenceArray

Atomic類的作用

  • 使得讓對單一數據的操作,實現了原子化
  • 使用Atomic類構建複雜的,無需阻塞的代碼
    • 訪問對2個或2個以上的atomic變量(或者對單個atomic變量進行2次或2次以上的操作)通常認爲是需要同步的,以達到讓這些操作能被作爲一個原子單元。
3. AtomicBoolean , AtomicInteger, AtomicLong, AtomicReference

這四種基本類型用來處理布爾,整數,長整數,對象四種數據。

  • 構造函數(兩個構造函數)
    • 默認的構造函數:初始化的數據分別是false,0,0,null
    • 帶參構造函數:參數爲初始化的數據
  • set( )和get( )方法:可以原子地設定和獲取atomic的數據。類似於volatile,保證數據會在主存中設置或讀取
  • getAndSet( )方法
    • 原子的將變量設定爲新數據,同時返回先前的舊數據
    • 其本質是get( )操作,然後做set( )操作。儘管這2個操作都是atomic,但是他們合併在一起的時候,就不是atomic。在Java的源程序的級別上,如果不依賴synchronized的機制來完成這個工作,是不可能的。只有依靠native方法纔可以。
  • compareAndSet( ) 和weakCompareAndSet( )方法
    • 這兩個方法都是conditional modifier方法。這2個方法接受2個參數,一個是期望數據(expected),一個是新數據(new);如果atomic裏面的數據和期望數據一致,則將新數據設定給atomic的數據,返回true,表明成功;否則就不設定,並返回false。
  • 對於AtomicInteger、AtomicLong還提供了一些特別的方法。getAndIncrement( )、incrementAndGet( )、getAndDecrement( )、decrementAndGet ( )、addAndGet( )、getAndAdd( )以實現一些加法,減法原子操作。(注意 --i、++i不是原子操作,其中包含有3個操作步驟:第一步,讀取i;第二步,加1或減1;第三步:寫回內存)

4. 示例

    例如,類 AtomicLong 和 AtomicInteger 提供了原子增量方法。一個應用程序將按以下方式生成序列號:

class Sequencer {
<span style="white-space:pre">	</span>private AtomicLong sequenceNumber = new AtomicLong(0);
<span style="white-space:pre">	</span>public long next() { return sequenceNumber.getAndIncrement(); }
}
    AtomicBoolean使用示例:

private AtomicBoolean running = new AtomicBoolean(false);
	@Override
	protected Object execute() throws Exception {
		if (running.compareAndSet(false, true)) {
			try {
			//TODO
			} finally {
				running.set(false);
			}
		}
	}

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章