Bruce Eckel將線程的應用定義爲如何給CPU分配時間來提高利用率,優秀的代碼都必須考慮線程,尤其要用到共享資源的時候。
我們都知道當前的Windows操作系統是一個“多線程”操作系統。那麼什麼是線程呢?線程就是進程中的一個實體,它和進程一樣能夠獨立的執行控制,由操作系統負責調度,其區別就在於線程沒有獨立的存儲空間,而是與同屬於一個進程的其他線程共享一個存儲空間,這使得多線程之間的通信較進程簡單,並且多線程的執行都是併發而且是相互獨立的。爲了運行所有這些線程,操作系統爲每個獨立線程安排一些CPU 時間,操作系統以輪轉方式向線程提供時間片,這就給人一種假象,好象這些線程都在同時運行。
聲明:
大部分內容轉自[url]http://www.3lian.com/zl/2004/10-5/222237.html[/url],作者不知道是誰。只有最後一個實例是經過本人改過的,裏面添加了一些必要的註釋,有助於大家對線程的理解。
在Java中實現一個線程有兩種方法,第一是實現Runnable接口實現它的run()方法,第二種是繼承Thread類,覆蓋它的run()方法。下面是代碼示例:
public class DoSomething implements Runnable {
public void run(){
// here is where you do something
}
}
public class DoAnotherThing extends Thread {
public void run(){
// here is where you do something
}
}
public void run(){
// here is where you do something
}
}
public class DoAnotherThing extends Thread {
public void run(){
// here is where you do something
}
}
這兩種方法的區別是,如果你的類已經繼承了其它的類,那麼你只能選擇實現Runnable接口了,因爲Java只允許單繼承的。
Java中的線程有四種狀態分別是:運行、就緒、掛起、結束。如果一個線程結束了也就說明他是一個死線程了。當你調用一個線程實例的start()的方法的時候,這個時候線程進入就緒狀態,注意並不是運行狀態,當虛擬機開始分配給他CPU的運行時間片的時候線程開始進入運行狀態,當線程進入等待狀態,例如等待某個事件發生的時候,這時候線程處於掛起狀態。
啓動一個線程你只需要調用start()方法,針對兩種實現線程的方法也有兩種啓動線程的方法,分別如下:
DoSomething doIt = new DoSomething();
Thread myThread = new Thread( doIt );
myThread.start();
DoAnotherThing doIt = new DoAnotherThing();
doIt.start();
Thread myThread = new Thread( doIt );
myThread.start();
DoAnotherThing doIt = new DoAnotherThing();
doIt.start();
由於安全等因素Thread中的stop()方法已經不推薦使用了,因此如果你想要停止一個線程的時候可以通過設置一個信號量,例如:
public class MyThread implements Runnable {
private boolean quit = false;
public void run(){
while( !quit ){
// do something
}
}
public void quit(){
quit = true;
}
}
private boolean quit = false;
public void run(){
while( !quit ){
// do something
}
}
public void quit(){
quit = true;
}
}
如果每個線程只做它自己的事情,那麼就很簡單了,但是有的時候幾個線程可能要同時訪問一個對象並可能對它進行修改,這個時候你必須使用線程的同步在方法或者代碼塊使用關鍵字synchronized,例如:
public class Counter {
private int counter;
public synchronized int increment(){
return ++counter;
}
public synchronized int decrement(){
if( --counter < 0 ){
counter = 0;
}
return counter;
}
}
private int counter;
public synchronized int increment(){
return ++counter;
}
public synchronized int decrement(){
if( --counter < 0 ){
counter = 0;
}
return counter;
}
}
每個java對象都可以最爲一個監視器,當線程訪問它的synchronized方法的時候,他只允許在一個時間只有一個線程對他訪問,讓其他得線程排隊等候。這樣就可以避免多線程對共享數據造成破壞。記住synchronized是會耗費系統資源降低程序執行效率的,因此一定要在需要同步的時候才使用,尤其在J2ME的開發中要小心。
如果你要是想讓線程等待某個事件的發生然後繼續執行的話,那麼這就涉及到線程的調度了。在java中通過wait(),notify(),notifyAll()來實現,這三個方法是在Object類中定義的,當你想讓線程掛起的時候調用obj.wait()方法,在同樣的obj上調用notify()則讓線程重新開始運行。 最後以SUN提供的Producer/Consumer的例子來結束這篇文章,內容是Producer產生一個數字而Consumer消費這個數字,這個小程序裏面基本覆蓋了本文所有的知識點。請詳細研究一下代碼
每個java對象都可以最爲一個監視器,當線程訪問它的synchronized方法的時候,他只允許在一個時間只有一個線程對他訪問,讓其他得線程排隊等候。這樣就可以避免多線程對共享數據造成破壞。記住synchronized是會耗費系統資源降低程序執行效率的,因此一定要在需要同步的時候才使用,尤其在J2ME的開發中要小心。
如果你要是想讓線程等待某個事件的發生然後繼續執行的話,那麼這就涉及到線程的調度了。在java中通過wait(),notify(),notifyAll()來實現,這三個方法是在Object類中定義的,當你想讓線程掛起的時候調用obj.wait()方法,在同樣的obj上調用notify()則讓線程重新開始運行。 最後以SUN提供的Producer/Consumer的例子來結束這篇文章,內容是Producer產生一個數字而Consumer消費這個數字,這個小程序裏面基本覆蓋了本文所有的知識點。請詳細研究一下代碼
本人改了一個sun提供的線程實例,註釋都在代碼裏,
package org.bjtu.cn;
public class ProducerConsumerTest {
public static void main(String[] args) {
CubbyHole c=new CubbyHole();
Producer p1 = new Producer(c, 1); //提供者線程,只運行構造器
Consumer c1 = new Consumer(c, 1); //消費者線程,只運行構造器
/**
* start表示讓cpu準備就緒,運行run()的順序由cpu決定
* 這裏由CubbyHole的available決定,初始化改爲true則先運行c1.get方法
*/
p1.start();
c1.start();
}
}
class CubbyHole {
private int contents;
/**
* available線程掛起標記,爲true表示put掛起,get執行,false則get掛起,put執行
*/
private boolean available = false;
public synchronized int get() {
while (available == false){
try{
wait();
}
catch(InterruptedException e){}
}
available = false;
notifyAll();
return contents;
}
/**
* 先運行put方法,完成之後將put掛起(available = true),若不掛起則永遠不會執行get
* notify和notifyAll是一樣的,喚醒等待的線程,若不喚醒則put只執行一次
* @param value
*/
public synchronized void put(int value) {
while (available == true) {
try{
wait();
}
catch(InterruptedException e){}
}
contents = value;
available = true;
notifyAll();
}
}
class Producer extends Thread{
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println("Producer #" + this.number+ " put: " + i);
try {
sleep((int)(Math.random() * 100)); //線程等待0.1秒再執行
}
catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Consumer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
value = cubbyhole.get();
System.out.println("Consumer #" + this.number+ " got: " + value);
}
}
}
public class ProducerConsumerTest {
public static void main(String[] args) {
CubbyHole c=new CubbyHole();
Producer p1 = new Producer(c, 1); //提供者線程,只運行構造器
Consumer c1 = new Consumer(c, 1); //消費者線程,只運行構造器
/**
* start表示讓cpu準備就緒,運行run()的順序由cpu決定
* 這裏由CubbyHole的available決定,初始化改爲true則先運行c1.get方法
*/
p1.start();
c1.start();
}
}
class CubbyHole {
private int contents;
/**
* available線程掛起標記,爲true表示put掛起,get執行,false則get掛起,put執行
*/
private boolean available = false;
public synchronized int get() {
while (available == false){
try{
wait();
}
catch(InterruptedException e){}
}
available = false;
notifyAll();
return contents;
}
/**
* 先運行put方法,完成之後將put掛起(available = true),若不掛起則永遠不會執行get
* notify和notifyAll是一樣的,喚醒等待的線程,若不喚醒則put只執行一次
* @param value
*/
public synchronized void put(int value) {
while (available == true) {
try{
wait();
}
catch(InterruptedException e){}
}
contents = value;
available = true;
notifyAll();
}
}
class Producer extends Thread{
private CubbyHole cubbyhole;
private int number;
public Producer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
for (int i = 0; i < 10; i++) {
cubbyhole.put(i);
System.out.println("Producer #" + this.number+ " put: " + i);
try {
sleep((int)(Math.random() * 100)); //線程等待0.1秒再執行
}
catch (InterruptedException e){
e.printStackTrace();
}
}
}
}
class Consumer extends Thread {
private CubbyHole cubbyhole;
private int number;
public Consumer(CubbyHole c, int number) {
cubbyhole = c;
this.number = number;
}
public void run() {
int value = 0;
for (int i = 0; i < 10; i++) {
value = cubbyhole.get();
System.out.println("Consumer #" + this.number+ " got: " + value);
}
}
}
輸出結果
Producer #1 put: 0
Consumer #1 got: 0
Producer #1 put: 1
Consumer #1 got: 1
Producer #1 put: 2
Consumer #1 got: 2
Producer #1 put: 3
Consumer #1 got: 3
Producer #1 put: 4
Consumer #1 got: 4
Producer #1 put: 5
Consumer #1 got: 5
Producer #1 put: 6
Consumer #1 got: 6
Producer #1 put: 7
Consumer #1 got: 7
Producer #1 put: 8
Consumer #1 got: 8
Producer #1 put: 9
Consumer #1 got: 9
Consumer #1 got: 0
Producer #1 put: 1
Consumer #1 got: 1
Producer #1 put: 2
Consumer #1 got: 2
Producer #1 put: 3
Consumer #1 got: 3
Producer #1 put: 4
Consumer #1 got: 4
Producer #1 put: 5
Consumer #1 got: 5
Producer #1 put: 6
Consumer #1 got: 6
Producer #1 put: 7
Consumer #1 got: 7
Producer #1 put: 8
Consumer #1 got: 8
Producer #1 put: 9
Consumer #1 got: 9
Servlet線程
Servlet是在多線程環境下的。即可能有多個請求發給一個servelt實例,每個請求是一個線程。
1.什麼是線程安全的代碼
在多線程環境下能正確執行的代碼就是線程安全的。
安全的意思是能正確執行,否則後果是程序執行錯誤,可能出現各種異常情況。
2.如何編寫線程安全的代碼
很多書籍裏都詳細講解了如何這方面的問題,他們主要講解的是如何同步線程對共享資源的使用的問題。主要是對synchronized關鍵字的各種用法,以及鎖的概念。
Java1.5中也提供瞭如讀寫鎖這類的工具類。這些都需要較高的技巧,而且相對難於調試。
但是,線程同步是不得以的方法,是比較複雜的,而且會帶來性能的損失。等效的代碼中,不需要同步在編寫容易度和性能上會更好些。
我這裏強調的是什麼代碼是始終爲線程安全的、是不需要同步的。如下:
1)常量始終是線程安全的,因爲只存在讀操作。
2)對構造器的訪問(new 操作)是線程安全的,因爲每次都新建一個實例,不會訪問共享的資源。
3)最重要的是:局部變量是線程安全的。因爲每執行一個方法,都會在獨立的空間創建局部變量,它不是共享的資源。局部變量包括方法的參數變量。
在多線程環境下能正確執行的代碼就是線程安全的。
安全的意思是能正確執行,否則後果是程序執行錯誤,可能出現各種異常情況。
2.如何編寫線程安全的代碼
很多書籍裏都詳細講解了如何這方面的問題,他們主要講解的是如何同步線程對共享資源的使用的問題。主要是對synchronized關鍵字的各種用法,以及鎖的概念。
Java1.5中也提供瞭如讀寫鎖這類的工具類。這些都需要較高的技巧,而且相對難於調試。
但是,線程同步是不得以的方法,是比較複雜的,而且會帶來性能的損失。等效的代碼中,不需要同步在編寫容易度和性能上會更好些。
我這裏強調的是什麼代碼是始終爲線程安全的、是不需要同步的。如下:
1)常量始終是線程安全的,因爲只存在讀操作。
2)對構造器的訪問(new 操作)是線程安全的,因爲每次都新建一個實例,不會訪問共享的資源。
3)最重要的是:局部變量是線程安全的。因爲每執行一個方法,都會在獨立的空間創建局部變量,它不是共享的資源。局部變量包括方法的參數變量。