【Java高併發學習】線程相關(二)

線程相關

1.線程組

如果線程數量過多,而且功能分配明確,我們可以把功能一樣的線程放入一個線程組中統一進行操作。

/**
 * 線程組:將類似功能的線程放入同一組內,便於管理
 * @author wsz
 * @date 2017年11月27日
 */
class Print1 implements Runnable{

	@Override
	public void run() {
		String name = Thread.currentThread().getThreadGroup().getName()+"_"+Thread.currentThread().getName();
		while(true) {
			System.out.println(name);
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
class Print2 implements Runnable{

	@Override
	public void run() {
		String name = Thread.currentThread().getThreadGroup().getName()+"_"+Thread.currentThread().getName();
		while(true) {
			System.out.println(name);
			try {
				Thread.sleep(1000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class ThreadGroupTest implements Runnable{

	public static void main(String[] args) {
		ThreadGroup group = new ThreadGroup("ThreadGroup");//線程組及命名
//		Thread tg1 = new Thread(group,new ThreadGroupTest(),"tg1");
//		Thread tg2 = new Thread(group,new ThreadGroupTest(),"tg2");
		Thread tg1 = new Thread(group, new Print1(), "print1");
		Thread tg2 = new Thread(group, new Print2(), "print2");
		tg1.start();
		tg2.start();
		System.out.println(group.activeCount());//估值活動線程數目
		group.list();//線程組中線程信息
	}

	@Override
	public void run() {
		String name = Thread.currentThread().getThreadGroup().getName()+"_"+Thread.currentThread().getName();
		while(true) {
			System.out.println(name);
			try {
				Thread.sleep(5000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

2.守護線程

守護線程一般用於系統性的服務,比如垃圾回收線程、JIT線程。當系統中的用戶線程全部結束工作後,守護線程也將結束工作。
注意:守護線程的設置必須在開啓之前,否則將出現異常。

/**
 * 守護線程:一般在後臺完成系統性的服務,比如垃圾回收線程、JIT線程。
 * 工作線程:完成程序業務的線程。當期線程結束,那麼守護線程也將停止工作。因此當程序中只存在守護線程時,java虛擬機就會退出。
 * @author wsz
 * @date 2017年11月27日
 */
class Daemon1 implements Runnable{

	@Override
	public void run() {
		while(true) {
			System.out.println("alive");
			try {
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}
public class DaemonThread {

	public static void main(String[] args) throws InterruptedException {
		Thread t = new Thread(new Daemon1());
		//設置爲守護線程,且必須在線程start之前設置,否則出現java.lang.IllegalThreadStateException異常,
		//且線程將一直打印“alive”,因爲當前程序只有main主線程,當main休眠2秒後程序將退出。
		//如果t不是守護線程,在main結束後,t線程仍將一直打印不會結束。
		t.setDaemon(true);
		t.start();
//		t.setDaemon(true);  //出現異常,且不會終止,將一直打印。
		t.sleep(2000);
	}
}

3.線程優先級

優先級別高的線程有更高的機會獲取足夠的資源運行。使用1-10表示線程優先級,數字越大線程優先級越高。Thread類中定義了三個靜態標量。

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = 1;

   /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = 5;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = 10;

/**
 * 線程優先級:數字1-10,數字越大級別越高
 * @author wsz
 * @date 2017年11月28日
 */
public class PriorityThread {
	public final static Object object = new Object();
	
	class HighThread implements Runnable{
		int count = 0;
		@Override
		public void run() {
			while(true) {
				synchronized (object) {
					count++;
					if(count > 200000) {
						System.out.println("HighThread is ok");
						break;
					}
				}
			}
		}
		
	}
	class LowThread implements Runnable{
		int count = 0;
		@Override
		public void run() {
			while(true) {
				synchronized (object) {
					count++;
					if(count > 200000) {
						System.out.println("LowThread is ok");
						break;
					}
				}
			}
		}
		
	}
	
	public static void main(String[] args) {
		Thread high = new Thread(new PriorityThread().new HighThread());
		Thread low = new Thread(new PriorityThread().new LowThread());
		high.setPriority(Thread.MAX_PRIORITY);
		low.setPriority(Thread.MIN_PRIORITY);
		high.start();
		low.start();
	}

}

4.synchronized

程序並行是爲了獲取更高的執行效率,但必須得以準確性爲前提。
volatile關鍵字並不能真正保證線程安全。只能保證一個線程修改數據後,其他線程能夠看到這次改動;但當多個線程同時修改某一個數據時,依舊會發生衝突。
public class AddThread implements Runnable{
	static AddThread instance = new AddThread();
	static volatile int count = 0; 
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(count);//count值一般總是小於2個線程累加之和的。即使使用了volatile
	}

	@Override
	public void run() {
		for(int i = 0; i < 10000 ;i ++) {
			count++;
		}
	}
}

爲了實現線程之間的同步可以使用synchronized關鍵字對同步的代碼塊進行加鎖。當線程進入代碼塊之前需要先獲得對應的鎖資源,即可保證線程間的安全性,並確保線程的原子性、可見性(只能由一個線程運行同步塊內的程序,不會衝突)、有序性(同步塊內程序只能由一個線程運行)。
  1. 指定加鎖對象:對給定對象加鎖,進入同步塊之前必須先獲得該對象的鎖資源(注意鎖對象的可變性,比如Integer不可變,不能作爲加鎖對象)
  2. 直接作用於實例方法:相當於對當前實例加鎖,進入同步塊之前必須先獲得當前實例的鎖資源
  3. 直接作用於靜態方法:相當於對當前類加鎖,進入同步塊之前必須先獲得當前類的鎖資源
對剛纔的代碼指定加鎖對象,即可保證count的值爲20000。
public class AddThread implements Runnable{
	static AddThread instance = new AddThread();//同一個對象
	static volatile int count = 0; 
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(count);//count值計算正確爲20000
	}

	@Override
	public void run() {
		synchronized (instance) {
			for(int i = 0; i < 10000 ;i ++) {
				count++;
			}
		}
	}
}
作用於實例對象:
public class AddThread implements Runnable{
	static AddThread instance = new AddThread();
	static volatile int count = 0; 
	public static void main(String[] args) throws InterruptedException {
		//此時指向同一個實例對象,保證線程關注到同一個對象鎖資源上。
		Thread t1 = new Thread(instance);
		Thread t2 = new Thread(instance);
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(count);//count值計算正確爲20000

	}

	@Override
	public void run() {
//		synchronized (instance) {
//			for(int i = 0; i < 10000 ;i ++) {
//				count++;
//			}
//		}
		for(int i = 0 ;i < 10000 ; i++) {
			increase();
		}
	}
	
	public synchronized void increase() {//作用於實例方法,
		count++;
	}
}
如果想新建不同的實例對象並保證線程安全,可以把同步方法設置爲static修飾,這樣就是對當前類進行加鎖,而不是當前實例。
public class AddThread implements Runnable{
	static AddThread instance = new AddThread();
	static volatile int count = 0; 
	public static void main(String[] args) throws InterruptedException {
		//此時指向同一個實例對象,保證線程關注到同一個對象鎖資源上。
//		Thread t1 = new Thread(instance);
//		Thread t2 = new Thread(instance);
		//此時作用於靜態方法,可以實例化不同對象
		Thread t1 = new Thread(new AddThread());
		Thread t2 = new Thread(new AddThread());
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(count);//count值計算正確爲20000

	}

	@Override
	public void run() {
//		synchronized (instance) {
//			for(int i = 0; i < 10000 ;i ++) {
//				count++;
//			}
//		}
		for(int i = 0 ;i < 10000 ; i++) {
			increase();
		}
	}
	
	public static synchronized void increase() {//作用於實例方法,
		count++;
	}
}

5.集合與線程安全問題

5.1ArrayList

public class ArrayListTest {

	static ArrayList<Integer> arry = new ArrayList<Integer>();
	
	class AddList implements Runnable{

		@Override
		public void run() {
			for(int i= 0; i< 10000 ; i++)
				arry.add(i);
		}
	}
	public static void main(String[] args) throws InterruptedException {
		Thread a1 = new Thread(new ArrayListTest().new AddList());
		Thread a2 = new Thread(new ArrayListTest().new AddList());
		a1.start();
		a2.start();
		a1.join();
		a2.join();
		System.out.println(arry.size());//size值將出現小於20000的情況
	}

}
運行上面程序ArrayList將添加數字,可能出現情況:
  1. 正常結果20000
  2. 拋出下面的異常信息:在ArrayList擴容時,內部一致性被破壞,但是沒有鎖保護,另一個線程訪問到了不一致的內部狀態,出現越界問題。
  3. 打印結果小於正常值20000:此時保存容器大小的變量被多線程不正常訪問,即讀寫不一致,導致賦值出現爲題。
  4. 解決方法:可以使用線程安全的Vector代替。
Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 10
	at java.util.ArrayList.add(ArrayList.java:459)
	at arrayList.ArrayListTest$AddList.run(ArrayListTest.java:14)
	at java.lang.Thread.run(Thread.java:748)
10002

5.2HashMap


HashMap也不是線程安全的。在併發下容易使得內部結構混亂,比如鏈變成環。Java8中已規避了成環問題,但仍不建議在多線程環境中使用,可使用線程安全的ConcurrentHashMap。
public class HashMapTest {
	static HashMap<String,Object> map = new HashMap<String,Object>(); 
	
	class AddHashMap implements Runnable{
		@Override
		public void run() {
			for(int i= 0; i< 100 ; i++)
				map.put(String.valueOf(i), i);
		}
	}
	
	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new HashMapTest().new AddHashMap());
		Thread t2 = new Thread(new HashMapTest().new AddHashMap());
		t1.start();
		t2.start();
		t1.join();
		t2.join();
		System.out.println(map.size());//size值將出現小於200的情況
	}

}

最後github代碼地址:https://github.com/BeHappyWsz/Thread.git


發佈了54 篇原創文章 · 獲贊 3 · 訪問量 2萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章