之前,我們討論了多線程的創建、實現與信息共享通過static變量和同一個繼承Runnable接口的類的成員變量,實現了由粗粒度到細粒度之間的轉變,加強了線程之間的交流,但這種同步的意義不高。
線程狀態:
- NEW剛創建(new)
- RUNNABLE 就緒態(start)
- RUNNING 運行中(run)
- BLOCK 阻塞(sleep)
- TERMINATED 結束
線程阻塞和喚醒的方法: - sleep,時間一到,自己會醒來
- wait/notify/ notifyAll 等待,需要別人來喚醒
- join,等待另一個線程結束
- interrupt,向另外一個線程發送中斷信號,該線程收到信息,會觸發InterruptException(可接觸阻塞),並進行下一步處理。
下面,先來看一個例子,生產者和消費者問題。
package product;
/*
* 經典生產者與消費者問題
* 生產者不斷的往倉庫中存放產品,消費者從倉庫中消費產品
* 其中生產者和消費者都可以有若干個
* 倉庫規則:容量有限,庫滿時不能存放,庫空時不能取產品
*/
public class ProductTest
{
public static void main(String[] args)
{
// TODO 自動生成的方法存根
Storage storage = new Storage();
Thread consumer1 = new Thread(new Consumer(storage));
consumer1.setName("消費者1");
Thread consumer2 = new Thread(new Consumer(storage));
consumer2.setName("消費者2");
Thread producer1 = new Thread(new Producer(storage));
producer1.setName("生產者1");
Thread producer2 = new Thread(new Producer(storage));
producer2.setName("生產者2");
producer1.start();
producer2.start();
try
{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
consumer1.start();
consumer2.start();
}
}
package product;
public class Storage
{
private Product[] products = new Product[10];
private int top = 0;
public synchronized void push (Product product)
{
while(top == products.length)
{
try
{
System.out.println("producer wait");
wait();
}catch(Exception e)
{
e.printStackTrace();
}
}
products[top++] = product;
System.out.println(Thread.currentThread().getName()+" 生產了產品 "+product);
System.out.println("producer notifyAll");
notifyAll();
}
public synchronized Product pop()
{
while(top == 0)
{
try
{
System.out.println("consumer wait");
wait();
}catch (Exception e)
{
e.printStackTrace();
}
}
top--;
Product p = new Product(products[top].getId(),products[top].getName());
products[top] = null;
System.out.println(Thread.currentThread().getName()+" 消費了產品 "+p);
System.out.println("comsumer notifyAll");
notifyAll();
return p;
}
}
package product;
public class Product
{
private int id;
private String name;
public Product(int id,String name)
{
this.id = id;
this.name = name;
}
public String toString()
{
return "(產品ID:"+this.id+"產品名稱:"+this.name+")";
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package product;
import java.util.Random;
public class Producer implements Runnable
{
private Storage storage;
public Producer(Storage storage)
{
this.storage = storage;
}
@Override
public void run()
{
int i=0;
Random r = new Random();
while(i<10)
{
i++;
Product product = new Product(i,"電話"+r.nextInt(100));
storage.push(product);
}
}
}
package product;
public class Consumer implements Runnable
{
private Storage storage;
public Consumer(Storage storage)
{
this.storage = storage;
}
public void run()
{
int i=0;
while(i<10)
{
i++;
storage.pop();
try
{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
}
}
}
從該例子可以看出,當某個線程被動的暫停和終止時,只能依靠別的線程類拯救自己當前的狀態,這樣很危險,比如被暫停的線程正打開某個文件,還未關閉等。下面通過一個例子,來看一下被動的終止和主動的終止的區別。
public class InterruptTest
{
public static void main(String[] args) throws InterruptedException
{
// TODO 自動生成的方法存根
TestThread1 t1 = new TestThread1();
TestThread2 t2 = new TestThread2();
t1.start();
t2.start();
Thread.sleep(2000);
t1.interrupt(); //讓t1中斷
t2.flag = false;
System.out.println("main thread is exiting");
}
}
class TestThread1 extends Thread
{
public void run()
{
while(!interrupted())
{
System.out.println("test thread1 is running");
try
{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
break;
}
}
System.out.println("test thread1 is exiting");
}
}
class TestThread2 extends Thread
{
public volatile boolean flag = true;
public void run()
{
while(flag)
{
System.out.println("test thread2 is running");
try
{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
}
System.out.println("test thread2 is exiting");
}
}
當interrupted時,若該線程處於sleep狀態時,此時終止將會爆發異常。
線程的死鎖
public class ThreadDemo5
{
public static Integer r1 = 1;
public static Integer r2 = 2;
public static void main(String[] args)
{
// TODO 自動生成的方法存根
TestThread51 t1 = new TestThread51();
t1.start();
TestThread52 t2 = new TestThread52();
t2.start();
}
}
class TestThread51 extends Thread
{
public void run()
{
synchronized (ThreadDemo5.r1)
{
System.out.println("TestThread51 already r1");
try
{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
synchronized(ThreadDemo5.r2)
{
System.out.println("TestThread51 is running");
}
}
}
}
class TestThread52 extends Thread
{
public void run()
{
synchronized (ThreadDemo5.r2)
{
System.out.println("TestThread52 already r2");
try
{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
synchronized(ThreadDemo5.r1)
{
System.out.println("TestThread52 is running");
}
}
}
}
守護(後臺)線程
普通線程的結束,是run方法運行結束。
守護線程的結束,是run方法運行結束,或main函數結束
public class ThreadDemo4
{
public static void main(String[] args) throws InterruptedException
{
// TODO 自動生成的方法存根
TestThread4 t = new TestThread4();
t.setDaemon(true);//設置爲守護線程
t.start();
Thread.sleep(2000);
System.out.println("main thread is exiting");
}
}
class TestThread4 extends Thread
{
public void run()
{
while(true)
{
System.out.println(Thread.currentThread().getName()+" is running ");
try
{
Thread.sleep(1000);
}catch(Exception e)
{
e.printStackTrace();
}
}
}
}
main函數結束後,子線程也結束了。可以看出,守護線程和main線程同呼吸,共命運。注意:不要在守護線程中打開文件或者連接數據庫。