java——多線程基礎

1、線程的理解:

(1)線程的使用:

線程的學習難點並不在於如何創建、開啓一個線程,而是應該何時創建,線程如何同步,使得程序能夠運行得到預期的結果。
比如文字處理程序 你可以設置每隔多長時間 輸入的內容可以自動的保存 而這個保存操作並不需要你的介入 而是使用一個線程專門負責這個操作,在這個程序中至少得有兩個線程 一個便是主線程 用來捕獲你從鍵盤輸入的內容 而另一個線程則負責定時保存輸入的內容。
在後面的socket的學習中也用到了多線程:

(2)線程的執行:

只有在多處理器的機器上, 多線程纔會被真正的同時運行,在單處理機上多線程是佔用分配的時間片來運行,因爲處理機的速度比較快 使得我們看起來它像是在同時運行,也就是我們操作系統中所說的併發和並行的概念。

2、創建線程:

方法一:定義線程實現Runnable接口

public class MyThread implements Runnable {

	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<10;i++) {
			System.out.println("myThread"+i);
		}
	}
}

方法二:定義一個Thread的子類並重寫run方法
缺點:Java中OOP單繼承的侷限性

public class MyThread1 extends Thread {
	public void run() {
		for(int i=0;i<10;i++) {
			System.out.println("MyThread"+i);
		}
	}
}

這裏需要注意的是 Thread類這裏用到了代理設計模式。線程體(也就是我們要執行的具體任務)實現了Runable接口和run方法。同時Thread類也實現了Runnable接口。此時線程體就相當於目標角色,Thread就相當於代理角色。當程序調用了Thread的start方法後,thread的run()方法會在某個特定的時候被調用。
方法三:實現Callable接口

class Mythread2 implements Callable<Integer>{

	@Override
	public Integer call() throws Exception {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+"啓動了");
		return 1024;
	}	
}

方法四:使用線程池

public class TestThreadPool {
    public static void main(String[] args) {
       ExecutorService threadPool=new  ThreadPoolExecutor(2,5,1L, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
       try{
           threadPool.execute(new MyThread());
       }catch (Exception e){
           e.printStackTrace();
       }finally {
           threadPool.shutdown();
       }
    }
}

測試:

public class MainTread {
	public static void main(String[] args) {
			Runnable r1=new MyThread();
			//r1.run();這樣只是執行了線程體,而不是開闢出類一個線程與主線程同時運行
			
			//這樣就開闢出來一個新的線程 Thread(Runnable target)
			Thread t=new Thread(r1);//要啓動一個新的線程就必須new一個Thread對象出來
			t.start();//線程啓動調用start方法 線程執行默認運行的run()方法  
			
			/*
				方法二:
				MyThread1 mt=new MyThread1();
				mt.start();
			*/


		//方法三
		FutureTask<Integer> futureTask=new FutureTask<>(new Mythread2());
		Thread t1=new Thread(futureTask);
		t1.start();
		int result01=100;
		int result02=futureTask.get();//建議放到最後,要求獲得Callable線程的計算結果,如果沒有計算完成,就去強求,會導致阻塞直到計算完成
		System.out.println("****result*****"+(result01+result02));
		
	}
}

在java編程思想中提到Callabel是Java SE5引入的一種具有類型參數的泛型,它的類型表示的是方法call()(而不是run())中返回的值,並且必須使用ExecutorService.submit()方法調用它。

class TaskWithResult implements Callable<String>{
    private  int id;
    public TaskWithResult(int id){
        this.id=id;
    }
    @Override
    public String call() throws Exception {
        return "result of TaskWithResult "+id;
    }
}

public class TestThreadPool {
    public static void main(String[] args) {
       ExecutorService exec=new  ThreadPoolExecutor(2,5,1L, TimeUnit.SECONDS,new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());
        ArrayList<Future<String>> results=new ArrayList<>();
        for(int i=0;i<10;i++){
            results.add(exec.submit(new TaskWithResult(i)));
        }
        for(Future<String> fs:results)
            try{
                System.out.println(fs.get());
            }catch(InterruptedException e){
                System.out.println(e);
                return;
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                exec.shutdown();
            }
    }
}

上面的代碼源自Java編程思想,當我敲完這段代碼的時候,竟然在質疑它執行exec.shutdown的位置是否不對,從上面可以看出每次for循環都執行了一次exec.shutdown(),並且並不會報錯,說明執行shutdown方法並不是將線程池給關閉了。關於shutdown()方法這裏給出幾個簡短的總結:
(1)執行shutdown方法後,線程池不在接收新的任務,繼續提交任務會根據線程池的拒絕策略給你出響應(如拋出異常);
(2)等待隊列中的任務仍然會繼續執行;
(3)正在執行中的任務並不會立即被中斷執行,這個需要在run方法中單獨處理interrupted狀態,interrupt更類似一個標誌位,不會直接打斷線程的執行。
如何優雅的關閉線程池,可以參考以下文章,後期自己也會對這些做出總結。
線程池的優雅關閉實踐
關閉線程池的正確姿勢,shutdown(), shutdownNow()和awaitTermination() 該怎麼用?

3、線程的管理

(1)join()

等待該線程終止,在當前線程中調用另一個線程的jion方法,則當前線程轉入阻塞狀態,直到另一個線程運行結束後,當前線程纔會由阻塞狀態轉爲就緒狀態
我們通過實現下面這道題來理解join()方法

編寫一個程序,開啓 3 個線程,這三個線程的分別爲 A、B、C,每個線程打印對應的“A”、“B”、“C” 10 遍,要求輸出的結果必須按順序顯示。如:ABCABCABC……

public class Test {
    public static void main(String[] args) {
        try {
            for (int i=0;i<10;i++){
                Thread t1=new Thread(new Task("A"));
                Thread t2=new Thread(new Task("B"));
                Thread t3=new Thread(new Task("C"));
                t1.start();
                t1.join();
                t2.start();
                t2.join();
                t3.start();
                t3.join();
            }

        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println();
        System.out.println("Main Thread");

    /*
   output
   ABCABCABCABCABCABCABCABCABCABC
   Main Thread
    * */
    }

    static class Task implements Runnable{
        String name;

        public Task(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            System.out.print(this.name);
        }
    }
}

此處代碼運行的結果是 待tr tr1線程運行結束後 主線程纔會開始運行

(2)yield和sleep

yield和sleep(0)實現的效果一樣,因此我們可以發現它倆的區別是sleep可以規定該線程多長時間內不可以搶佔處理機,而yield()不可以,它意味着該線程放棄處理機後立馬可以參加搶佔處理機。
代碼實現:

public class MyThread implements Runnable {
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++) {
			/*
			 * 每循環一次 該線程讓出了處理機   即處理機重新選擇分配給一個線程 進行運行  仍有可能分配給原來的線程
			 * */
			Thread.yield();
			System.out.println("MyThread"+i);
		}
	}
}
public class MyThread implements Runnable {
	@Override
	public void run() {
		try {
			Thread.sleep(1000);//該線程休眠 5秒
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		for(int i=0;i<50;i++) {
			
			System.out.println("MyThread"+i);
		}
	}

}
(3)設置線程的優先級

需要注意的是Java中優先級高的線程只能有機會優先被執行 而不會絕對被優先執行

public class MainThread {
	/*
	 * 線程優先級設置測試
	 * */
	public static void main(String[] args) {
		MyThread1 mt1=new MyThread1();
		MyThread2 mt2=new MyThread2();
	
		Thread tr1=new Thread(mt1);
		Thread tr2=new Thread(mt2);
		int p1 = tr1.getPriority();
		int p2 = tr2.getPriority();
		
		System.out.println(p1+"   "+p2);//線程的默認優先級爲5
		
		//設置線程的優先級
		tr1.setPriority(10);
		tr2.setPriority(7);
		tr1.start();
		
		tr2.start();
	
		for(int i=0;i<50;i++) {
			System.out.println("MainThread	"+i);
		}	
	}
}

線程同步

(1)wait(),notify()與 synchronized

需要先明白以下幾個問題:
1、如何使用?
(1) obj.wait()(等待) 與 obj.notify()(喚醒) 必須要與synchronized(obj)方法一起使用
(2) wait()必須在synchronized代碼塊中,它使得當前線程等待,直到其它線程調用此對象的notify()或notifyAll()方法,即其它線程喚醒自己。
(3) notify()方法可不可以自己喚醒自己? 答案當然是NO,notify()喚醒的是在等待隊列中的線程,自己就沒有在等待隊列,需要注意的是notify()喚醒的是等待隊列中的隨機一個線程, notifyAll()可將等待隊列中的所有的線程全部喚醒
2、wait()與sleep的區別 。
sleep是線程的方法 wait是object的方法 sleep釋放的CPU的搶佔權 wait()將CPU的搶佔權跟鎖全部都釋放

3、爲什麼要使用 synchronized?
多線程操作同一個對象的時候要加鎖,不加鎖的話會導致程序的執行無可再現性,每次的運行結果都不相同。
下面我們實現一個簡單的生產者消費者來理解線程同步的這些方法。
簡單的生產者消費者模式(參考自參考資料1)

/*
使用wait()和notify()方法實現
 緩衝區滿或者爲空時都調用wait()方法等待。
 當生產者生產了一個產品或者消費者消費了一個產品之後會喚醒所有線程。
 */
public class Test1 {
    private static Integer count=0;
    private static final Integer FULL=10;
    private static String LOCK="lock";

    public static void main(String[] args) {
        Test1 test1=new Test1();
        for (int i=0;i<5;i++){
            new Thread(test1.new Producer()).start();
        }
        for (int i=0;i<5;i++){
            new Thread(test1.new Consumer()).start();
        }
    }

    class Producer implements Runnable{

        @Override
        public void run() {
            for(int i=0;i<10;i++){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK){
                    while(count==FULL){
                        try{
                            LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    count++;
                    System.out.println(Thread.currentThread().getName()+"生產者生產,目前總共有"+count);
                    LOCK.notifyAll();
                }
            }
        }
    }
    class Consumer implements Runnable{

        @Override
        public void run() {
            for (int i=0;i<10;i++){
                try {
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (LOCK){
                    while (count==0){
                        try {
                            LOCK.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(Thread.currentThread().getName()+"消費者消費,總共有"+count);
                    count--;
                    LOCK.notifyAll();
                }
            }
        }
    }
}

參考資料

Java實現生產者和消費者的5種方式

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