Java之線程基礎
簡介
本文主要介紹Java編程中線程的基礎知識,包括線程介紹、線程的五大狀態、線程的三種創建方式、線程的同步,最後根據線程的經典問題——生產者消費者模型,給出了實現的源代碼。
通過本文的學習,可以掌握基本的Java多線程編程。
一、線程介紹
若將操作系統中的任務看成進程,那麼任務中的多個執行流可以看做是多個線程。
進程是系統進行資源分配和調度的一個獨立單元。
線程是進程的組成部分,一個進程可以擁有多個線程,一個線程必須有一個父進程。線程可以擁有自己的堆棧、自己的程序計數器和自己的局部變量,但不擁有系統資源,它與父進程的其他線程共享該進程所擁有的全部資源。
二、線程的五大狀態
每個線程都有自己的生命週期,通常將生命週期分爲5個狀態(階段),即新建(New)、就緒(Ready)、運行(Running)、阻塞(Blocked)、死亡(Dead)。
1、新建狀態
當使用new關鍵字新創建一個Thread對象時,該線程就進入了新建狀態。JVM會爲新建的線程分配內存、並初始化線程對象的成員變量,線程對象的執行體還未啓動。
2、就緒狀態
當一個線程對象執行了start()成員方法時,該線程便進入了就緒狀態。JVM開始爲線程對象創建方法調用棧和程序計數器。此時線程對象同樣沒有運行,只代表線程對象可以運行,運行時機取決於JVM的線程調度器。
3、運行狀態
當JVM調度器調度就緒狀態的線程對象時,即執行線程對象的run()方法,那麼該線程對象進入了運行狀態。
對於單個處理器中存在多個線程時,處理器採用時間片輪換的方式來執行各個線程。
4、阻塞狀態
爲了使多個線程都可到執行,那麼處於運行狀態的線程對象不可能一直處於運行狀態。當被中斷時,線程對象從運行狀態變爲了阻塞狀態。
線程進入阻塞狀態的情況:
- 遇到 sleep();
- 遇到 join();
- 遇到 wait();
- 遇到 read()、write();
5、死亡狀態
當線程生命週期結束時,線程對象便進入了死亡狀態,進入死亡狀態的線程不能重進入其它任何狀態,只能靜靜等着JVM垃圾回收機制來回收自己。
三、線程的三種創建方式
JDK中提供了三種創建線程對象的方式,如繼承Thread、實現Runnable、實現Callable。
1、繼承Thread
線程對象類直接繼承Thread類,並重寫Thread類的run()方法。如下所示:
public class ThreadTest extends Thread{
@Override
public void run() {
// 執行體代碼
}
}
該線程對象的運行,如下所示:
public static void main(String[] args) {
Thread thread01 = new ThreadTest();
thread01.start();
}
2、實現Runnable
Runnable是一個接口,線程對象類可以實現該接口,並重寫Runnable接口的run()方法。如下所示:
public class ThreadTest implements Runnable{
@Override
public void run() {
// 執行體代碼
}
}
該線程對象的運行,如下所示:
public static void main(String[] args) {
ThreadTest thread01 = new ThreadTest();
new Thread(thread01, "Thread-name").start();
}
3、實現Callable
實現Callable接口的線程,創建過程如下:
- 1.創建一個線程,創建Callable的實現類Race,並且重寫call方法;
- 2.得到Future對象;
- 3.獲取返回值;
- 4.停止服務;
//返回Interger類型
public class ThreadTest implements Callable<Integer>{
@Override
public Integer call() {
//執行體代碼
return Integer;
}
}
該線程對象的運行,如下所示:
public static void main(String[] args) {
ExecutorService ser = Executors.newFixedThreadPool(2);
ThreadTest threadTest = new ThreadTest();
//獲取值
Future<Integer> result = ser.submit(threadTest);
try {
int numR = result.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
//停止服務
ser.shutdown();
}
四、線程的同步
在多線程的情況下,會存在對公共資源進行訪問的行爲,也就是資源的競爭關係,其中同步就是多線程編程中處理競爭關係的一種方法,它使多線程訪問公共資源時是安全有效的。同步使用synchronized關鍵字,其中又分爲synchronized方法和synchronized塊。
1、synchronized方法
用synchronized關鍵字修飾類的一個方法,如下所示:
public synchronized void seats() {
//需要同步的對象屬性操作代碼
}
用synchronized關鍵字修飾的一個作用塊,如下所示:
synchronized(ThreadTest.class) {
//需要同步的對象屬性操作代碼
}
五、生產者消費者實例
多線程編程中,最經典的問題就是生產者消費者問題:
生產者負責生產產品,消費者購買產品,當庫存爲空的時候,生產者繼續生產,消費者購買數量要小於等於庫存上限,不能出現,生產一次,消費兩次,或者生產了沒有消費的情況。
我的代碼實現如下:
import java.util.ArrayList;
import java.util.List;
public class Cooperation {
public static void main(String[] args) {
// TODO Auto-generated method stub
PipeHandle pipe = new PipeHandle();
Productor productor = new Productor(pipe);
Customer customer = new Customer(pipe);
new Thread(productor,"Productor").start();
new Thread(customer,"Customer").start();
}
}
//生產者
class Productor implements Runnable{
PipeHandle pipe;
public Productor(PipeHandle pipe) {
super();
this.pipe = pipe;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0; i < 100; i++) {
Food food = new Food();
pipe.push(food);
System.out.println("Productor is making " + i +" food");
}
}
}
//消費者
class Customer implements Runnable{
PipeHandle pipe;
public Customer(PipeHandle pipe) {
super();
this.pipe = pipe;
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0; i < 100; i++) {
Food food = pipe.pop();
System.out.println("Coustomer is get "+i+" food");
}
}
}
//商品緩存區管道
class PipeHandle{
Food[] pipe = new Food[10];
int count;
public PipeHandle( ) {
}
public synchronized void push(Food food) {
if(count >= 10) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
pipe[count++] = food;
this.notifyAll();
}
public synchronized Food pop() {
Food food = null;
if(count == 0) {
try {
this.wait();
} catch (InterruptedException e) {
}
}
food = pipe[--count];
this.notifyAll();
return food;
}
}
//商品
class Food{
}