java多線程技能
前言:本系列將從零開始講解java多線程相關的技術,內容參考於《java多線程核心技術》與《java並發編程實戰》等相關資料,希望站在巨人的肩膀上,再通過我的理解能讓知識更加簡單易懂。
目錄
- 認識cpu、核心與線程
- java多線程系列(一)之java多線程技能
- java多線程系列(二)之對象變量的並發訪問
- java多線程系列(三)之等待通知機制
- java多線程系列(四)之ReentrantLock的使用
- java多線程系列(五)之synchronized ReentrantLock volatile Atomic 原理分析
- 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/
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。如果覺得還有幫助的話,可以點一下右下角的【推薦】,希望能夠持續的為大家帶來好的技術文章!想跟我一起進步么?那就【關注】我吧。