java多線程系列(一)---多線程技能


java多線程技能

前言:本系列將從零開始講解java多線程相關的技術,內容參考於《java多線程核心技術》與《java並發編程實戰》等相關資料,希望站在巨人的肩膀上,再通過我的理解能讓知識更加簡單易懂。

目錄

並發歷史

  • 在沒有操作系統的時候,一台計算機只執行一個程序,在那個時候,對珍貴的計算機資源來說是一種浪費
  • 為了提高資源利用率(比如在等待輸入的時候,可以執行其他程序),為了提高公平性(不同用戶和程序對計算機上的資源有平等的使用權),為了提高便利性(實現多個任務的時候,可以通過多個程序,而不用一個程序實現多個任務)計算機加入了操作系統
  • 同樣,相同的原因,線程誕生了。線程可以共享進程的資源。

線程優勢

發揮多處理器的強大功能

  • 隨着技術的發展,多處理器系統越來越普及。在一個雙處理器系統上,如果只用一個線程,那么無疑浪費了資源。

線程狀態

  • 新建(New):創建后尚未啟動的線程
  • 運行(Runanle):包括了操作系統線程中的Running和Ready,處於此狀態的線程可能正在執行或者等待cpu分配時間片
  • 無限期等待(Waiting):等待被其他線程顯式喚醒,執行wait或者join方法或者LockSupport的park方法
  • 限期等待(Timed Waiting):一定時間后會由系統自動喚醒
  • 阻塞(Blocked):等待獲取到一個排它鎖
  • 結束(Terminated):線程執行結束

多線程編程的兩種方式

  • 繼承Thread
  • 實現Runnable接口

通過繼承Thread

代碼的執行順序與代碼的順序無關

public class T1 {
	public static void main(String[] args) {
		MyThread myThread=new MyThread();
		myThread.start();
		System.out.println("代碼的執行結果與代碼的順序無關");
	}
}
class MyThread extends Thread
{
	public void run()
	{
		System.out.println("創建的線程");
	}
}

如果直接執行run方法是同步(主線程調用),start方法是讓系統來找一個時間來調用run方法(子線程調用),

public class T1 {
	public static void main(String[] args) {
		MyThread myThread=new MyThread();
		myThread.run();
		System.out.println("如果是直接執行run方法,肯定是按代碼順序執行的,因為是通過主線程調用的");
	}
}
class MyThread extends Thread
{
	public void run()
	{
		System.out.println("創建的線程");
	}
}

通過實現Runnable接口

比繼承Thread的方式更有優勢

  • java不能多繼承,所以如果線程類已經有一個父類,那么無法再繼承Thread類
public class MyRunnable implements Runnable {
	@Override
	public void run() {
		System.out.println("運行中!");
	}
}
public class Run {

	public static void main(String[] args) {
		Runnable runnable=new MyRunnable();
		Thread thread=new Thread(runnable);
		thread.start();
		System.out.println("運行結束!");
	}

}



線程數據非共享

public static void main(String[] args) {
			MyThread a = new MyThread("A");
			MyThread b = new MyThread("B");
			MyThread c = new MyThread("C");
			a.start();
			b.start();
			c.start();
		}
		
class MyThread extends Thread {

	private int count = 5;

	public MyThread(String name) {
		super();
		this.setName(name);
	}

	@Override
	public void run() {
		super.run();
		while (count > 0) {
			count--;
			System.out.println("由 " + this.currentThread().getName()
					+ " 計算,count=" + count);
		}
	}
}

  • 這里的i並不共享,每一個線程維護自己的i變量

線程數據共享

public static void main(String[] args) {
	MyThread mythread=new MyThread();
    //線程a b c啟動的時候,執行的是myThread的方法,此時數據共享
	Thread a=new Thread(mythread,"A");
	Thread b=new Thread(mythread,"B");
	Thread c=new Thread(mythread,"C");

	a.start();
	b.start();
	c.start();

}


  • 由於i++不是原子操作(先獲取i的值,讓后再加一,再把結果賦給i),所以輸出的值會有重復的情況,比如4 4 2

synchronized關鍵字讓i++同步執行

 public synchronized void run() {
		super.run();
			count--;
			System.out.println("由 "+this.currentThread().getName()+" 計算,count="+count);//輸出的一定是4 3 2
	}
  • synchronized 關鍵字,給方法加上鎖,多個線程嘗試拿到鎖,拿到鎖的線程執行方法,拿不到的不斷的嘗試拿到鎖

Thread方法

  • currentThread(),獲得當前線程
  • isLive() 線程是否活躍狀態(啟動還未運行或者啟動了正在運行即為活躍狀態)
  • sleep()方法,讓線程休眠
  • getId()方法 獲得該線程的唯一標識
  • suspeend()方法,讓線程暫停(已報廢)
  • ressume()方法,讓線程恢復(已報廢)
  • stop()方法,讓線程終止(已報廢)

停止線程的方法

  • 線程自己執行完后自動終止
  • stop強制終止,不安全
  • 使用interrupt方法

interrupt方法

  • 線程對象有一個boolean變量代表是否有中斷請求,interrupt方法將線程的中斷狀態設置會true,但是並沒有立刻終止線程,就像告訴你兒子要好好學習一樣,但是你兒子怎么樣關鍵看的是你兒子。
public static void main(String[] args) {
		try {
			MyThread thread = new MyThread();
			thread.start();
			Thread.sleep(200);
			thread.interrupt();
		} catch (InterruptedException e) {
			System.out.println("main catch");
			e.printStackTrace();
		}
		System.out.println("end!");
	}
	class MyThread extends Thread {
		@Override
		public void run() {
			super.run();
			for (int i = 0; i < 500000; i++) {
				System.out.println("i=" + (i + 1));
			}
		}
	}



  • 上面的代碼雖然執行了interrupt方法,但是並沒有中斷run方法里面的程序,並且run方法全部執行完,也就是一直執行到500000

判斷線程是否中斷

  • interrupted方法判斷當前線程是否中斷,清除中斷標志
  • isInterrupt 方法判斷線程是否中斷,不清除中斷標志

interrupted方法

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        for (int i = 0; i < 500000; i++) {
            System.out.println("i=" + (i + 1));
        }
    }
}
public class Run {
    public static void main(String[] args) {
        try {
            MyThread thread = new MyThread();
            thread.start();
            Thread.sleep(1000);
            thread.interrupt();
            //Thread.currentThread().interrupt();
            System.out.println("是否停止1?="+thread.interrupted());//false
            System.out.println("是否停止2?="+thread.interrupted());//false main線程沒有被中斷!!!
      //......
  • 這里thread線程執行了interrupt方法,按到里thread的中斷狀態應該為true,但是因為interrupted方法判斷的是當前線程的中斷狀態,也就是main線程(main線程執行thread.interrupted方法),所以他的中斷狀態是false
public class Run {
    public static void main(String[] args) {
        try {
            Thread.currentThread().interrupt();
            System.out.println("是否停止1?="+Thread.interrupted());//true
            System.out.println("是否停止2?="+Thread.interrupted());//false 
      //......
  • 主線程執行interrupt方法,第一次執行interrupted方法的時候,中斷狀態為true,但是interrupted方法有清除中斷標志的作用,所以再執行的時候輸出的是false

isInterrupt方法

public static void main(String[] args) {
		MyThread thread=new MyThread();
		thread.start();
		thread.interrupt();
		System.out.println(thread.isInterrupted());//true
		System.out.println(thread.isInterrupted());//true
	}
  • 這里也有判斷線程中斷的作用,而判斷的是他的調用者的中斷狀態,而且沒有清除中斷標志的作用,所以兩次都是true

停止線程

  • 在上面的代碼中,我們雖然執行了interrupt方法,但是並沒有中斷進程,那么我們如果來中斷呢?我們可以在run方法中進行判斷,判斷中斷狀態,狀態為true,那么就停止run方法。

import exthread.MyThread;

import exthread.MyThread;

public class Run {

	public static void main(String[] args) {
		try {
			MyThread thread = new MyThread();
			thread.start();
			Thread.sleep(2000);
			thread.interrupt();
		} catch (InterruptedException e) {
			System.out.println("main catch");
			e.printStackTrace();
		}
		System.out.println("end!");
	}

}

public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		for (int i = 0; i < 500000; i++) {
			if (this.interrupted()) {
				System.out.println("已經是停止狀態了!我要退出了!");
				break;
			}
			System.out.println("i=" + (i + 1));
		}
		System.out.println("666");
	}
}
  • 還有一個問題,我們要中斷進程,通過上面的的操作我們終止了for循環,但是后面的輸出666仍然執行,這並不是我們想要的中斷。於是我們可以順便拋出異常,然后直接捕獲,這樣的話后面的代碼就不執行了。

異常法停止線程

public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		try {
			for (int i = 0; i < 500000; i++) {
				if (this.interrupted()) {
					System.out.println("已經是停止狀態了!我要退出了!");
					throw new InterruptedException();
				}
				System.out.println("i=" + (i + 1));
			}
			System.out.println("我在for下面");
		} catch (InterruptedException e) {
			System.out.println("進MyThread.java類run方法中的catch了!");
			e.printStackTrace();
		}
	}
}


  • 當然我們也可以直接return,但是拋出異常比較好,因為后面可以繼續將異常拋出,讓線程中斷事件得到傳播

return 停止線程

for (int i = 0; i < 500000; i++) {
				if (this.interrupted()) {
					System.out.println("已經是停止狀態了!我要退出了!");
					return;
				}
				System.out.println("i=" + (i + 1));
			}

sleep與interrupt

  • 中斷狀態,進入sleep拋出異常
  • 睡眠進入中斷狀態,拋出異常
public static void main(String[] args) {
		try {
			MyThread thread = new MyThread();
			thread.start();
			Thread.sleep(200);
			thread.interrupt();
		} catch (InterruptedException e) {
			System.out.println("main catch");
			e.printStackTrace();
		}
		System.out.println("end!");
	}
}
	class MyThread extends Thread {
		@Override
		public void run() {
			super.run();
			try {
				System.out.println("run begin");
				Thread.sleep(200000);
				System.out.println("run end");
			} catch (InterruptedException e) {
				System.out.println("在沉睡中被停止!進入catch!"+this.isInterrupted());
				e.printStackTrace();
			}
		}
	}*/

暫停線程

  • suspend (作廢)會讓同步方法直接鎖住
public static void main(String[] args) {
		try {
			final SynchronizedObject object = new SynchronizedObject();

			Thread thread1 = new Thread() {
				@Override
				public void run() {
					object.printString();
				}
			};

			thread1.setName("a");
			thread1.start();

			Thread.sleep(1000);

			Thread thread2 = new Thread() {
				@Override
				public void run() {
					System.out
							.println("thread2啟動了,但進入不了printString()方法!只打印1個begin");
					System.out
							.println("因為printString()方法被a線程鎖定並且永遠的suspend暫停了!");
					object.printString();
				}
			};
			thread2.start();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

}
	class SynchronizedObject {

		synchronized public void printString() {
			System.out.println("begin");
			if (Thread.currentThread().getName().equals("a")) {
				System.out.println("a線程永遠 suspend了!");
				Thread.currentThread().suspend();
			}
			System.out.println("end");
		}

	}

  • 當thread1執行了suspend方法后,printString方法直接就被鎖住了,也就是thread1把這個鎖占住了,但是卻不工作
public void println(String x) {
        synchronized (this) {
            print(x);
            newLine();
        }
    }
  • 用System.out.println() 方法替換pringString方法同樣也會鎖住,因為這個方法也加鎖了,當thread1暫停之后,同樣占住了這個鎖

yield方法

  • 放棄當前cpu資源,讓其他任務去占用,但是什么時候放棄不知道,因為放棄后,可能又開始獲得時間片

線程優先級

  • cpu先執行優先級高的線程的對象任務, setPriority方法可以設置線程的優先級
  • 線程優先級具有繼承性,也就是說A線程啟動B線程,二者優先級一樣,同樣main主線程啟動線程A,main和A的優先級也是一樣的
public static void main(String[] args) {
		System.out.println("main thread begin priority="
				+ Thread.currentThread().getPriority());
		Thread.currentThread().setPriority(6);
		System.out.println("main thread end   priority="
				+ Thread.currentThread().getPriority());
		MyThread1 thread1 = new MyThread1();
		thread1.start();
	}


	class MyThread1 extends Thread {
		@Override
		public void run() {
			System.out.println("MyThread1 run priority=" + this.getPriority());
			MyThread2 thread2 = new MyThread2();
			thread2.start();
		}
	}

優先級特性

  • 規則性,cpu盡量將資源給優先級高的
  • 隨機性,優先級較高的不一定先執行完run方法

守護線程

  • 線程有兩種一種是用戶線程,一種是守護線程
  • 垃圾回收線程是典型的守護線程,當jvm中還有非守護線程,守護線程就一直還在,知道非守護線程不存在了,守護線程才銷毀

總結

  • 線程提高了資源利用率
  • 線程的實現可以通過繼承Thread類,也可以通過實Runnable接口
  • 線程中斷方式有3種,常用的是interrupt方法,該方法並沒有立即中斷線程,只是做了一個中斷標志
  • interrupted和isInterrupt兩種方法都可以查看線程中斷狀態,第一種查看的是當前線程的中斷狀態,第二種查看的該方法調用者的中斷狀態
  • interrupted方法會清除中斷狀態,isInterrupt不會清除中斷狀態
  • interrupt方法沒有真正中斷線程,所以可以在run方法里面判斷中斷狀態,然后通過拋出異常或者return來中斷線程
  • 當線中斷狀態為true,再進入sleep會拋出異常,反之一樣,sleep狀態執行interrupt方法,同樣會拋出異常
  • 線程暫停容易將鎖占住
  • 線程具有優先級,可以通過方法設置線程優先級,cpu會將資源盡量給優先級高的線程,但是當優先級差別不大的時候,優先級高的不一定先執行完run方法
  • 線程有兩種,一種用戶線程,一種守護線程,直到用戶線程都銷毀,守護線程才銷毀

我覺得分享是一種精神,分享是我的樂趣所在,不是說我覺得我講得一定是對的,我講得可能很多是不對的,但是我希望我講的東西是我人生的體驗和思考,是給很多人反思,也許給你一秒鍾、半秒鍾,哪怕說一句話有點道理,引發自己內心的感觸,這就是我最大的價值。(這是我喜歡的一句話,也是我寫博客的初衷)

作者:jiajun 出處: http://www.cnblogs.com/-new/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM