Java多線程——(1)多線程基礎

1.線程的基本操作

線程共有5個狀態:

NEW(新建狀態)

RUNNABLE(可運行態)

TERMINATED(線程終止)

BLOCKED (阻塞) ,進入同步塊時申請監視器導致的阻塞

WAITING (無限等待)

TIMED_WAITING(有限等待)

1.1新建線程

 常見有兩種創建線程的方法:

1)繼承Thread類重寫run()方法。

2)繼承Runnable接口。在構造時傳入具體實現對象。

如果沒有傳遞target實例,那麼run方法內部就不會執行任何邏輯。

如果傳遞了tartget,那麼run方法便會執行target實例中的run方法。

    public Thread() {
        init(null, null, "Thread-" + nextThreadNum(), 0);
    }    
    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    .....
    /* What will be run. */
    private Runnable target; 

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

要注意的是,如果沒有執行start()方法開啓線程,而是直接在主線程中調用一個線程的run()方法,結果只會在當前線程調用run()方法,而並沒有實質性地開啓新線程。

1.2 終止線程——stop()

 Thread.stop()是一個被棄用的方法。主要原因是線程的突然終止會導致數據的不一致。太過於粗暴。

    @Deprecated
    public final void stop() {
      ....   
    }

1.3 中斷線程——interrupt()

public void Thread.interrupt() //中斷線程
public boolean Thread.isInterrupted() //判斷是否被中斷
public static boolean Thread.interrupted() //判斷是否被中斷,並清除當前中斷狀態
public void run(){
    while(true){
        //doSomething.....
    }
}

t1.interrupt(); //發出中斷命令,而並未做出響應。所以while循環會繼續執行下去

/////////////////////////////////////////////////////////////////////////////////

public void run(){
    while(true){
        if(Thread.currentThread.isInterrupted()){
            System.out.println("Interruted!");
            break;
        }
        //doSomething...
    }
}

t1.interrupt(); //發出中斷命令,while循環中會判斷出現中斷,退出循環

////////////////////////////////////////////////////////////////////////////
但若是doSomething中出現異常時候,要防範拋出異常時,中斷標記位會被清空。要重新中斷
public void run(){
    while(true){
        if(Thread.currentThread.isInterrupted()){
            System.out.println("Interruted!");
            break;
        }
        try{
            Thread.sleep(2000);
        }catch(InterruptedException e){
            System.out.println("Interrupted When Sleep");
            //由於拋出異常後會清除中斷標記,所以再次設置中斷標記,以被if檢測到
            Thread.currentThread().interrupt()
        }
        //doSomething...
    }
}

1.4  掛起(suspend) 和繼續執行(resume) 線程

這兩個操作也是不被推薦使用的。兩個線程無法控制順序時,當resume()在suspend()執行之前就已經執行,所以此後被掛起的線程便再也不能被喚醒了,而suspend()是不會釋放鎖的,導致等待在這個鎖上的其他線程會持續等待下去。

1.5 等待線程結束(join)和謙讓(yeild)

 join() 表示等待前一個線程執行完畢後再執行,也有叫“線程插隊”

public class JoinMain{
	public volatile static int i = 0;
	public static class AddThread extends Thread{
		@Override
		public void run() {
			for(i=0; i < 10000000; i++);
		}
	}
	public static void main(String args[]) throws InterruptedException{
		AddThread at = new AddThread();
		at.start();
		at.join();       //主線程會等待at線程執行完畢後再一起走
		System.out.println(i);
	}
	
}

/////////////////////////////////////////////////
join的本質:
while(isAlive()){
    wait(0);
}
線程執行完畢會調用notifyAll()通知等待的所有線程

yeild會讓出時間片,但不是白給,而是再和別的線程競爭。”再給對方一次競爭機會“。

public final void join() throws InterruptedException
public final synchronized void join(long millis) throws InterruptedException//一般用於測試目的

2. 守護線程&線程優先級

當一個Java應用內,只有守護線程時,JVM就會自然退出。

java 中的線程優先級的範圍是1~10,默認的優先級是5。高優先級的線程更容易在競爭中獲勝。

package Thread;
/**
 * 後臺線程
 * @author liq
 *
 */
public class DamonThread {
	
	class Damon implements Runnable{

		@Override
		public void run() {
			while(true) {
				try {
				Thread.sleep(1000);
					System.out.println(Thread.currentThread().getName()+" is running");
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
		
	}
	
	public static void main(String[] args) {
		Damon damon = new DamonThread().new Damon();
		Thread damonThread = new Thread(damon, "後臺線程");
		damonThread.setPriority(10);  //在start之前設置優先級
		damonThread.setDaemon(true);
		System.out.println("'damonThread' is a Damon Thread?"+damonThread.isDaemon());
		damonThread.start();
		for(int i=0; i<10; i++) {
			try {
				Thread.sleep(1000);
				System.out.println("Main Thrad is Running"+i);
				if (i == 9) {
					System.out.println("Main Thrad is dead");
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			
		}
	}
}

3. 基本的線程同步操作——synchronized關鍵字&wait(),notify()

3.1 synchronized線程同步

-指定加鎖對象:對給定對象加鎖,進入同步代碼前要獲得給定對象的鎖。

-直接作用於實例方法:相當於對當前實例加鎖,進入同步代碼前要獲得當前實例的鎖。

-直接作用於靜態方法:相當於對當前類加鎖,進入同步代碼前要獲得當前類的鎖。

下面進行

/**
 * synchronized代碼塊
 * @author liq
 *
 */
public class SynchronizedBlock {
	
	public static void main(String[] args) {
		Ticket ticketTask = new Ticket();
		new Thread(ticketTask,"售票窗口1").start();
		new Thread(ticketTask,"售票窗口2").start();
		new Thread(ticketTask,"售票窗口3").start();
		new Thread(ticketTask,"售票窗口4").start();
	}
	
}


class Ticket implements Runnable{

	private int ticketsNum = 10; //總共十張票
	Object lock = new Object();
	@Override
	public void run() {
		
		while(true) {
			synchronized (lock) {
				try {
					if (ticketsNum > 0) {
						System.out.println(Thread.currentThread().getName()+"售票,現存:"+ --ticketsNum);
						Thread.sleep(1000);
					}else {
						System.out.println("票售完了!");
						break;
					}
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		}
	}
	
}
鎖靜態方法,就是對當前類加鎖
class Ticket implements Runnable{

	private static int ticketsNum = 10; //總共十張票
	Object lock = new Object();
	@Override
	public void run() {
		sellTickt();
	}
	
	public static synchronized void sellTickt() { //鎖靜態方法,就是對當前類加鎖
		while(true) {
			try {
				if (ticketsNum > 0) {
					System.out.println(Thread.currentThread().getName()+"售票,現存:"+ --ticketsNum);
					Thread.sleep(1000);
				}else {
					System.out.println("票售完了!");
					break;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}

}

 3.2 wait()和notify()實現線程同步

需要注意:

1)wait和notify方法必須在同步代碼塊中執行,即在執行前需要拿到鎖。

2)wait()會釋放鎖。

3)notify也會釋放鎖。

notify()是在同步隊列中隨機喚醒一個線程

notifyAll()是喚醒全部開始競爭鎖。

實現生產者消費者例子:


/**
 * This demon shows the co-operation between two threads of Input and Output
 * @author liq
 *
 */
public class WaitAndNotify {

	public static void main(String[] args) {
		Storage storage = new Storage();
		Input input = new Input(storage);
		Output output = new Output(storage);
		new Thread(input,"input線程").start();
		new Thread(output,"output線程").start();
	}
	
}

//this is Input thread which is used to play a producer role to put a number in storage per second  
class Input implements Runnable{

	Storage storage;
	int num;
	
	public Input(Storage storage) {
		this.storage = storage;
	}

	@Override
	public void run() {
		while(true) {
			try {
				Thread.sleep(1000);
				storage.put(num++);
				System.out.println("producer puts num:"+num);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

//this is Output thread which is used to play a consumer role to get a number in storage per second  
class Output implements Runnable{

	Storage storage;
	
	public Output(Storage storage) {
		this.storage = storage;
	}

	@Override
	public void run() {
		while(true) {
			try {
				Thread.sleep(1000);
				System.out.println("------------------------consumer gets num:"+storage.get());
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
}

//this is a mutual storage of consumer and producer thread to storage number
class Storage{

	private int[] cells = new int[10];
	int size = 0; //實際存儲容量
	int inPos,outPos;
	
	//往cells中放入一個數
	public synchronized void put(int in) {
		try {
				if (size == 10) {    //若存儲已滿,當前線程等待
					this.wait();
				}
				cells[inPos++] = in;
				size++;
				//超過10位置重新從0開始存放
				if (inPos == 10) {
					inPos=0;
				}
				this.notifyAll();   //通知在此同步鎖上等待的其他線程(消費者)開始執行
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	
	//從cells中取出一個數
	public synchronized int get() {
		int result = 0;
		try {
				//Storage 爲空了,線程等待
				if (size == 0) {
					this.wait();       //若存儲已空,當前線程等待。
				}
				
				result = cells[outPos++];
				size--;
				//超過大小,從0開始取出
				if (outPos == 10) {
					outPos = 0;
				}
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			return result;
		}
		
	}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章