進程、線程 進程(Process) 是程式的運行實例。例如,一個運行的 Eclipse 就是一個進程。進程是程式向操作系統申請資源(如記憶體空間和文件句柄)的基本單位。線程(Thread)是進程中可獨立執行的最小單位。一個進程可以包含多個線程。進程和線程的關係,好比一個營業中的飯店與其正在工作的員 ...
進程、線程
進程(Process) 是程式的運行實例。例如,一個運行的 Eclipse 就是一個進程。進程是程式向操作系統申請資源(如記憶體空間和文件句柄)的基本單位。線程(Thread)是進程中可獨立執行的最小單位。一個進程可以包含多個線程。進程和線程的關係,好比一個營業中的飯店與其正在工作的員工之間的關係。
1.1 線程的創建、啟動與運行
在 Java 中實現多線程主要用兩種手段,一種是繼承 Thread 類,另一種就是實現 Runnable 介面。(當然還有Callable和線程池)。下麵我們就分別來介紹這兩種方式的使用,其他請關註此博客下文。
(1).繼承Thread的類
public class PrimeThread extends Thread{
//線程執行體
@Override
public void run(){
for(int i = 0; i < 100; i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
}
public class TestThread {
public static void main(String[] args) {
//新建一個線程
PrimeThread p1 = new PrimeThread();
//啟動一個線程
p1.start();
PrimeThread p2 = new PrimeThread();
p2.start();
for(int i = 0; i < 100; i++ ){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
}
(2).實現 Runnable介面
public class Ticket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(ticket > 0){
System.out.println(Thread.currentThread().getName() + "=" + --ticket);
}
}
}
public class TestThread2 {
public static void main(String[] args) {
Ticket ticket = new Ticket();
//雖然是實現了Runnable介面 本質上只是實現了線程執行體 啟動工作還是需要Thread類來進行
Thread t1 = new Thread(ticket,"售票視窗一");
t1.start();
Thread t2 = new Thread(ticket,"售票視窗二");
t2.start();
Thread t3 = new Thread(ticket,"售票視窗三");
t3.start();
}
}
兩種實現方式的對比:
1.從面向對象編程角度看:第一種創建方式(繼承Thread類) 是一種基礎繼承的技術,第二種創建方式(以Runnable介面實例為構造器參數直接通過new創建Thread實例)是一種基礎組合的技術。方式二不僅會避免單繼承的尷尬,也會降低類與類之間的耦合性。
2.從對象共用角度看:第二種創建方式意味著多個線程實例可以共用同一個Runnable實例。而第一種方式則需要依賴static關鍵字來完成操作。
1.2 線程的控制
Java的調度方法
同優先順序線程組成先進先出隊列(先到先服務),使用時間片策略
對高優先順序,使用優先調度的搶占式策略
Thread類的相關方法:
- sleep(long millis) : 是 Thread 類中的靜態方法,使當前線程進入睡眠狀態
- join() / join(long millis) : 是一個實例方法,使當前線程進入阻塞狀態
- interrupt() : 中斷阻塞狀態的線程
- isAlive() : 判斷當前線程是否處於存活狀態
- yield() : 線程讓步
public class TestThread3 {
public static void main(String[] args) throws Exception {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 100;i++){
if(i % 2 == 0){
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
},"線程1");
t1.start();
//線程1在 sleep之前就執行完了
t1.sleep(10000);
//join方法 迫使t2 必須等線程1 執行完 才能執行 然而 t1輸出完自己的 睡著了 t2被迫等了10秒
t1.join();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for(int i = 1; i < 100;i++){
if(i % 2 != 0){
System.out.println(Thread.currentThread().getName() + "=" + i);
}
}
}
},"線程2");
t2.start();
}
}
1.3 線程的同步
線程同步:模擬售票程式,實現三個視窗同時售票 100 張 (1.1案例)
問題:當三個視窗同時訪問共用數據時,產生了無序、重覆、超額售票等多線程安全問題
解決:將需要訪問的共用數據“包起來”,視為一個整體,確保一次只能有一個線程執行流訪問該“共用數據”
Java給上述問題提供了幾種相應的解決方法
(1).同步代碼塊
synchronized(同步監視器){
//需要訪問的共用數據
}
同步監視器 : 俗稱“鎖”,可以使用任意對象的引用充當,註意確保多個線程持有同一把鎖(同一個對象)
(2).同步方法
同步方法 : 在方法聲明處加 synchronized. 註意:非靜態同步方法隱式的鎖 ---- this
例如:
public synchronized void show(){}
(3).同步鎖
同步鎖 : Lock 介面
同步代碼塊
public class SafeTicket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
//使用同步代碼塊
synchronized (this) {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" + --ticket);
}
}
}
}
}
同步方法:
public class SafeTicket implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(true){
//使用同步代碼塊
sale();
}
}
public synchronized void sale(){
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" +
--ticket);
}
}
}
同步鎖
public class SafeTicket implements Runnable{
private int ticket = 100;
private Lock l = new ReentrantLock();
@Override
public void run() {
while(true){
l.lock();
try {
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + " 完成售票,餘票:" +
--ticket);
}
} finally {
l.unlock();//釋放鎖
}
}
}
}
死鎖
死鎖 是指兩個或兩個以上的進程在執行過程中,由於競爭資源或者由於彼此通信而造成的一種阻塞的現象,若無外力作用,它們都將無法推進下去。此時稱系統處於 死鎖 狀態或系統產生了 死鎖 ,這些永遠在互相等待的進程稱為 死鎖 進程
public class TestDeadLock {
public static void main(String[] args) {
final StringBuffer s1 = new StringBuffer();
final StringBuffer s2 = new StringBuffer();
new Thread() {
public void run() {
synchronized (s1) {
s2.append("A");
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2) {
s2.append("B");
System.out.print(s1);
System.out.print(s2);
}
}
}
}.start();
new Thread() {
public void run() {
synchronized (s2) {
s2.append("C");
synchronized (s1) {
s1.append("D");
System.out.print(s2);
System.out.print(s1);
}
}
}
}.start();
}
}
1.4 線程的通信
在 java.lang.Object 類中:
wait() : 使當前“同步監視器”上的線程進入等待狀態。同時釋放鎖
notify() / notifyAll() : 喚醒當前“同步監視器”上的(一個/所有)等待狀態的線程
註意:上述方法必須使用在同步中
場景1:使用兩個線程列印 1-100 線程1和線程2交替列印
public class MyThread implements Runnable{
int i = 0;
@Override
public void run() {
while(true){
synchronized (this) {
this.notify();
if(i <= 100){
System.out.println(Thread.currentThread().getName() + "=" + i++);
}
try {
this.wait();
} catch (InterruptedException e) {
}
}
}
}
}
public class TestThread4 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread t1 = new Thread(myThread,"線程1");
Thread t2 = new Thread(myThread,"線程2");
t1.start();
t2.start();
}
}
經典例題:生產者/消費者問題
- 生產者(Productor)將產品交給店員(Clerk),而消費者(Customer)從店員處取走產品,
- 店員一次只能持有固定數量的產品(比如:20),如果生產者試圖生產更多的產品,店員會叫生產者停一下,
- 如果店中有空位放產品了再通知生產者繼續生產;
- 如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。
public class TestProduct {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Productor pro = new Productor(clerk);
Customer cus = new Customer(clerk);
new Thread(pro).start();
new Thread(cus).start();
}
}
// 店員
class Clerk {
private int product;
// 進貨
public synchronized void getProduct() {
if (product >= 20) {
System.out.println("產品已滿!");
try {
wait();
} catch (InterruptedException e) {
}
} else {
System.out.println("生產者生產了第" + ++product + " 個產品");
notifyAll();
}
}
// 賣貨
public synchronized void saleProduct() {
if (product <= 0) {
System.out.println("缺貨!");
try {
wait();
} catch (InterruptedException e) {
}
} else {
System.out.println("消費者消費了第" + --product + " 個產品");
notifyAll();
}
}
}
// 生產者
class Productor implements Runnable {
private Clerk clerk;
public Productor() {
}
public Productor(Clerk clerk) {
this.clerk = clerk;
}
public Clerk getClerk() {
return clerk;
}
public void setClerk(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true) {
clerk.getProduct();
}
}
}
// 消費者
class Customer implements Runnable {
private Clerk clerk;
public Customer() {
}
public Customer(Clerk clerk) {
this.clerk = clerk;
}
public Clerk getClerk() {
return clerk;
}
public void setClerk(Clerk clerk) {
this.clerk = clerk;
}
@Override
public String toString() {
return "Customer [clerk=" + clerk + "]";
}
@Override
public void run() {
while(true){
clerk.saleProduct();
}
}
}