進程線程
什麼是進程呢?
進程就是程序(任務)的執行過程,它是動態性的,比如大家常使用的QQ、開發用的Eclipse等經過點擊運行就可以看作一個進程,在磁盤中存儲的相關文件不是進程;進程持有資源(共享內存,共享文件)和線程。
那什麼是線程呢?
拿QQ來說,在你和朋友進行文字聊天的同時還可以收發文件;在使用Eclipse進行源代碼編輯,後臺會進行語法檢驗和源代碼編譯,這些不同的小任務就是線程。再舉一個生活中的例子,可以將一個班級比作一個進程,班級中的每一個學生視作一個線程,一個班級可以有多個學生,學生是班級裡的最小單元,構成了班級的最小單位,這些學生都使用共同的黑板、教室等資源。
概括起來線程具有如下特點:
線程是系統中最小的執行單元
同一進程中有多個線程
線程共享進程的資源
線程之間靠互斥和同步進行交互。
線程的創建
創建線程有兩種常用方法,一種是繼承Thread類,一種是實現Runnable方法,這兩種創建線程的方法都屬於java.lang包,擁有同樣的run()方法。
這個方法提供了線程工作實際運行的代碼。這兩種創建方式是存在一定聯繫的,查看Thread類的結構如下:
public class Thread implements Runnable {}
由源碼發現Thread類實現了Runnable接口,它們之間具有多態關係。若使用繼承Thread 類的方式創建線程時,最大的侷限就是不支持多繼承(Java語言的特點是單根繼承),因而可以使用Runnable接口的方式創建線程。
線程創建的兩種方式代碼:
1.繼承Thread類
class MyThread extends Thread {
......
@Override
public void run() {
......
}
}
MyThread mt = new MyThread(); //創建線程
mt.start();
2.實現Runnable接口
class MyThread implements Runnable {
......
@Override
public void run() {
......
}
}
MyThread mt = new MyThread();
Thread td = new Thread(mt); //創建線程
td.start(); //啟動線程
兩種創建線程方式的比較:
Runnable方式可以避免Thread方式由於Java單繼承特性帶來的缺陷
Runnable的代碼可以被多個線程(Thread實例)共享,適合於多個線程處理同一資源的情況
非線程安全問題
線程被調用的時機是隨機的。在使用多線程技術時,代碼的運行結果與代碼執行順序或調用順序是無關的。
在多線程中可能出現多個線程同時處理一個共享變量(i–,變量自減),產生非線程安全 問題(非線程安全主要是指多個線程對同一個對象中的同一個實例變量進行操作時會出現值被更改、值不同步的情況,進而影響程序的執行流程)
在某些JVM中,i–操作要分成如下三部(不是原子的):
1) 取得原有i值
2) 計算i-1
3) 對i進行賦值
在這三個步驟中,有多個線程同時訪問,則一定會出現非線程安全問題。
為了解決如上問題,可以通過在run方法前加入synchronized關鍵字,是多個線程在執行run方法時,以排隊的方法進行處理。
System.out.println()方法內部是同步的
public void println(String x) {
syschronized (this) {
print(x);
newLine();
}
}
但是在println()方法中使用 i– 操作是在println()之前發生的,因而有發生非線程安全問題的概率。
Java線程的生命週期
創建:新建一個線程對象,如Thread thd = new Thread()
就緒:創建了線程對象後,調用了線程的start()方法(注意:此時線程只是進入了線程隊列,等待獲取CPU服務,具備了運行的條件,但並不一定已經開始運行了)
運行:處於就緒狀態的線程,一旦獲取了CPU資源,便進入到運行狀態,開始執行run()方法裡面的邏輯
阻塞:一個正在執行的線程在某些情況下,由於某種原因而暫時讓出了CPU資源,暫停了自己的執行,便進入了阻塞狀態,如調用了sleep()方法
終止:線程的run()方法執行完畢,或者線程調用了stop()方法,線程便進入終止狀態。(線程調用stop()終止的方法已被淘汰掉了)
Java線程:用戶線程和守護線程
用戶線程:運行在前臺,執行具體的任務,程序的主線程、連接網絡的子線程等都是用戶線程
守護線程:運行在後臺,為其他前臺線程服務;
特點:一旦所有用戶線程都結束運行,守護線程會隨JVM一起結束工作
應用:數據庫連接池中的檢測線程
JVM虛擬機啟動後的檢測線程
如何設置守護進程
可以通過調用Thread類的setDaemon(true)方法來設置當前的線程為守護線程
實例:
package mkw.demo.daemon;
import java.io.File;
import java.io.FileOutputStream;
import java.io.OutputStream;
import java.util.Scanner;
class DaemonThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("進入守護進程" + Thread.currentThread().getName());
try {
writeTofile();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
System.out.println("退出守護進程" + Thread.currentThread().getName());
}
private void writeTofile() throws Exception{
File filename = new File("d:" + File.separator + "daemon.txt");
OutputStream os = new FileOutputStream(filename,true);
int count = 0;
while(count < 10){
os.write(("\r\nword" + count).getBytes());
System.out.println("守護線程" + Thread.currentThread().getName()
+ "向文件中寫入了word" + count++);
Thread.sleep(1000); //睡眠1秒
}
}
}
public class daemonThreadDaemon {
public static void main(String[] args) {
// TODO Auto-generated method stub
System.out.println("進入主線程" + Thread.currentThread().getName());
DaemonThread daemonThread = new DaemonThread();
Thread thread = new Thread(daemonThread);
thread.setDaemon(true);
thread.start();
Scanner sc = new Scanner(System.in);
sc.next();
System.out.println("退出主線程" + Thread.currentThread().getName());
}
程序運行結果:
進入主線程main
進入守護進程Thread-0
守護線程Thread-0向文件中寫入了word0
守護線程Thread-0向文件中寫入了word1
守護線程Thread-0向文件中寫入了word2
守護線程Thread-0向文件中寫入了word3
守護線程Thread-0向文件中寫入了word4
退出守護進程Thread-0
注意事項:
setDaemon(true)必須在start()方法之前調用,否則會拋出IllegalThreadStateException異常
在守護線程中產生的新線程也是守護線程
不是所有的任務都可以分配給守護線程來執行,比如讀寫操作或者計算邏輯
使用jstack生成線程快照
命令行工具(jstat.exe)、界面化工具(jvisualvm.exe)
jstack作用:生成JVM當前時刻線程的快照(threaddump,即當前進程中所有線程的信息)
目的:幫助定位程序問題出現的原因,如長時間停頓、CPU佔用率過高等
下面使用jstack命令行工具查看上一步中創建守護進程程序的快照,首先運行該程序,在任務管理器中獲取程序的進程ID,然後再DOS中使用jstack命令查看
D:\Program Files\Java\jdk1.7.0_51\bin>jstack -l 71704 //-l選項查看鎖信息
2016-08-29 10:32:42 //線程生成快照的時間
Full thread dump Java HotSpot(TM) 64-Bit Server VM (24.51-b03 mixed mode): //eclipse運行時使用的jre類型和版本,HotSpot為Sun公司的虛擬機,使用的是服務器版本
//第一個線程Thread-0就是創建的守護線程,後邊跟有daemon標記,緊接著是表示線程的優先級、標識等信息
"Thread-0" daemon prio=6 tid=0x000000000bd68000 nid=0x110ac waiting on condition [0x000000000d14f000
]
java.lang.Thread.State: TIMED_WAITING (sleeping) //顯示線程的狀態
at java.lang.Thread.sleep(Native Method)
at mkw.demo.daemon.DaemonThread.writeTofile(daemonThreadDaemon.java:31)
at mkw.demo.daemon.DaemonThread.run(daemonThreadDaemon.java:15)
at java.lang.Thread.run(Unknown Source)
Locked ownable synchronizers: //線程鎖信息,None未加鎖,使用-l選項才會顯示
- None
"Service Thread" daemon prio=6 tid=0x000000000bd11800 nid=0x10924 runnable [0x0000000000000000] //此處的線程是java程序運行所必須的一些線程信息
java.lang.Thread.State: RUNNABLE
Locked ownable synchronizers:
- None
......
//主線程main,沒有daemon標識,是用戶線程
"main" prio=6 tid=0x00000000025ee800 nid=0x11294 runnable [0x00000000029ff000]
java.lang.Thread.State: RUNNABLE
at java.io.FileInputStream.readBytes(Native Method)
at java.io.FileInputStream.read(Unknown Source)
at java.io.BufferedInputStream.read1(Unknown Source)
at java.io.BufferedInputStream.read(Unknown Source)
- locked <0x00000007cc055778> (a java.io.BufferedInputStream)
at sun.nio.cs.StreamDecoder.readBytes(Unknown Source)
at sun.nio.cs.StreamDecoder.implRead(Unknown Source)
at sun.nio.cs.StreamDecoder.read(Unknown Source)
- locked <0x00000007cc0a19f0> (a java.io.InputStreamReader)
at java.io.InputStreamReader.read(Unknown Source)
at java.io.Reader.read(Unknown Source)
at java.util.Scanner.readInput(Unknown Source)
at java.util.Scanner.next(Unknown Source)
at mkw.demo.daemon.daemonThreadDaemon.main(daemonThreadDaemon.java:47)
Locked ownable synchronizers:
- None
查看JDK文檔中的解釋
線程狀態。線程可以處於下列狀態之一:NEW至今尚未啟動的線程處於這種狀態。RUNNABLE正在Java虛擬機中執行的線程處於這種狀態。BLOCKED受阻塞並等待某個監視器鎖的線程處於這種狀態。WAITING無限期地等待另一個線程來執行某一特定操作的線程處於這種狀態。TIMED_WAITING等待另一個線程來執行取決於指定等待時間的操作的線程處於這種狀態。TERMINATED已退出的線程處於這種狀態。在給定時間點上,一個線程只能處於一種狀態。這些狀態是虛擬機狀態,它們並沒有反映所有操作系統線程狀態。
閱讀更多 有理想的代碼dog 的文章