多執行緒
一、實作多執行緒
行程
是正在運行的程式
- 是系統進行資源分配和呼叫的獨立單位
- 每一個行程都有它自己的記憶體空間和系統資源
執行緒
是行程中的單個順序控制流,是一條執行路徑
- 單執行緒:一個行程如果只有一條執行路徑,則稱為單執行緒程式

- 多執行緒:一個行程如果有多條執行路徑,則稱為多執行緒程式

實作多執行緒方式:
- 繼承Thread類
- 定義一個類MyThread繼承Thread類
- 在MyThread類中重寫run()方法
- 創建MyThread類的物件
- 啟動執行緒
注:run()用來封裝執行緒執行的代碼,
run():封裝執行緒執行的代碼,直接呼叫,相當于普通方法的呼叫,
strat():啟動執行緒;然后有JVM呼叫此執行緒的run()方法,
Thread類中的方法:設定和獲取執行緒名稱
- void setName(String name):將此執行緒的名稱更改為引數name
- 也可以通過構造方法來設定
- String getName():回傳此執行緒的名稱
- public static Thread currentThread():回傳對當前正在執行的執行緒物件的參考
public class MyThread extends Thread {
MyThread(){}
MyThread(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++){
System.out.println(getName()+","+i);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
//通過set方法設定執行緒名稱
// MyThread mt = new MyThread();
// MyThread mt2 = new MyThread();
//
// mt.setName("A:");
// mt2.setName("B:");
//// mt.run();//直接呼叫
//// mt2.run();
// mt.start();
// mt2.start();//啟動執行緒,JVM呼叫run方法
//通過構造方法設定:
MyThread mt = new MyThread("A:");
MyThread mt2 = new MyThread("B:");
mt.start();
mt2.start();
//static Thread currenThread()回傳當前正在執行的執行緒物件的參考
System.out.println(Thread.currentThread().getName()); // main
}
}
執行緒調度
兩種執行緒調度模型:
- 分時調度模型:所有執行緒輪流使用CPU的使用權,平均分配每個執行緒占用CPU的時間片,
- 搶占式調度模型:優先讓優先級高的執行緒使用CPU,如果執行緒的優先級相同,那么會隨機選擇一個,優先級高的執行緒獲取的CPU時間片相對多一些,(java),
Thread類中設定和獲取執行緒優先級的方法
- public final int getPriority():回傳此執行緒的優先級,
- public final void setPriority(int newPriority):更改此執行緒的優先級(執行緒優先級的范圍是1-10,默認為5),
執行緒優先級高僅僅表示執行緒獲取CPU時間片的幾率高,但是要在次數比較多,或者多次運行的時候才能看到你想要的效果,
public class ThreadPriority extends Thread{
ThreadPriority(){}
ThreadPriority(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++){
System.out.println(getName()+":"+i);
}
}
}
public class Demo {
public static void main(String[] args) {
ThreadPriority tp = new ThreadPriority("A:");
ThreadPriority tp2 = new ThreadPriority("B:");
ThreadPriority tp3 = new ThreadPriority("C:");
System.out.println(Thread.MIN_PRIORITY);//1
System.out.println(Thread.NORM_PRIORITY);//5
System.out.println(Thread.MAX_PRIORITY); // 10
System.out.println("--------------------------------");
System.out.println(tp.getPriority());//5
System.out.println(tp2.getPriority());//5
System.out.println(tp3.getPriority());//5
System.out.println("-------------------------");
//設定優先級
//public final void setPriority(int newPriority):更改此執行緒的優先級
tp.setPriority(5);
tp2.setPriority(10);
tp3.setPriority(1);
System.out.println(tp.getPriority());//5
System.out.println(tp2.getPriority());//10
System.out.println(tp3.getPriority());//1
System.out.println("-------------------");
tp.start();
tp2.start();
tp3.start();
}
}
執行緒控制
方法:
- static void sleep(long millis):使當前正在執行的執行緒停留(暫停執行)指定的毫秒數
- void join():等待這個執行緒死亡
- void setDaemon(boolean on)將此執行緒標記為守護執行緒,當運行的執行緒是守護執行緒時,JAVA虛擬機將退出
sleep
public class Sleep extends Thread{
Sleep(){}
Sleep(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++){
System.out.println(getName()+i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class SleepDemo {
public static void main(String[] args) {
Sleep s = new Sleep("A");
Sleep s2 = new Sleep("B");
Sleep s3 = new Sleep("C");
s.start();
s2.start();
s3.start();
}
}
join
public class JoinDemo {
public static void main(String[] args) {
//雍正,胤禩是康熙的兒子 ,雍正、胤禩要繼承皇位得康熙退位
// void join():等待這個執行緒死亡-->康熙
Join j = new Join("康熙");
Join j2 = new Join("雍正");
Join j3 = new Join("胤禩");
j.start();
try {
j.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
j2.start();
j3.start();
}
}
public class Join extends Thread{
Join(){}
Join(String name){
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++){
System.out.println(getName()+i);
}
}
}
setDaemon
public class SetDaemon extends Thread{
public SetDaemon() {
}
public SetDaemon(String name) {
super(name);
}
@Override
public void run() {
for (int i = 0; i < 100; i++){
System.out.println(getName()+i);
}
}
}
public class SetDaemonDemo {
public static void main(String[] args) {
//結拜兄弟 同年同月同日死
SetDaemon sd2 = new SetDaemon("虛竹");
SetDaemon sd3 = new SetDaemon("段譽");
//設定為主執行緒
Thread.currentThread().setName("喬峰");
//設定為守護執行緒
sd2.setDaemon(true);
sd3.setDaemon(true);
sd2.start();
sd3.start();
for (int i = 0; i < 10; i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
執行緒生命周期

實作Runnable介面實作多執行緒
- 定義一個類MyRunnable實作Runnable介面
- 實作MyRunnable類中的run()方法
- 創建MyRunnable類的物件
- 創建Thread類的物件,把MyRunnable物件作為構造方法的引數
- 啟動執行緒
多執行緒的兩種方式:
- 繼承Thread類
- 實作Runnable介面
實作Runnable介面:避免了Java單繼承的局限性,適合多個相同程式的代碼去處理同一個資源的情況,把執行緒和程式的代碼、資料有效分離,較好的體現了面向物件的設計思路
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++){
System.out.println(Thread.currentThread().getName()+i);
}
}
}
public class MyRunnableDemo {
public static void main(String[] args) {
//創建MyRunnable類的物件
MyRunnable mr = new MyRunnable();
//創建Thread類的物件,把MyRunnable物件作為構造方法的引數
Thread t1 = new Thread(mr,"A");
Thread t2 = new Thread(mr,"B");
//啟動執行緒
t1.start();
t2.start();
}
}
執行緒同步
賣票
public class SellTicket implements Runnable {
private int Ticket = 100;
@Override
public void run() {
while (true) {
if (Ticket > 0) {
try {
Thread.sleep(100); //模擬賣票間隔時間
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "售賣第" + Ticket + "張票");
Ticket--;
}
}
}
}
public class Demo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t = new Thread(st, "一號視窗:");
Thread t2 = new Thread(st, "二號視窗");
Thread t3 = new Thread(st, "三號視窗");
t.start();
t2.start();
t3.start();
}
}
因為執行緒執行的隨機性
- 出現重票
- 出現負數票
解決
判斷多執行緒程式是否會有資料安全問題的標準
- 是否是多執行緒環境
- 是否有共享資料
- 是否有多條陳述句操作共享資料
解決思路
- 破壞安全環境
實作: - 將多條陳述句操作共享資料的代碼鎖起來,讓任意時刻只能有一個執行緒執行(同步代碼塊)
- 格式:
synchronized(任意物件){
多條陳述句操作共享資料的代碼
}
public class SellTicket implements Runnable {
private int Ticket = 100;
private Object obj = new Object();
@Override
public void run() {
synchronized (obj) {
while (true){
if (Ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "視窗正在售賣第" + Ticket + "張票");
Ticket--;
}
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t = new Thread(st, "一號");
Thread t2 = new Thread(st, "二號");
Thread t3 = new Thread(st, "三號");
t.start();
t2.start();
t3.start();
}
}
優缺點:
- 解決了多執行緒的安全問題
- 當執行緒很多時,因為每個執行緒都會去判斷同步上的鎖,這很耗費資源,降低了程式的運行效率
同步方法
同步方法:將synchronized關鍵字加到方法上
- 格式:
修飾符synchronized回傳值型別方法名(引數)
同步方法的鎖物件: - this
public class SellTicket implements Runnable {
private int Ticket = 100;
private int x = 0;
@Override
public void run() {
while (true) {
synchronized (this) {
if (Ticket > 100) {
if (x % 2 == 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "視窗正在售賣第" + Ticket + "張票");
Ticket--;
} else {
sellTicket();
}
x++;
}
}
}
}
private synchronized void sellTicket() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "視窗正在售賣第" + Ticket + "張票");
Ticket--;
}
}
同步靜態方法:就是把synchonized關鍵字加到靜態方法上
- 格式:
- 修飾符 static synchronized 回傳值型別 方法名(方法引數){}
同步靜態方法的鎖物件
- 修飾符 static synchronized 回傳值型別 方法名(方法引數){}
- 類名+class
執行緒安全的類
- StringBuffer
- 如果要操作少量的資料用 = String
- 單執行緒操作字串緩沖區 下操作大量資料 = StringBuilder
- 多執行緒操作字串緩沖區 下操作大量資料 = StringBuffer
- Vector
- Vector與ArrayList一樣,也是通過陣列實作的,不同的是它支持執行緒的同步,即某一時刻只有一個執行緒能夠寫Vector,避免多執行緒同時寫而引起的不一致性,但實作同步需要很高的花費,因此,訪問它比訪問ArrayList慢,
- Hashtable
Collections類中的synchronizedList(ListList)可以回傳執行緒安全的串列、synchronizedMap(Map<K,V> m)可以回傳執行緒安全的映射、......
Lock鎖(JDK5之后)
- Lock實作提供比使用synchronized方法和陳述句獲得更多廣泛的鎖定操作
- Lock中提供了獲得鎖和釋放鎖的方法
- void lock():獲得鎖
- void unlock():釋放鎖
- Lock是介面不能直接實體化,這里采用它實作的類ReentrantLock來實體化
- ReentrantLock的構造方法
- ReentrantLock():創建一個ReentrantLock的實體
mport java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class SellTicket implements Runnable{
private int Ticket = 100;
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
try {
lock.lock();
if (Ticket > 0){
System.out.println(Thread.currentThread().getName()+"視窗正在售賣第"+Ticket+"張票");
Ticket--;
}
}finally { //用finally來保證必定釋放鎖
lock.unlock();
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
SellTicket st = new SellTicket();
Thread t = new Thread(st, "一號");
Thread t2 = new Thread(st, "二號");
Thread t3 = new Thread(st, "三號");
t.start();
t2.start();
t3.start();
}
}
生產者消費者
- 生產者消費者問題,實際上主要包括了兩類執行緒:
- 一類是生產者執行緒用于生產資料
- 一類是消費者執行緒用于消費資料
- 為了解耦生產者和消費者的關系,通常會采用共享資料區域,就像是一個倉庫
- 生產者生產資料之后直接放置在共享資料中,并不需要關心消費者的行為
- 消費者只需要從共享資料區中去獲取資料,并不需要關心生產者的行為
生產者----->共享資料區域<-------消費者
- 為體現生產和消費程序的等待和喚醒,java就提供了幾個方法供我們使用,這幾個方法在Object類中Object類的等待和喚醒方法:
- void wait()導致當前執行緒等待,知道另一個執行緒呼叫該物件的notify()方法或notifyAll()方法
- void notify()喚醒正在等待物件監視器的單個執行緒
- void notifyAll()喚醒正在等待物件監視器的所有執行緒
/*
生產者消費者案例:
奶箱(Box)定義一個成員變數,表示第x瓶奶,提供存盤牛奶和獲取牛奶的操作
生產者(Producer):實作Runnable介面,實作run()方法,呼叫存盤牛奶的操作
消費者(Customer):實作Runnable介面,實作run()方法,呼叫獲取牛奶操作‘
測驗類(Demo):里面有main方法,main方法中的代碼步驟如下:
1.創建奶箱物件,這是共享資料區域
2.創建生產者物件,把奶箱物件作為構造方法引數傳遞,因為在這個類中要呼叫存盤牛奶操作
3.創建消費者物件,把奶箱物件作為構造方法引數傳遞,因為在這個類中要呼叫存盤牛奶操作
4.創建兩個執行緒物件,分別把生產者物件和消費者物件作為構造方法引數傳遞
5.啟動執行緒
*/
public class Box {
private int milk;
private boolean x = false;//牛奶箱的狀態,沒有牛奶
public synchronized void put(int mike){
//如果有奶等待消費
if (x){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.milk = mike;
System.out.println("員工放入"+milk+"瓶奶");
x = true;
notify();
}
public synchronized void get(){
//如果沒有牛奶等待生產
if (!x){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("用戶拿到"+milk+"瓶奶");
x = false;
notify();
}
}
public class Producer implements Runnable{
Box b;
public Producer(Box b) {
this.b = b;
}
@Override
public void run() {
for (int i = 0; i < 5; i++){
b.put(i+1);
}
}
}
public class Customer implements Runnable{
Box b;
public Customer(Box b) {
this.b = b;
}
@Override
public void run() {
while (true){
b.get();
}
}
}
public class Demo {
public static void main(String[] args) {
Box b = new Box();
Producer p = new Producer(b);
Customer c = new Customer(b);
Thread t = new Thread(p);
Thread t2 = new Thread(c);
t.start();
t2.start();
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/189820.html
標籤:Java
