在java中,一個線程用一個Thread對象表示
一般每一個java程序都有一個main方法(applet沒有),它是主線程的入口點
而用Thread表示的線程,入口點自然不是main了,而是run方法
定義一個線程,主要就是寫它的run方法
寫run有兩種方法,一種是繼承Thread類,然后重寫它的run
另一種是實現Runnable接口,然后重寫它的run,如下所示:
============================
//用外部類實現多線程
class ThreadTest2
{
public static void main(String[] args)
{
new ThreadTest2();
}
ThreadTest2(){
for(int i=1; i<=5; i++){
System.out.println("creating thread "+i);
outterThread th = new outterThread(i);//創建新線程
th.start();//啟動剛創建的線程
}
}
}
class outterThread extends Thread//通過繼承Thread類來實現多線程;外部類
{
int count;
outterThread(int i){
count = i;
}
public void run() {
while(true){
System.out.println("thread "+count+" is running");
try{
sleep(1000*count);
}
catch(InterruptedException e){}
}
}
}
=====================================
class ThreadTest3 implements Runnable //通過實現Runnable接口來實現多線程
{
int count;
public static void main(String[] args)
{
for(int i=1; i<=5; i++)
//調用了Thread類的構造函數Thread(Runnable target)
new Thread(new ThreadTest3(i)).start();
}
ThreadTest3(int i){
System.out.println("creating thread "+i);
count = i;
}
public void run(){
while(true){
System.out.println("thread "+count+" is running");
try{
Thread.sleep(1000*count);//讓線程睡眠一段時間
}
catch(InterruptedException e){}
}
}
}
可以看到,不論如何重寫run,都要生成Thread對象.不同的是,第二種方法要在Thread的構造函數里傳入Runnable的實現類作為參數
上面的例子還用到了start()和sleep()
前者用作啟動線程,調用它之后,run方法就開始執行;后者使線程暫停參數指定的時間,單位是毫秒.這個sleep會拋出異常,所以要有try,catch塊
===========================
在上面的例子中,在for循環中創建的線程和主線程是并列的,一個線程的結束不會影響另一個線程
在上面的例子中,執行完for循環,主線程就結束了,而其他線程還在運行著
用setDaemon()可以使一個線程作為后臺線程執行,也就是說,當其他非Daemon線程結束時,不論run執行完沒有,后臺線程也將退出
反過來,你也可以使主線程等待其他線程結束后再退出.注意這里是主線程等待其他線程結束,而上面講的daemon線程是在所有非daemon(主線程和其他非daemon)退出后再退出.要實現這一目的,可使用join().它可帶可不帶參數,帶參數的話, 參數表示等待時間,如果過了這指定的時間不管join了的線程退出與否,主線程都會結束(如果main方法已經執行完了)
下面是例子
=======================
class DaemonAndJoin
{
public static void main(String[] args)
{
Thread th = new Thread( new Runnable(){
int count = 2;
public void run(){
while(count>0){
for(int i=0; i<5; i++){
try{
Thread.sleep(500);
System.out.println("in thread 1 ");
}catch(InterruptedException e){
e.printStackTrace();
}
}
count--;
System.out.println("one for is done");
}
}
});
Thread th2 = new Thread(new Runnable(){
public void run(){
while(true){
try{
Thread.sleep(1000);
System.out.println("in thread 2");
}catch(InterruptedException e){
e.printStackTrace();
}
}
}
});
System.out.println("main thread begins");
th.start();
th2.setDaemon(true);//設置后臺線程
th2.start();
try{
th.join();//join
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("main thread quits");
}
}
==============
上面例子,如果沒有th這個線程,th2和主線程都會很快結束
如果有th,但是沒有join,主線程會很快結束,但是th2因為還有一個非daemon未結束,所以不會很快結束,而一直等到th結束才結束
如果沒有setDaemon,則th不影響th2
==================
線程的同步
當多個線程同時對一個對象進行操作,就有可能發生問題,這時就要用線程同步來解決
考慮下面的例子
class SychronizedTest
{
public static void main(String[] args)
{
final Student stu = new Student();
Thread setNameAndNumth1 = new Thread( new Runnable(){
public void run(){
while(true){
synchronized(stu){
stu.setNameAndNum("john","jj");
}
}
}
});
Thread setNameAndNumth2 = new Thread( new Runnable(){
public void run(){
while(true){
//synchronized(stu){
stu.setNameAndNum("kate","kk");
//}
}
}
});
setNameAndNumth1.start();
setNameAndNumth2.start();
System.out.println("test started:");
}
}
class Student{
private String name;
private String id;
private int count = 0;
public /*synchronized*/ void setNameAndNum(String name,String id){
// synchronized(this){
this.name = name;
this.id = id;
count++;
if(!check())
System.out.println( count + " unmatched name and id: "+name+" "+id);
// }
}
private boolean check(){
return name.charAt(0) == id.charAt(0);
}
}
在這個例子中,Student類有兩個屬性.
有兩個線程,它們修改同一個Student類的實例,然后檢查修改后的這個實例的兩個屬性符不符合要求(判斷兩個屬性的第一個字符是否一樣)
如果不符合要求,則在控制臺輸出語句,否則不輸出
例子中有三部分注釋,每一部分說明了一種同步方式
最上面方式使Student類的方法都按同步的方式執行
第二種通過synchronized關鍵字,使單一一個方法,而不是全部方法按同步的方式執行
第三種只是一個方法里的某一部分按同步的方法執行(雖然在這個例子里,方法的整個方法體都被括起來了)
====================================
最后附上經典的生者消費者程序
class WaitNotifyTest
{
public static void main(String[] args)
{
Clerk clerk = new Clerk();
Producer producer = new Producer(clerk);
Consumer consumer = new Consumer(clerk);
Thread producerThread = new Thread(producer);
Thread consumerThread = new Thread(consumer);
producerThread.start();
consumerThread.start();
}
}
class Producer implements Runnable{
Clerk clerk;
Producer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("producer starts producing product " );
for(int i=0; i<10; i++){
try{
Thread.sleep( (int)(Math.random()*3000) );
}catch(InterruptedException e){
e.printStackTrace();
}
clerk.setProduct(i);
// System.out.println("producer is producing product " + i);
}
}
}
class Consumer implements Runnable{
Clerk clerk;
Consumer(Clerk clerk){
this.clerk = clerk;
}
public void run(){
System.out.println("consumer starts consuming product " );
for(int i=0; i<10; i++){
try{
Thread.sleep( (int)(Math.random()*3000) );
}catch(InterruptedException e){
e.printStackTrace();
}
clerk.getProduct();
// System.out.println("consumer is consuming product "+ i);
}
}
}
class Clerk{
int product = -1;
public synchronized void setProduct(int product){
if( this.product != -1 ){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.product = product;
System.out.println("producer is producing product " + product);
notify();
}
public synchronized void getProduct(){
if( product == -1 ){
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
System.out.println("consumer is consuming product "+ product);
this.product = -1;
notify();
}
}