1. 程式人生 > >Java網路程式設計—(4)執行緒下

Java網路程式設計—(4)執行緒下

本來打算簡單的說一說java執行緒就算的,無奈執行緒實在是博大精深,一篇文章連基礎都說不完,所以又寫了個下篇來說一說執行緒的問題。上一節說了說執行緒的基本概念以及其基本的使用方法,也說明了執行緒存在的問題,這篇文章主要就執行緒會出現的問題來做出解決辦法。

同步

上一節已經說過執行緒的非同步有時候會出現問題,所以為了解決有時候會出現的問題就要用同步來解決。事實上只要有多個執行緒共享資源,都必須要考慮同步。這些執行緒可能是Thread的繼承類也可能實現了Runnable介面,也可能是完全不同的類的例項。關鍵在於這些執行緒所共享的資源,而不是這些執行緒是哪個類。

synchronized關鍵字

/**
 * 
@author Aaron * @建立日期:2017年8月31日下午11:01:33 * @修改日期 */
public classSynchronizedDemoimplementsRunnable{ @Override publicvoidrun(){ FileInputStream fis; try { fis = new FileInputStream("D:/test.txt"); MessageDigest sha = MessageDigest.getInstance("SHA-256"); DigestInputStream din = new DigestInputStream(fis, sha); while
(din.read() != -1) {//讀取整個檔案 byte[] digest = sha.digest(); System.out.println(fis+":"); System.out.println(DatatypeConverter.printHexBinary(digest)); System.out.println(); din.close(); } } catch (FileNotFoundException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch
(NoSuchAlgorithmException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } publicstaticvoidmain(String[] args){ for (int i = 0; i < 4; i++) { SynchronizedDemo synchronizedDemo = new SynchronizedDemo(); Thread thread = new Thread(synchronizedDemo); thread.start(); } } }

可以看出來程式並沒有按照順序輸出,這就是執行緒非同步造成的結果,為了讓程式順序輸出得到我們想要的結果我們可以使用synchronize來操作。使用synchronize把輸出的程式碼包圍起來就會對system.out同步了。

synchronized (System.out) {
					System.out.println(fis+":");
					System.out.println(DatatypeConverter.printHexBinary(digest));
					System.out.println();
					din.close();
				}

這個時候我們可以看到就是順序輸出的結果了。一旦執行緒開始列印這些值,所有其他執行緒在列印它們的值之前都必須停止來等待這個執行緒結束。同步要求同一物件的所有程式碼必須連續的執行,而不能並行執行(如果其他的類中的其他程式碼塊也對此物件執行了同步,那麼這個物件也是不能並行執行的)。Java沒有提供任何房啊來阻止其他執行緒使用共享資源,它只能防止對同一物件的其他執行緒使用這個資源。只有多個執行緒共享資源的時候才會考慮同步。

我們不僅可以對物件使用同步,還可以對方法使用同步。但是使用同步並不是一一勞永逸的方法,因為這會是VM的效能下降,並且增加了死鎖的可能性。

死鎖

假如A先生和B先生需要分別借兩本一樣的書來作為參考資料來寫論文,但是A先生先寄走了A書,B先生又借走了B書,這樣兩個人就都寫不成論文了。所以就造成了死鎖。

/**
 * @author Aaron
 * @建立日期:2017年8月31日下午11:01:33
 * @修改日期
 */
public abstract classSynchronizedDemoimplementsRunnable{

	publicstaticvoidmain(String[] args)throws InterruptedException {
		Object obj1 = new Object();
		Object obj2 = new Object();
		Object obj3 = new Object();

		Thread t1 = new Thread(new SyncThread(obj1, obj2), "t1");
		Thread t2 = new Thread(new SyncThread(obj2, obj3), "t2");
		Thread t3 = new Thread(new SyncThread(obj3, obj1), "t3");

		t1.start();
		Thread.sleep(5000);
		t2.start();
		Thread.sleep(5000);
		t3.start();

	}

}

classSyncThreadimplementsRunnable{
	private Object obj1;
	private Object obj2;

	publicSyncThread(Object o1, Object o2){
		this.obj1 = o1;
		this.obj2 = o2;
	}

	@Override
	publicvoidrun(){
		String name = Thread.currentThread().getName();
		System.out.println(name + " acquiring lock on " + obj1);
		synchronized (obj1) {
			System.out.println(name + " acquired lock on " + obj1);
			work();
			System.out.println(name + " acquiring lock on " + obj2);
			synchronized (obj2) {
				System.out.println(name + " acquired lock on " + obj2);
				work();
			}
			System.out.println(name + " released lock on " + obj2);
		}
		System.out.println(name + " released lock on " + obj1);
		System.out.println(name + " finished execution.");
	}

	privatevoidwork(){
		try {
			Thread.sleep(30000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

}

以上就是一個非常經典的死鎖的例子:

從結果裡就可以看出來程式一直在執行無法停止。

多個執行緒切換執行

public static void main(String[] args) {
Print print = new Print();
Number number = new Number(print);
Letter letter = new Letter(print);
Thread thread = new Thread(number);
Thread thread2 = new Thread(letter);
thread.start();
thread2.start();
}
private Print print;

/**
 * 帶引數的構造方法傳入 print物件 確保兩個執行緒類操作的是同一個物件
 * 
 * @param print
 */
public Number(Print print) {
    super();
    this.print = print;
}

@Override
public void run() {
    if (print.getTemp()) {//如果符合條件就往下執行
        synchronized (print) {//該物件獲得鎖
            for (int i = 1; i <= 52; i++) {
                System.out.println(i);
                if (i % 2 == 0) {
                    try {
                        print.setTemp(false);//首先把標誌更改
                        print.notifyAll();//喚醒其他所有正在等待的執行緒
                        print.wait();//並把此執行緒處於等待狀態
                        print.notify();//喚醒此執行緒,防止程式執行到最後仍處於未停止狀態
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                }

            }
        }
    }
}
private Print print;

public Letter(Print print) {
    super();
    this.print = print;
}

@Override
public void run() {
    if (!print.getTemp()) {
        synchronized (print) {
            for (int i = 65; i <= 90; i++) {
                System.out.println((char) i);
                try {
                    print.setTemp(true);
                    print.notifyAll();
                    print.wait();
                    print.notify();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

            }
        }
    }
private boolean temp = true;
public boolean getTemp() {
    return temp;
}

public void setTemp(boolean temp) {
    this.temp = temp;
}

避免死鎖

有很多方針可供我們使用來避免死鎖的局面。

  • 避免巢狀封鎖:這是死鎖最主要的原因的,如果你已經有一個資源了就要避免封鎖另一個資源。如果你執行時只有一個物件封鎖,那是幾乎不可能出現一個死鎖局面的。例如,這裡是另一個執行中沒有巢狀封鎖的run()方法,而且程式執行沒有死鎖局面,執行得很成功。
public void run() {
    String name = Thread.currentThread().getName();
    System.out.println(name + " acquiring lock on " + obj1);
    synchronized (obj1) {
        System.out.println(name + " acquired lock on " + obj1);
        work();
    }
    System.out.println(name + " released lock on " + obj1);
    System.out.println(name + " acquiring lock on " + obj2);
    synchronized (obj2) {
        System.out.println(name + " acquired lock on " + obj2);
        work();
    }
    System.out.println(name + " released lock on " + obj2);
 
    System.out.println(name + " finished execution.");
}
  • 只對有請求的進行封鎖:你應當只想你要執行的資源獲取封鎖,比如在上述程式中我在封鎖的完全的物件資源。但是如果我們只對它所屬領域中的一個感興趣,那我們應當封鎖住那個特殊的領域而並非完全的物件。
  • 避免無限期的等待:如果兩個執行緒正在等待物件結束,無限期的使用執行緒加入,如果你的執行緒必須要等待另一個執行緒的結束,若是等待程序的結束加入最好準備最長時間。

執行緒排程

當多個執行緒執行的時候必須要考慮執行緒的執行順序的問題,因為既要避免死鎖又要避免飢餓等待。

優先順序

在Java中不是所有的執行緒都是均等的,每個執行緒都有一個優先順序,從0——10不等,0是最低階,10是最高階。如果不對執行緒指定優先順序就會預設5。要注意的事不是所有的作業系統都支持者11個優先順序,Windows只有7個優先順序,並且1和2,3和4,6和7,8和9會做同樣的處理,也就是說優先順序9的執行緒並不會搶佔優先順序8的執行緒。執行緒的優先順序可以通過setPriority來改變

publicstaticvoidmain(String[] args)throws InterruptedException {
		ListCallbackDigest lad = new ListCallbackDigest(fileName);
		lad.add(this);
		Thread thread = new Thread(lad);
		thread.setPriority(9);
		thread.start();
	}

不過,一般情況加不要使用太高優先順序,因為可能會造成其他執行緒飢餓狀態。

搶佔

執行緒排程器主要有兩種:搶佔式和協作式。搶佔式執行緒排程器確定一個執行緒正常的輪到其CPU時間的時候回暫停這個執行緒,協作式將CPU控制權交給其他執行緒的時候會等待正在執行的執行緒自己暫停。

放棄

yield()方法將通知虛擬機器如果有一個執行緒在等待執行那麼就可以執行,當前執行緒放棄執行。

休眠

sleep()

interrrupt()

連線執行緒

join()

等待、通知

wait()

notify()

notifyAll()

結束

執行緒以合理的方法結束,執行緒將撤銷,其他執行緒接管CPU。

執行緒池

這裡我們只簡單的說一下執行緒池的問題。之前就已經瞭解到向一個程式中新增多個執行緒會極大的提升效能,尤其是I/O受限的程式。不過,執行緒也是需要開銷的,如果一個程式中有數百個執行緒,java虛擬機器會做大量的工作,就算是執行緒很快就會結束,但是對虛擬機器而言也會加重垃圾回收器等的負擔,為了減輕CPU的開銷減輕虛擬機器的壓力我們可以使用java.util.concurrent中的Excutors類,輕鬆的建立執行緒池。

**
 * @author Aaron
 * @建立日期:201795日下午5:09:37
 * @修改日期
 */
public classExcutorsDemoimplementsRunnable{
	private File inputs;

	publicExcutorsDemo(File inputs){
		this.inputs = inputs;
	}

	@Override
	publicvoidrun(){
		// 不壓縮已經壓縮的檔案
		if (!inputs.getName().endsWith(".gz")) {
			File outputs = new File(inputs.getParent(), inputs.getName()
					+ ".gz");
			if (!outputs.exists()) {
				// 不覆蓋已經存在的檔案
				try (InputStream is = new BufferedInputStream(
						new FileInputStream(inputs));
						OutputStream os = new BufferedOutputStream(
								new GZIPOutputStream(new FileOutputStream(
										outputs)));) {
					int b;
					while ((b = is.read()) != -1) {
						os.write(b);
						os.flush();
					}
				} catch (IOException e) {
					e.printStackTrace();
				}

			}
		}

	}

	public File getInputs(){
		return inputs;
	}

	publicvoidsetInputs(File inputs){
		this.inputs = inputs;
	}

}
/**
 * @author Aaron
 * @建立日期:2017年9月5日下午5:21:39
 * @修改日期
 */
public class ExcutorsDemoMain {
	public final static int THREAD_COUNT = 4;

	public static void main(String[] args) {
		ExecutorService pool = Executors.newFixedThreadPool(THREAD_COUNT);
		for (String filename : args) {
			File file = new File(filename);
			if (file.exists()) {
				if (file.isDirectory()) {
					File[] files = file.listFiles();
					for (int i = 0; i < files.length; i++) {
						if (!files[i].isDirectory()) {// 不遞迴處理目錄
							Runnable task = new ExcutorsDemo(files[i]);
							pool.submit(task);
						}
					}
				} else {
					Runnable task = new ExcutorsDemo(file);
					pool.submit(task);
				}
			}
		}
		pool.shutdown();
	}
}

我們使用GZIPOutputStream過濾流來對當前目錄的每一個檔案進行壓縮,一方面這是一個大量的I/O操作,另一方面資料壓縮是一個CPU密集度很高的操作,所以我們不能用太多執行緒,這是使用執行緒池的大好機會,客戶端執行緒將壓縮檔案,主程式告訴執行緒壓縮什麼檔案,在這裡主程式會遠遠快於執行緒,所以我們需要填充執行緒池,然後啟動執行緒池的中執行緒。主程式構造 了一個執行緒池並填充了4個執行緒,以迭代的方式列出所有的檔案和目錄,這些檔案以及這些目錄中的檔案會用來構建ExcutorsDemo,然後交到執行緒池,最後由著4個執行緒之一處理。一旦將所有的檔案增加到這個執行緒池,就可以呼叫pool.shutDown,這個方法不會終止等待的工作,他只是通知執行緒池在沒有更多的任務增加到內部佇列,而且一旦完成所有的等待的工作就應當關閉。

這裡我給出一個操作I/O經典不算複雜的例子:

public class Test {
    int a = 0;
    static File startPath = null;
    static File endPath = null;
    public static void main(String[] args) throws IOException {
    Scanner input = new Scanner(System.in);
    System.out.println("請輸入您要複製的原目錄:(完整目錄)");
    startPath = new File(input.next());
    System.out.println("請輸入您要複製的目標目錄:(完整目錄)");
    endPath = new File(input.next());
    Test2016510 test2016510 = new Test2016510();
    test2016510.copy(startPath, endPath);
    System.out.println("複製完成");
}
@SuppressWarnings("unused")
public void copy(File startFile, File endFile) throws IOException {

    File startFilePath = startFile;
    File endFilePath = endFile;

    File[] startfilePaths = startFilePath.listFiles();
    if (startFile.exists()) {
        for (File file : startfilePaths) {

            if (file.isDirectory()) {
                if (file != null) {
                    String ss = file.toString().substring(3,
                            file.toString().length());
                    File newFile = new File(endFilePath.toString() + "\\"
                            + ss);
                    newFile.mkdirs();
                    this.copy(file, newFile);
                } else {
                    String ss = file.toString().substring(3,
                            file.toString().length());
                    File newFile = new File(endFilePath.toString() + "\\"
                            + ss);
                    newFile.mkdirs();
                }
            } else if (file.isFile()) {
                String ss = file.getPath().substring(3,
                        file.toString().length());
                File newFile = new File(this.endPath + "\\" + ss);
                String ssss = file.getParent().substring(3);
                File parentFile = new File(this.endPath + "\\" + ssss);
                if (!parentFile.exists()) {
                    parentFile.mkdirs();
                    FileInputStream fis = new FileInputStream(file);
                    BufferedInputStream bis = new BufferedInputStream(fis);

                    FileOutputStream fos = new FileOutputStream(newFile);
                    BufferedOutputStream bos = new BufferedOutputStream(fos);

                    byte[] buff = new byte[1024];
                    int temp = 0;
                    while ((temp = bis.read(buff)) != -1) {
                        bos.write(buff, 0, temp);
                        bos.flush();
                    }
                    bos.close();
                    fos.close();
                    bis.close();
                    fis.close();
                } else {
                    FileInputStream fis = new FileInputStream(file);
                    BufferedInputStream bis = new BufferedInputStream(fis);

                    FileOutputStream fos = new FileOutputStream(newFile);
                    BufferedOutputStream bos = new BufferedOutputStream(fos);

                    byte[] buff = new byte[1024];
                    int temp = 0;
                    while ((temp = bis.read(buff)) != -1) {
                        bos.write(buff, 0, temp);
                        bos.flush();
                    }
                    bos.close();
                    fos.close();
                    bis.close();
                    fis.close();
                }

            }
        }
    } else {
        System.out.println("您指定的檔案不存在,程式結束。");
    }
}

這個列子使用位元組流完整的複製資料夾以及檔案裡面的檔案,對於理解I/O會有幫助。當然更復雜的運算,我們還可以使用多執行緒甚至執行緒池來複制完整的檔案以及資料夾,就有點類似於某雷了。


相關推薦

Java網路程式設計—(4)執行

本來打算簡單的說一說java執行緒就算的,無奈執行緒實在是博大精深,一篇文章連基礎都說不完,所以又寫了個下篇來說一說執行緒的問題。上一節說了說執行緒的基本概念以及其基本的使用方法,也說明了執行緒存在的問題,這篇文章主要就執行緒會出現的問題來做出解決辦法。 同步 上一節已經

Java網路程式設計--多執行的Socket

首先,學好計算機網路知識真的很重要。雖然,學不好不會影響理解下面這個關於巨集觀講解,但是,學好了可以自己打漁吃,學不好就只能知道眼前有魚吃卻打不到漁。 在Java中網路程式有2種協議:TCP和UDP。 TCP 是可靠的連線。這個可靠的意思就是得有

java網路程式設計——多執行資料收發並行

## 基本介紹與思路 #### 收發並行 前一篇部落格中,完成了客戶端與服務端的簡單TCP互動,但這種互動是觸發式的:客戶端傳送一條訊息,服務端收到後再回送一條。沒有做到收發並行。收發並行的字面意思很容易理解,即資料的傳送與接收互相不干擾,相互獨立。當然,要保證服務端和客戶端都能做到收發並行。 #### 業務

Java網路程式設計4.UDP網路程式設計之多執行優化

UDP網路程式設計之多執行緒優化——DatagramSocket類 1、UDP網路程式設計之多執行緒優化的思想 (1)一個執行緒實現客戶端——傳送資料 (2)一個執行緒實現伺服器端——接收資料

Java併發程式設計執行生命週期、守護執行、優先順序和join、sleep、yield

Java併發程式設計中,其中一個難點是對執行緒生命週期的理解,和多種執行緒控制方法、執行緒溝通方法的靈活運用。這些方法和概念之間彼此聯絡緊密,共同構成了Java併發程式設計基石之一。 Java執行緒的生命週期 Java執行緒類定義了New、Runnable、Running Man、Blocked和Dead

Java併發程式設計(1)-執行安全基礎概述

文章目錄 一、執行緒安全性 1.1、無狀態類 1.2、有狀態類 二、原子性 2.1、原子操作 2.2、競爭操作 2.3、複合操作

Java併發程式設計執行安全、執行通訊

Java多執行緒開發中最重要的一點就是執行緒安全的實現了。所謂Java執行緒安全,可以簡單理解為當多個執行緒訪問同一個共享資源時產生的資料不一致問題。為此,Java提供了一系列方法來解決執行緒安全問題。 synchronized synchronized用於同步多執行緒對共享資源的訪問,在實現中分為同步程

java併發程式設計一一執行池原理分析(三)

合理的設定執行緒池的大小 接著上一篇探討執行緒留下的尾巴。如果合理的設定執行緒池的大小。 要想合理的配置執行緒池的大小、首先得分析任務的特性,可以從以下幾個角度分析: 1、任務的性質:CPU密集型任務、IO密集型任務、混合型任務等; 2、任務的優先順序:高、中、低; 3、任務的執行時

java併發程式設計一一執行池原理分析(二)

2、執行緒池 1、什麼是執行緒池 Java中的執行緒池是運用場景最多的併發框架,幾乎所有需要非同步或併發執行任務的程式都可以使用執行緒池。 在開發工程中,合理的使用執行緒池能夠帶來3個好處。 第一:降低資源的消耗,通過重複利用已建立的執行緒降低執行緒建立和銷燬造成的消耗 第二:提

java併發程式設計一一執行池原理分析(一)

1、併發包 1、CountDownLatch(計數器) CountDownLatch 類位於 java.util.concurrent 包下,利用它可以實現類似於計數器的功能。 比如有一個任務A,它要等待其他4個任務執行完成之後才能執行,此時就可以利用CountDownLatch

Java併發程式設計執行池(三)

一.介紹 Java通過Executors提供四種執行緒池,分別為: (1)newCachedThreadPool:建立一個可快取執行緒池,如果執行緒池長度超過處理需要,可靈活回收空閒執行緒,若無可回收,則新建執行緒。 (2)newFixedThreadPool: 建立一個定長執行緒池,可控制

java併發程式設計執行的基本概念

本文為學習筆記。源自學習微信公眾號“我們都是小青蛙”。 本篇文章將記錄如何使用java中的執行緒。 main執行緒 main方法是程式入口,我們對已經編譯好的class檔案呼叫java命令時就可以執行一個java程式。這個過程中,其實系統自動為我們建立了一個程序

Java併發(二十一):執行池實現原理 Java併發(十八):阻塞佇列BlockingQueue Java併發(十八):阻塞佇列BlockingQueue Java併發程式設計執行池的使用

一、總覽 執行緒池類ThreadPoolExecutor的相關類需要先了解:  (圖片來自:https://javadoop.com/post/java-thread-pool#%E6%80%BB%E8%A7%88) Executor:位於最頂層,只有一個 execute(Runnab

Java併發程式設計執行池的使用

如果併發的執行緒數量很多,並且每個執行緒都是執行一個時間很短的任務就結束了,這樣頻繁建立執行緒就會大大降低系統的效率,因為頻繁建立執行緒和銷燬執行緒需要時間。   那麼有沒有一種辦法使得執行緒可以複用,就是執行完一個任務,並不被銷燬,而是可以繼續執行其他的任務?   在J

Java併發程式設計執行池的使用(轉載)

轉載自:https://www.cnblogs.com/dolphin0520/p/3932921.html Java併發程式設計:執行緒池的使用   在前面的文章中,我們使用執行緒的時候就去建立一個執行緒,這樣實現起來非常簡便,但是就會有一個問題:   如果併發的執行緒數量很多,並且每個執行緒都是執行

C網路程式設計--多執行伺服器

 伺服器主要用的是socket(雙向的通訊的一端),bind(繫結),listen(切換監聽狀態),accept(與客戶端取得連線) 將accept放入多執行緒,可以多個客戶端連線 #include <stdio.h> //標準輸入輸出 #incl

python網路程式設計執行

  一 .背景知識 1.程序    之前我們已經瞭解了作業系統中程序的概念,程式並不能單獨執行,只有將程式裝載到記憶體中,系統為它分配資源才能執行,而這種執行的程式就稱之為程序。程式和程序的區別就在於:程式是指令的集合,它是程序執行的靜態描述文字;程序是程式的一次執行活動,屬於動態概念。在多道

JAVA併發程式設計執行池 ThreadPoolExecutor

生活 前期追深度,否則會華而不實,後期追廣度,否則會坐井觀天; 前言 在前面,我們已經對Thread有了比較深入的瞭解,並且已經學會了通過new Thread()來建立一個執行緒,並通過start方法來啟動一個執行緒,這種方法非常簡單,同樣也存在弊端: 1、每次通過new Thr

17-Java併發程式設計執行間協作的兩種方式:wait、notify、notifyAll和Condition

Java併發程式設計:執行緒間協作的兩種方式:wait、notify、notifyAll和Condition   在前面我們將了很多關於同步的問題,然而在現實中,需要執行緒之間的協作。比如說最經典的生產者-消費者模型:當佇列滿時,生產者需要等待佇列有空間才能繼續往裡面放

Java併發程式設計執行安全和ThreadLocal

執行緒安全的概念:當多個執行緒訪問某一個類(物件或方法)時,這個類始終都能表現出正確的行為,那麼這個類(物件或方法)就是執行緒安全的。 執行緒安全 說的可能比較抽象,下面就以一個簡單的例子來看看什麼是執行緒安全問題。 public class MyThread