多線程 一、實現多線程 進程 是正在運行的程式 是系統進行資源分配和調用的獨立單位 每一個進程都有它自己的記憶體空間和系統資源 線程 是進程中的單個順序控制流,是一條執行路徑 單線程:一個進程如果只有一條執行路徑,則稱為單線程程式 多線程:一個進程如果有多條執行路徑,則稱為多線程程式 實現多線程方式: ...
多線程
一、實現多線程
進程
是正在運行的程式
- 是系統進行資源分配和調用的獨立單位
- 每一個進程都有它自己的記憶體空間和系統資源
線程
是進程中的單個順序控制流,是一條執行路徑
- 單線程:一個進程如果只有一條執行路徑,則稱為單線程程式
- 多線程:一個進程如果有多條執行路徑,則稱為多線程程式
實現多線程方式:
- 繼承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();
}
}