文件操作 文件讀寫 語法:open(file, mode, encoding) 參數:file —— 文件所在位置(相對路徑、絕對路徑) mode —— 操作文件的模式 encoding —— 文件的編碼格式 相對路徑:基於目前的路徑獲取 絕對路徑:一個完整的路徑 操作文件的模式:r-讀 w-寫 a ...
線程與進程
進程:是指一個記憶體中運行的應用程式,每個進程都有一個獨立的記憶體空間,一個應用程式可以同時運行多個進程;進程也是程式的一次執行過程,是系統運行程式的基本單位;系統運行一個程式即是一個進程從創建、運行到消亡的過程。
線程:是進程中的一個執行單元,負責當前進程中程式的執行,一個進程中至少有一個線程。一個進程中是可以有多個線程的,這個應用程式也可以稱之為多線程程式。
進程與線程的區別
進程:有獨立的記憶體空間,進程中的數據存放空間(堆空間和棧空間)是獨立的,至少有一個線程。
線程:堆空間是共用的,棧空間是獨立的,線程消耗的資源比進程小的多。
- 因為一個進程中的多個線程是併發運行的,那麼從微觀角度看也是有先後順序的,哪個線程執行完全取決於 CPU 的調度,程式員是干涉不了的。而這也就造成的多線程的隨機性。
- Java 程式的進程裡面至少包含兩個線程,主進程也就是 main()方法線程,另外一個是垃圾回收機制線程。每當使用 java 命令執行一個類時,實際上都會啟動一個 JVM,每一個 JVM 實際上就是在操作系統中啟動了一個線程,java 本身具備了垃圾的收集機制,所以在 Java 運行時至少會啟動兩個線程。
- 由於創建一個線程的開銷比創建一個進程的開銷小的多,那麼我們在開發多任務運行的時候,通常考慮創建多線程,而不是創建多進程。
線程的創建
繼承Thread類
Java使用java.lang.Thread
類代表線程,所有的線程對象都必須是Thread類或其子類的實例。
Java中通過繼承Thread類來創建並啟動多線程的步驟如下:
- 定義一個類繼承Thread類
- 重寫run 方法(線程任務)
- 開啟線程
創建子類對象
調用thread類的start方法
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("sub... "+i);
}
}
}
public static void main(String[] args) {
// 創建自定義的線程對象
MyThread mt = new MyThread();
// 開啟線程
mt.start();
// 一個線程僅可以被啟動一次
// mt.start()
// 主線程執行
for (int i = 0; i < 50; i++) {
System.out.println("main... "+i);
}
}
線程名字的設置和獲取
-
Thread類的方法
String getName()
可以獲取到線程的名字。 -
Thread類的方法
setName(String name)
設置線程的名字。 -
通過Thread類的構造方法
Thread(String name)
也可以設置線程的名字。
public class Demo {
public static void main(String[] args) {
MyThread mt = new MyThread();
//設置線程名字
mt.setName("旺財");
mt.start();
}
}
class MyThread extends Thread{
public void run(){
System.out.println("線程名字:"+super.getName());
}
}
線程是有預設名字的,如果不設置,JVM會賦予線程預設名字Thread-0,Thread-1。
獲取運行main方法線程的名字
Demo類不是Thread的子類,因此不能使用getName()方法獲取。
Thread類定義了靜態方法static Thread currentThread()
獲取到當前正在執行的線程對象。
public static void main(String[] args){
Thread t = Thread.currentThread();
System.out.println(t.getName());
}
實現Runnable介面
java.lang.Runnable
介面類
步驟如下:
- 定義Runnable介面的實現類,並重寫該介面的run()方法,該run()方法的方法體同樣是該線程的線程執行體。
- 創建Runnable實現類的實例,並以此實例作為Thread的target來創建Thread對象,該Thread對象才是真正的線程對象。
- 調用線程對象的start()方法來啟動線程。
public class Demo {
public static void main(String[] args) {
// 創建自定義類對象 線程任務對象
MyRunnable mr = new MyRunnable();
// 創建線程對象
Thread t = new Thread(mr);
t.start();
for (int i = 0; i < 20; i++) {
System.out.println("main " + i);
}
}
}
public class MyRunnable implements Runnable{
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
通過實現Runnable介面,使得該類有了多線程類的特征。run()方法是多線程程式的一個執行目標。所有的多線程代碼都在run方法裡面。Thread類實際上也是實現了Runnable介面的類。
在啟動的多線程的時候,需要先通過Thread類的構造方法Thread(Runnable target) 構造出對象,然後調用Thread對象的start()方法來運行多線程代碼。
實際上所有的多線程代碼都是通過運行Thread的start()方法來運行的。
匿名內部類方式創建線程
使用線程的內匿名內部類方式,可以方便的實現每個線程執行不同的線程任務操作。
public static void main(String[] args) {
// 第一種方式 實際上需要的就是Thread類的子類對象
Thread t = new Thread(){
@Override
public void run() {
System.out.println("方式一 賦值!");
}
};
t.start();
new Thread() {
@Override
public void run() {
System.out.println("方式一 直接調用!");
}
}.start();
// 第二種方式 實際上需要的就是Runnable介面的實現類對象
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("方式二 賦值!");
}
};
new Thread(r).start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("方式二 直接調用!");
}
}).start();
}
Thread和Runnable的區別
如果一個類繼承Thread,則不適合資源共用。但是如果實現了Runable介面的話,則很容易的實現資源共用。
總結:
實現Runnable介面比繼承Thread類所具有的優勢:
- 適合多個相同的程式代碼的線程去共用同一個資源。
- 可以避免java中的單繼承的局限性。
- 增加程式的健壯性,實現解耦操作,代碼可以被多個線程共用,代碼和線程獨立。
Thread類API
睡眠sleep
public static void sleep(long time)
讓當前線程進入到睡眠狀態,到毫秒後自動醒來繼續執行
public class Test{
public static void main(String[] args){
for(int i = 1;i<=5;i++){
Thread.sleep(1000);
System.out.println(i)
}
}
}
設置線程優先順序
線程的切換是由線程調度控制的,我們無法通過代碼來干涉,但是我們通過提高線程的優先順序最大程度的改善線程獲取時間片的幾率
線程的優先順序被劃分為10級,值分別為1-10,其中1最低,10最高.Thread提供了3個常量來表示:
public static final int MIN_PRIORITY //1 最低優先順序
public static final int NORM_PRIORITY //5 預設優先順序
public static final int MAX_PRIORITY //10 最大優先順序
// 更改線程的優先順序
public final void setPriority(int newPriority)
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t2: " + i);
}
}
});
// 設置優先順序
t1.setPriority(Thread.MAX_PRIORITY);
t2.setPriority(Thread.MIN_PRIORITY);
t1.start();
t2.start();
}
join
讓當前線程等待,調用方法的線程進行插隊先執行,執行完畢後,在讓當前線程執行.對其他線程沒有任何影響.
註意 此處的當前線程不是調用方法的線程 而是Thread.currentThread().
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t2: " + i);
}
}
});
t1.start();
// t1.join();
/* t1在此處插隊時
此時 Thread.currentThread() 為 main 線程, 所以 main 線程等待, t1 線程插隊
由於main 線程等待 t2線程還未開啟, 因此t1執行完畢, main和t2搶
*/
t2.start();
t1.join();
/* t1在此處插隊時
此時 Thread.currentThread() 依然為 main 線程, 所以 main 線程等待, t1 線程插隊
而在這之前 t2 線程已經開啟, 因此會出現兩種情況:
1. t2 先執行完 t1 執行完之後 主線程執行
2. t1 先執行完 t2 和 主線程搶
*/
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
面試題
三個線程同時開啟 保證線程一之後執行線程二 再之後執行線程三
class JoinMethodTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 50; i++) {
System.out.println("t2: " + i);
}
}
});
Thread t3 = new Thread(new Runnable() {
@Override
public void run() {
try {
t2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 50; i++) {
System.out.println("t3: " + i);
}
}
});
t1.start();
t2.start();
t3.start();
}
}
線程停止
public final void stop() 直接中斷線程 此方法已過時 不安全
public boolean isInterrupted() 獲取線程是否中斷的狀態 如果中斷返回true 沒中斷返回false
public void interrupt() 中斷線程 此時調用interrupted方法 會返回true
代碼演示:
public class Test02 {
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i <1000000 ; i++) {
System.out.println(i);
//是否有人要中斷線程 如果有返回true 如果沒有返回false
//讓線程中斷更為平滑 可以使用代碼來控制中斷
boolean b = Thread.currentThread().isInterrupted();
if(b){
break;
}
}
}
});
t1.start();
try {
Thread.sleep(2000);
// t1.stop(); //方法已經過時 不安全
t1.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用戶線程與守護線程
java分為兩種線程:用戶線程和守護線程
/*
用戶線程
我們正常寫的代碼都是用戶線程
守護線程
用戶線程存在 守護線程可以執行 用戶線程執行完成 守護線程即使沒有執行完 jvm也會退出
守護線程和用戶線程沒有本質的區別,唯一不同之處就在於虛擬機的退出:
如果用戶線程已經全部退出運行了,只剩下守護線程存在,虛擬機會直接退出.但是只要有用戶線程運行,虛擬機就不會退出.
設置守護線程:
public final void setDaemon(boolean on) on的值為true將線程設置為守護線程,需要在開啟線程之前設置
*/
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1: " + i);
}
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t2: " + i);
}
}
});
/*
由於t2每次休眠50ms, 而t1休眠200ms, 所以t2先執行完
而t1並不是守護線程, 所以t2執行完後, t1繼續執行
*/
// t1.start();
// t2.start();
//將t1設置為守護線程, t2執行完後, t1將不再執行, jvm直接退出
t1.setDaemon(true);
t1.start();
t2.start();
}
線程安全
多條線程操作同一資源時 可能出現數據錯誤 此時線程是不安全的
public class Demo {
public static void main(String[] args) {
SellTickets st = new SellTickets();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
Thread t3 = new Thread(st);
t1.start();
t2.start();
t3.start();
}
}
class SellTickets implements Runnable {
private int i = 20;
public void run() {
while (true) {
if (i > 0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " 賣票 " + i--);
}
}
}
}
/*
Thread-2 賣票 19
Thread-0 賣票 20
Thread-1 賣票 18
Thread-1 賣票 17
Thread-2 賣票 15
Thread-0 賣票 16
Thread-2 賣票 14
Thread-0 賣票 13
Thread-1 賣票 12
Thread-1 賣票 10
Thread-0 賣票 9
Thread-2 賣票 11
Thread-2 賣票 8
Thread-0 賣票 8
Thread-1 賣票 6
Thread-0 賣票 5
Thread-2 賣票 4
Thread-1 賣票 3
Thread-2 賣票 2
Thread-1 賣票 1
Thread-0 賣票 0
Thread-2 賣票 -1
*/
發現程式出現了兩個問題:
- 相同的票數
- 不存在的票,比如0票與-1票,是不存在的。
線程同步
當我們使用多個線程訪問同一資源的時候,且多個線程中對資源有寫的操作,就容易出現線程安全問題。
要解決上述多線程併發訪問一個資源的安全性問題:也就是解決重覆票與不存在票問題,Java中提供了同步機制(synchronized)來解決。
根據案例簡述:
視窗1線程進入操作的時候,視窗2和視窗3線程只能在外等著,視窗1操作結束,視窗1和視窗2和視窗3才有機會進入代碼去執行。
也就是說在某個線程修改共用資源的時候,其他線程不能修改該資源,等待修改完畢同步之後,才能去搶奪CPU資源,完成對應的操作,保證了數據的同步性,解決了線程不安全的現象。
為了保證每個線程都能正常執行原子操作,Java引入了線程同步機制。
同步代碼塊
同步代碼塊:線程操作的共用數據進行同步。synchronized
關鍵字可以用於方法中的某個區塊中,表示只對這個區塊的資源實行互斥訪問。
// 格式
synchronized(同步鎖){
需要同步操作的代碼
}
同步鎖:
同步鎖又稱為對象監視器。同步鎖只是一個概念,可以想象為在對象上標記了一個鎖.
- 鎖對象 可以是任意類型。
- 多個線程對象 要使用同一把鎖。
註意:在任何時候,最多允許一個線程擁有同步鎖,誰拿到鎖就進入代碼塊,其他的線程只能在外等著(BLOCKED)。
使用同步代碼塊解決代碼:
public class Ticket implements Runnable{
private int ticket = 100;
private Object lock = new Object();
/*
* 執行賣票操作
*/
@Override
public void run() {
//每個視窗賣票的操作
//視窗 永遠開啟
while(true){
synchronized (lock) {
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
}
}
}
}
註意:線程運行至同步代碼塊的時候,需要判斷鎖,獲取鎖,出去同步代碼塊後要釋放鎖,增加了很多操作,因此線程安全,程式的運行速度慢!
同步方法
同步方法:當一個方法中的所有代碼,全部是線程操作的共用數據的時候,可以將整個方法進行同步。使用synchronized修飾的方法,就叫做同步方法,保證A線程執行該方法的時候,其他線程只能在方法外等著。
// 格式
public synchronized void method(){
// 可能會產生線程安全問題的代碼
}
使用同步方法代碼如下:
public class Ticket implements Runnable{
private int ticket = 100;
/*
* 執行賣票操作
*/
@Override
public void run() {
//每個視窗賣票的操作
//視窗 永遠開啟
while(true){
sellTicket();
}
}
/*
* 鎖對象 是 誰調用這個方法 就是誰
* 隱含 鎖對象 就是 this
*
*/
public synchronized void sellTicket(){
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
}
}
同步鎖是誰?
-
對於非static方法,同步鎖就是this。
-
對於static方法,我們使用當前方法所在類的位元組碼對象(類名.class)。
Lock鎖
java.util.concurrent.locks.Lock
機制提供了比synchronized代碼塊和synchronized方法更廣泛的鎖定操作,同步代碼塊/同步方法具有的功能Lock都有,除此之外更強大,更體現面向對象。
Lock鎖也稱同步鎖,加鎖與釋放鎖方法化了,如下:
public void lock()
:加鎖。public void unlock()
:釋放鎖。
使用如下:
public class Ticket implements Runnable{
private int ticket = 100;
Lock lock = new ReentrantLock();
/*
* 執行賣票操作
*/
@Override
public void run() {
//每個視窗賣票的操作
//視窗 永遠開啟
while(true){
lock.lock();
if(ticket>0){//有票 可以賣
//出票操作
//使用sleep模擬一下出票時間
try {
Thread.sleep(50);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
//獲取當前線程對象的名字
String name = Thread.currentThread().getName();
System.out.println(name+"正在賣:"+ticket--);
}
lock.unlock();
}
}
}
線程狀態
這裡先列出各個線程狀態發生的條件,下麵將會對每種狀態進行詳細解析。
線程狀態 | 導致狀態發生條件 |
---|---|
NEW(新建) | 線程剛被創建,但是並未啟動。還沒調用start方法。 |
Runnable(可運行) | 線程可以在java虛擬機中運行的狀態,可能正在運行自己代碼,也可能沒有,這取決於操作系統處理器。 |
Blocked(鎖阻塞) | 當一個線程試圖獲取一個對象鎖,而該對象鎖被其他的線程持有,則該線程進入Blocked狀態;當該線程持有鎖時,該線程將變成Runnable狀態。 |
Waiting(無限等待) | 一個線程在等待另一個線程執行一個(喚醒)動作時,該線程進入Waiting狀態。進入這個狀態後是不能自動喚醒的,必須等待另一個線程調用notify或者notifyAll方法才能夠喚醒。 |
Timed Waiting(計時等待) | 同waiting狀態,有幾個方法有超時參數,調用他們將進入Timed Waiting狀態。這一狀態將一直保持到超時期滿或者接收到喚醒通知。帶有超時參數的常用方法有Thread.sleep 、Object.wait。 |
Teminated(被終止) | 因為run方法正常退出而死亡,或者因為沒有捕獲的異常終止了run方法而死亡。 |
等待和喚醒
Object類的方法
void wait()
// 在其他線程對用此對象的notify()方法或notifyAll方法前, 導致當前線程等待
void wait(long time)
// 當前線程等待 time 毫秒
void notify()
// 喚醒在此對象監視器上等待的單個線程
void notifyAll()
// 喚醒在此對象監視器上等待的所有線程
public class RelatedInObject {
public static void main(String[] args) throws InterruptedException {
Object o = new Object();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (o) {
System.out.println("開始等待");
try {
o.wait();
// 等待3000毫秒後
// o.wait(3000);
System.out.println("已經被喚醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("end");
}
}).start();
Thread.sleep(5000);
synchronized (o) {
System.out.println("開始喚醒");
o.notify();
}
}
}