一、線程和進程的概念
現在的操作系統是多任務操作系統。多線程是實現多任務的一種方式。
進程是程序的一個動態執行過程,是指一個內存中運行的應用程序,每個進程都有自己獨立的一塊內存空間,一個進程中可以啓動多個線程。比如在 Windows系統中,一個運行的exe就是一個進程。線程是指進程中的一個執行流程,一個進程中可以運行多個線程。比如java.exe進程中可以運行很多線程。線程總是屬於某個進程,進程中的多個線程共享進程的內存。“同時”執行是人的感覺,在線程之間實際上輪換執行。
二、Java中線程的實現
在Java中想實現多線程有兩種手段,一種是集成Thread類,另一種就是實現Runnable接口。下面看繼承自Thread類線程的創建原理。
首先定義一個線程類,該類必須繼承自Thread類,同時必須明確的覆寫run()方法,如:
class MyThread extends Thread{
public void run(){ //覆寫Thread類中的run方法此方法是線程中
線程主體;
}
}
然後定義一個主類,實例化線程類的對象,發動啓動線程的命令,如:
public class ThreadText{
public stataic void main(String args[]){
MyThread m1=new MyThread();//實例化對象
m1.start();//啓動多線程
}
}
實現Runnable接口,首先定義一個線程類繼承自Runnable接口,如:
class MyThread implements Runnable{
public void run(){ //覆寫Runnable接口中的run方法
線程主體;
}
}
然後定義一個主類,實例化線程類的對象,發動啓動線程的命令,如:
public class ThreadText{
public stataic void main(String args[]){
MyThread m1=new MyThread();//實例化Runnable子類對象
Thread t1=new Thread(m1);//實例化Thread類對象
t1.start();//啓動多線程
}
}
線程的兩種實現方式,通過實現Runnable接口的線程方式可以實現資源的共享,而繼承Thread則不可以,原因何在?先看下面兩段代碼:
通過Thread實現線程:
- //使用Thread實現線程不能實現資源共享
- //使用Thread實現線程不能實現資源共享
- class MyThread extends Thread
- {
- private int ticket=5;
- private String name;
- public MyThread(String name ){
- this.name=name;
- }
- public void run(){
- for(int i=0;i<10;i++){
- if(ticket>5){
- System.out.println("線程"+name+"賣票"+i);
- }
- }
- }
- }
- public class ThreadDemo02
- {
- public static void main(String args[]){
- MyThread A = new MyThread("A");
- MyThread B = new MyThread("B");
- A.start();
- B.start();
- }
- }
通過Runnable實現:
-
- //使用Runnable實現線程可以實現資源共享
- class MyThread implements Runnable
- {
- private int ticket=5;
- private String name;
- public MyThread(String name){
- this.name=name;
- }
- public void run(){
- for(int i=1;i<=10;i++){
- if(ticket>0){
- System.out.println("線程"+name+"賣票"+(ticket--));
- }
- }
- }
- }
- public class RunnableDemo02
- {
- public static void main(String args[]){
- MyThread A = new MyThread("A"); //實例化線程要執行的任務
- Thread Ta = new Thread(A); //實例兩個線程對象,實際傳遞的是一個任務
- Thread Tb = new Thread(A); //因爲兩個線程執行的是一個任務,所以資源是共享的
- Ta.start();
- Tb.start();
- }
- }
解釋:
因爲一個線程只能啓動一次,通過Thread實現線程時,線程和線程所要執行的任務是捆綁在一起的。也就使得一個任務只能啓動一個線程,不同的線程執行的任務是不相同的,所以沒有必要,也不能讓兩個線程共享彼此任務中的資源。
一個任務可以啓動多個線程,通過Runnable方式實現的線程,實際是開闢一個線程,將任務傳遞進去,由此線程執行。可以實例化多個 Thread對象,將同一任務傳遞進去,也就是一個任務可以啓動多個線程來執行它。這些線程執行的是同一個任務,所以他們的資源是共享。
兩種不同的線程實現方式本身就決定了其是否能進行資源共享。
個人理解:網上很多文章講述可以使用Runnable實現資源共享,當然有的使用繼承Thread也可以,但我還是表示懷疑這種做法是否真的可以實現資源共享,在共享資源的過程中並沒有使用同步字段,這樣是否會引起資源衝突,還需進一步探討,如有牛人解釋,不勝感激!
我麼知道Java傳統多線程的實現有兩種方法,繼承Thread類或者實現Runnable即可.線程啓動時調用start()方法.
實現Runnable接口相比繼承Thread類有如下好處:
1.避免單繼承的侷限,一個類可以同時實現多個接口
2.適合資源的共享.
線程的同步
Volatile 是保證多個線程之間變量可見性的,也就是說一個線程對變量進行了寫操作,另外一個線程能夠獲取它最新的值。
它的工作原理是,它對寫和讀都是直接操作工作主存的。(這個可以通過操作字節碼看到)
進行多線程編程,同步控制是非常重要的,而同步控制就涉及到了鎖。
對代碼進行同步控制我們可以選擇同步方法,也可以選擇同步塊,這兩種方式各有優缺點,至於具體選擇什麼方式,就見仁見智了,同步塊不僅可以更加精確的控制對象鎖,也就是控制鎖的作用域,何謂鎖的作用域?鎖的作用域就是從鎖被獲取到其被釋放的時間。而且可以選擇要獲取哪個對象的對象鎖。但是如果在使用同步塊機制時,如果使用過多的鎖也會容易引起死鎖問題,同時獲取和釋放所也有代價,而同步方法,它們所擁有的鎖就是該方法所屬的類的對象鎖,換句話說,也就是this對象,而且鎖的作用域也是整個方法,這可能導致其鎖的作用域可能太大,也有可能引起死鎖,同時因爲可能包含了不需要進行同步的代碼塊在內,也會降低程序的運行效率。而不管是同步方法還是同步塊,我們都不應該在他們的代碼塊內包含無限循環,如果代碼內部要是有了無限循環,那麼這個同步方法或者同步塊在獲取鎖以後因爲代碼會一直不停的循環着運行下去,也就沒有機會釋放它所獲取的鎖,而其它等待這把鎖的線程就永遠無法獲取這把鎖,這就造成了一種死鎖現象。
簡單自己總結一下:同步方法的鎖是this,靜態同步方法是該類 ;同步代碼塊的鎖自己規定,在syn後()中定義。
詳細解說一下同步方法的鎖,同步方法分爲靜態同步方法與非靜態同步方法。
所有的非靜態同步方法用的都是同一把鎖——實例對象本身,也就是說如果一個實例對象的非靜態同步方法獲取鎖後,該實例對象的其他非靜態同步方法必須等待獲取鎖的方法釋放鎖後才能獲取鎖,可是別的實例對象的非靜態同步方法因爲跟該實例對象的非靜態同步方法用的是不同的鎖,所以毋須等待該實例對象已獲取鎖的非靜態同步方法釋放鎖就可以獲取他們自己的鎖。
而所有的靜態同步方法用的也是同一把鎖——類對象本身,這兩把鎖是兩個不同的對象,所以靜態同步方法與非靜態同步方法之間是不會有競態條件的。但是一旦一個靜態同步方法獲取鎖後,其他的靜態同步方法都必須等待該方法釋放鎖後才能獲取鎖,而不管是同一個實例對象的靜態同步方法之間,還是不同的實例對象的靜態同步方法之間,只要它們同一個類的實例對象!
而對於同步塊,由於其鎖是可以選擇的,所以只有使用同一把鎖的同步塊之間纔有着競態條件,這就得具體情況具體分析了,但這裏有個需要注意的地方,同步塊的鎖是可以選擇的,但是不是可以任意選擇的!!!!這裏必須要注意一個物理對象和一個引用對象的實例變量之間的區別!使用一個引用對象的實例變量作爲鎖並不是一個好的選擇,因爲同步塊在執行過程中可能會改變它的值,其中就包括將其設置爲null,而對一個null對象加鎖會產生異常,並且對不同的對象加鎖也違背了同步的初衷!這看起來是很清楚的,但是一個經常發生的錯誤就是選用了錯誤的鎖對象,因此必須注意:同步是基於實際對象而不是對象引用的!多個變量可以引用同一個對象,變量也可以改變其值從而指向其他的對象,因此,當選擇一個對象鎖時,我們要根據實際對象而不是其引用來考慮!作爲一個原則,不要選擇一個可能會在鎖的作用域中改變值的實例變量作爲鎖對象!!!!
public class Foo {
private int x = 100;
public int getX() {
return x;
}
public int fix(int y) {
x = x - y;
return x;
}
}
public class MyRunnable implements Runnable {
private Foo foo = new Foo();
public static void main(String[] args) {
MyRunnable r = new MyRunnable();
Thread ta = new Thread(r, "Thread-A");
Thread tb = new Thread(r, "Thread-B");
ta.start();
tb.start();
}
public void run() {
for (int i = 0; i < 3; i++) {
this.fix(30);
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " : 當前foo對象的x值= " + foo.getX());
}
}
public int fix(int y) {
return foo.fix(y);
}
}
Thread-B : 當前foo對象的x值= 40
Thread-B : 當前foo對象的x值= -20
Thread-A : 當前foo對象的x值= -50
Thread-A : 當前foo對象的x值= -80
Thread-B : 當前foo對象的x值= -80
Process finished with exit code 0
6)、線程睡眠時,它所持的任何鎖都不會釋放。
public int fix(int y) {
synchronized (this) {
x = x - y;
}
return x;
}
public synchronized int getX() {
return x++;
}
public int getX() {
synchronized (this) {
return x;
}
}
public static synchronized int setName(String name){
Xxx.name = name;
}
public static int setName(String name){
synchronized(Xxx.class){
Xxx.name = name;
}
}
public class NameList {
private List nameList = Collections.synchronizedList(new LinkedList());
public void add(String name) {
nameList.add(name);
}
public String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
} else {
return null;
}
}
}
public class Test {
public static void main(String[] args) {
final NameList nl = new NameList();
nl.add("aaa");
class NameDropper extends Thread{
public void run(){
String name = nl.removeFirst();
System.out.println(name);
}
}
Thread t1 = new NameDropper();
Thread t2 = new NameDropper();
t1.start();
t2.start();
}
}
public class NameList {
private List nameList = Collections.synchronizedList(new LinkedList());
public synchronized void add(String name) {
nameList.add(name);
}
public synchronized String removeFirst() {
if (nameList.size() > 0) {
return (String) nameList.remove(0);
} else {
return null;
}
}
}
public class DeadlockRisk {
private static class Resource {
public int value;
}
private Resource resourceA = new Resource();
private Resource resourceB = new Resource();
public int read() {
synchronized (resourceA) {
synchronized (resourceB) {
return resourceB.value + resourceA.value;
}
}
}
public void write(int a, int b) {
synchronized (resourceB) {
synchronized (resourceA) {
resourceA.value = a;
resourceB.value = b;
}
}
}
}
線程的狀態
2. 就緒狀態(Runnable):線程對象創建後,其他線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取CPU的使用權。
3. 運行狀態(Running):就緒狀態的線程獲取了CPU,執行程序代碼。
4. 阻塞狀態(Blocked):阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
(一)、等待阻塞:運行的線程執行wait()方法,JVM會把該線程放入等待池中。
(二)、同步阻塞:運行的線程在獲取對象的同步鎖時,若該同步鎖被別的線程佔用,則JVM會把該線程放入鎖池中。
(三)、其他阻塞:運行的線程執行sleep()或join()方法,或者發出了I/O請求時,JVM會把該線程置爲阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead):線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
二. 線程狀態圖
線程間的相互作用:線程之間需要一些協調通信,來共同完成一件任務。
Object類中相關的方法有兩個notify方法和三個wait方法:
http://docs.oracle.com/javase/7/docs/api/java/lang/Object.html
因爲wait和notify方法定義在Object類中,因此會被所有的類所繼承。
這些方法都是final的,即它們都是不能被重寫的,不能通過子類覆寫去改變它們的行爲。
wait()方法
wait()方法使得當前線程必須要等待,等到另外一個線程調用notify()或者notifyAll()方法。
當前的線程必須擁有當前對象的monitor,也即lock,就是鎖。
線程調用wait()方法,釋放它對鎖的擁有權,然後等待另外的線程來通知它(通知的方式是notify()或者notifyAll()方法),這樣它才能重新獲得鎖的擁有權和恢復執行。
要確保調用wait()方法的時候擁有鎖,即,wait()方法的調用必須放在synchronized方法或synchronized塊中。
一個小比較:
當線程調用了wait()方法時,它會釋放掉對象的鎖。
另一個會導致線程暫停的方法:Thread.sleep(),它會導致線程睡眠指定的毫秒數,但線程在睡眠的過程中是不會釋放掉對象的鎖的。
notify()方法
notify()方法會喚醒一個等待當前對象的鎖的線程。
如果多個線程在等待,它們中的一個將會選擇被喚醒。這種選擇是隨意的,和具體實現有關。(線程等待一個對象的鎖是由於調用了wait方法中的一個)。
被喚醒的線程是不能被執行的,需要等到當前線程放棄這個對象的鎖。
被喚醒的線程將和其他線程以通常的方式進行競爭,來獲得對象的鎖。也就是說,被喚醒的線程並沒有什麼優先權,也沒有什麼劣勢,對象的下一個線程還是需要通過一般性的競爭。
notify()方法應該是被擁有對象的鎖的線程所調用。
(This method should only be called by a thread that is the owner of this object's monitor.)
換句話說,和wait()方法一樣,notify方法調用必須放在synchronized方法或synchronized塊中。
wait()和notify()方法要求在調用時線程已經獲得了對象的鎖,因此對這兩個方法的調用需要放在synchronized方法或synchronized塊中。
一個線程變爲一個對象的鎖的擁有者是通過下列三種方法:
1.執行這個對象的synchronized實例方法。
2.執行這個對象的synchronized語句塊。這個語句塊鎖的是這個對象。
3.對於Class類的對象,執行那個類的synchronized、static方法。
程序實例
利用兩個線程,對一個整形成員變量進行變化,一個對其增加,一個對其減少,利用線程間的通信,實現該整形變量0101這樣交替的變更。
public class NumberHolder
{
private int number;
public synchronized void increase()
{
if (0 != number)
{
try
{
wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
// 能執行到這裏說明已經被喚醒
// 並且number爲0
number++;
System.out.println(number);
// 通知在等待的線程
notify();
}
public synchronized void decrease()
{
if (0 == number)
{
try
{
wait();
}
catch (InterruptedException e)
{
e.printStackTrace();
}
}
// 能執行到這裏說明已經被喚醒
// 並且number不爲0
number--;
System.out.println(number);
notify();
}
}
public class IncreaseThread extends Thread
{
private NumberHolder numberHolder;
public IncreaseThread(NumberHolder numberHolder)
{
this.numberHolder = numberHolder;
}
@Override
public void run()
{
for (int i = 0; i < 20; ++i)
{
// 進行一定的延時
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
// 進行增加操作
numberHolder.increase();
}
}
}
public class DecreaseThread extends Thread
{
private NumberHolder numberHolder;
public DecreaseThread(NumberHolder numberHolder)
{
this.numberHolder = numberHolder;
}
@Override
public void run()
{
for (int i = 0; i < 20; ++i)
{
// 進行一定的延時
try
{
Thread.sleep((long) Math.random() * 1000);
}
catch (InterruptedException e)
{
e.printStackTrace();
}
// 進行減少操作
numberHolder.decrease();
}
}
}
public class NumberTest
{
public static void main(String[] args)
{
NumberHolder numberHolder = new NumberHolder();
Thread t1 = new IncreaseThread(numberHolder);
Thread t2 = new DecreaseThread(numberHolder);
t1.start();
t2.start();
}
}