多線程的基本概念
線程指進程中的一個執行場景,也就是執行流程,那麼進程和線程有什麼區別呢?
- 每個進程是一個應用程序,都有獨立的內存空間
- 同一個進程中的線程共享其進程中的內存和資源(共享的內存是堆內存和方法區內存,棧內存不共享,每個線程有自己的。)
什麼是進程?
一個進程對應一個應用程序。例如:在 windows 操作系統啓動 Word 就表示啓動了一個
進程。在 java 的開發環境下啓動 JVM,就表示啓動了一個進程。現代的計算機都是支持多
進程的,在同一個操作系統中,可以同時啓動多個進程。
多進程有什麼作用?
單進程計算機只能做一件事情。
玩電腦,一邊玩遊戲(遊戲進程)一邊聽音樂(音樂進程)。
對於單核計算機來講,在同一個時間點上,遊戲進程和音樂進程是同時在運行嗎?不是。
因爲計算機的 CPU 只能在某個時間點上做一件事。由於計算機將在“遊戲進程”和“音樂
進程”之間頻繁的切換執行,切換速度極高,人類感覺遊戲和音樂在同時進行。
多進程的作用不是提高執行速度,而是提高 CPU 的使用率。
進程和進程之間的內存是獨立的。
什麼是線程?
線程是一個進程中的執行場景。一個進程可以啓動多個線程。
多線程有什麼作用?
多線程不是爲了提高執行速度,而是提高應用程序的使用率。
線程和線程共享“堆內存和方法區內存”,棧內存是獨立的,一個線程一個棧。
可以給現實世界中的人類一種錯覺:感覺多個線程在同時併發執行。
java 程序的運行原理?
java 命令會啓動 java 虛擬機,啓動 JVM,等於啓動了一個應用程序,表示啓動了一個進程。該進程會自動啓動一個“主線程”,然後主線程去調用某個類的 main 方法。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。
線程生命週期
線程是一個進程中的執行場景,一個進程可以啓動多個線程
新建:採用 new 語句創建完成
就緒:執行 start 後
運行:佔用 CPU 時間
阻塞:執行了 wait 語句、執行了 sleep 語句和等待某個對象鎖,等待輸入的場合
終止:退出 run()方法
多線程不是爲了提高執行速度,而是提高應用程序的使用率.
線程和線程共享”堆內存和方法區內存”.棧內存是獨立的,一個線程一個棧.
可以給現實世界中的人類一種錯覺 : 感覺多線程在同時併發執行.
很多人都對其中的一些概念不夠明確,如同步、併發等等,讓我們先建立一個數據字典,以免產生誤會。
- 多線程:指的是這個程序(一個進程)運行時產生了不止一個線程
- 並行與併發:
- 並行:多個cpu實例或者多臺機器同時執行一段處理邏輯,是真正的同時。
- 併發:通過cpu調度算法,讓用戶看上去同時執行,實際上從cpu操作層面不是真正的同時。併發往往在場景中有公用的資源,那麼針對這個公用的資源往往產生瓶頸,我們會用TPS或者QPS來反應這個系統的處理能力。
線程安全:經常用來描繪一段代碼。指在併發的情況之下,該代碼經過多線程使用,線程的調度順序不影響任何結果。這個時候使用多線程,我們只需要關注系統的內存,cpu是不是夠用即可。反過來,線程不安全就意味着線程的調度順序會影響最終結果,如不加事務的轉賬代碼:
void transferMoney(User from, User to, float amount){
to.setMoney(to.getBalance() + amount);
from.setMoney(from.getBalance() - amount);
}
同步:Java中的同步指的是通過人爲的控制和調度,保證共享資源的多線程訪問成爲線程安全,來保證結果的準確。如上面的代碼簡單加入@synchronized關鍵字。在保證結果準確的同時,提高性能,纔是優秀的程序。線程安全的優先級高於性能。
Java命令會啓動Java虛擬機,啓動JVM,等於啓動了一個應用程序,表示啓動了一個進程,該進程會自動啓動一個”主線程”,
然後主線程去調用某個類的main()方法,所以main()方法運行在主線程中.
線程的調度與控制
線程的調度模型分爲: 分時調度模型和搶佔式調度模型,Java使用搶佔式調度模型
通常我們的計算機只有一個 CPU,CPU 在某一個時刻只能執行一條指令,線程只有得到 CPU時間片,也就是使用權,纔可以執行指令。在單 CPU 的機器上線程不是並行運行的,只有在多個 CPU 上線程纔可以並行運行。Java 虛擬機要負責線程的調度,取得 CPU 的使用權,目前有兩種調度模型:分時調度模型和搶佔式調度模型,Java 使用搶佔式調度模型。分時調度模型:所有線程輪流使用 CPU 的使用權,平均分配每個線程佔用 CPU 的時間片搶佔式調度模型:優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。
- 分時調度模型: 所有線程輪流使用CPU的使用權,平均分配每個線程佔用CPU的時間片
- 搶佔式調度模型: 優先讓優先級高的線程使用CPU,如果線程的優先級相同,那麼會隨機選擇一個,優先級高的線程獲取的CPU時間片相對多一些.
public class ThreadTest {
public static void main(String[] args) {
ThreadTest1();
ThreadTest2();
ThreadTest3();
ThreadTest4();
ThreadTest5();
}
/**
* 三個方法: 獲取當前線程對象:Thread.currentThread(); 給線程起名: t1.setName("t1"); 獲取線程的名字: t.getName();
*/
private static void ThreadTest1() {
Thread t = Thread.currentThread();// t保存的內存地址指向的線程爲"主線程"
System.out.println(t.getId());
Thread t1 = new Thread(new Processor1());
// 給線程起名
t1.setName("t1");
t1.start();
Thread t2 = new Thread(new Processor1());
t2.setName("t2");
t2.start();
}
/**
* 線程優先級高的獲取的CPU時間片相對多一些 優先級: 1-10 最低: 1 最高: 10 默認: 5
*/
private static void ThreadTest2() {
Thread t1 = new Processor2();
Thread t2 = new Processor2();
t1.setName("t1");
t2.setName("t2");
System.out.println(t1.getPriority());
System.out.println(t2.getPriority());
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
/**
* 1.Thread.sleep(毫秒); 2.sleep方法是一個靜態方法 3.該方法的作用: 阻塞當前線程,騰出CPU,讓給其它線程
*/
private static void ThreadTest3() {
Thread t = new Thread(new Processor3());
t.start();
for (int i = 0; i < 11; i++) {
System.out.println(Thread.currentThread().getName() + "========>" + i);
try {
t.sleep(5000);// 等同於Thread.sleep(5000);阻塞的還是當前線程,和t線程無關.
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 某線程正在休眠,如何打斷它的休眠 以下方式依靠的是異常處理機制
*/
private static void ThreadTest4() {
try {
Thread t = new Thread(new Processor4());
t.start();
Thread.sleep(5000);// 睡5s
t.interrupt();// 打斷Thread的睡眠
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 如何正確的更好的終止一個正在執行的線程 需求:線程啓動5s之後終止.
*/
private static void ThreadTest5() {
Processor5 p = new Processor5();
Thread t = new Thread(p);
t.start();
// 5s之後終止
try {
Thread.sleep(5000);
p.isRun = false;
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
class Processor1 implements Runnable {
@Override
public void run() {
Thread t = Thread.currentThread();// t保存的內存地址指向的線程爲"t1線程對象"
System.out.println(t.getName());
System.out.println(t.getId());
}
}
class Processor2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "----------->" + i);
}
}
}
class Processor3 implements Runnable {
/**
* Thread中的run方法不能拋出異常,所以重寫runn方法之後,在run方法的聲明位置上不能使用throws 所以run方法中的異常只能try...catch...
*/
@Override
public void run() {
for (int i = 0; i < 11; i++) {
System.out.println(Thread.currentThread().getName() + "========>" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Processor4 implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000000000);
System.out.println("能否執行這裏");
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 11; i++) {
System.out.println(Thread.currentThread().getName() + "========>" + i);
}
}
}
class Processor5 implements Runnable {
boolean isRun = true;
@Override
public void run() {
for (int i = 0; i < 11; i++) {
if (isRun) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "========>" + i);
}
}
}
}
本文所有代碼已經完整附上,如果想查看運行結果,可以把代碼直接複製到開發工具運行查看
線程優先級
線 程 優 先 級 主 要 分 三 種 : MAX_PRIORITY( 最 高 級 );MIN_PRIORITY ( 最 低 級 )NORM_PRIORITY(標準)默認
//設置線程的優先級,線程啓動後不能再次設置優先級
//必須在啓動前設置優先級
//設置最高優先級
t1.setPriority(Thread.MAX_PRIORITY);
sleep
sleep 設置休眠的時間,單位毫秒,當一個線程遇到 sleep 的時候,就會睡眠,進入到阻塞狀態,放棄 CPU,騰出 cpu 時間片,給其他線程用,所以在開發中通常我們會這樣做,使其他的線程能夠取得 CPU 時間片,當睡眠時間到達了,線程會進入可運行狀態,得到 CPU 時間片繼續執行,如果線程在睡眠狀態被中斷了,將會拋出 IterruptedException
public class ThreadTest05 {
public static void main(String[] args) {
Runnable r1 = new Processor();
Thread t1 = new Thread(r1, "t1");
t1.start();
Thread t2 = new Thread(r1, "t2");
t2.start();
}
}
class Processor implements Runnable {
public void run() {
for (int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + "," + i);
if (i % 10 == 0) {
try {
//睡眠 100 毫秒,主要是放棄 CPU 的使用,將 CPU 時間片交給其他線程使用
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
停止一個線程
- 如果我們的線程正在睡眠,可以採用 interrupt 進行中斷
- 通常定義一個標記,來判斷標記的狀態停止線程的執行
yield
它與 sleep()類似,只是不能由用戶指定暫停多長時間,並且 yield()方法只能讓同優先級的線程有執行的機會,採用 yieid 可以將 CPU 的使用權讓給同一個優先級的線程
join
當前線程可以調用另一個線程的 join 方法,調用後當前線程會被阻塞不再執行,直到被調用的線程執行完畢,當前線程纔會執行
synchronized
線程同步,指某一個時刻,指允許一個線程來訪問共享資源,線程同步其實是對對象加鎖,如果對象中的方法都是同步方法,那麼某一時刻只能執行一個方法,採用線程同步解決以上的問題,我們只要保證線程一操作 s 時,線程 2 不允許操作即可,只有線程一使用完成 s 後,再讓線程二來使用 s 變量
- 異步編程模型 : t1線程執行t1的,t2線程執行t2的,兩個線程之間誰也不等誰.
- 同步編程模型 : t1線程和t2線程執行,t2線程必須等t1線程執行結束之後,t2線程才能執行,這是同步編程模型.
-
- 什麼時候要用同步呢?爲什麼要引入線程同步呢?
- 1.爲了數據的安全,儘管應用程序的使用率降低,但是爲了保證數據是安全的,必須加入線程同步機制.
- 線程同步機制使程序變成了(等同)單線程.
- 2.什麼條件下要使用線程同步?
- 第一: 必須是多線程環境
- 第二: 多線程環境共享同一個數據.
- 第三: 共享的數據涉及到修改操作.
//synchronized 是對對象加鎖
//採用 synchronized 同步最好只同步有線程安全的代碼
//可以優先考慮使用 synchronized 同步塊
//因爲同步的代碼越多,執行的時間就會越長,其他線程等待的時間就會越長
//影響效率
public synchronized void run() {
//使用同步塊
synchronized (this) {
for (int i=0; i<10; i++) {
s+=i;
}
System.out.println(Thread.currentThread().getName() + ", s=" + s);
s = 0;
}
public class SynchronizedTest {
public static void main(String[] args) {
SynchronizeTest1();
}
private static void SynchronizeTest1() {
Account account=new Account("Actno-001",5000.0);
Thread t1=new Thread(new Processor(account));
Thread t2=new Thread(new Processor(account));
t1.start();
t2.start();
}
}
/**
* 取款線程
*/
class Processor implements Runnable{
Account act;
Processor(Account act){
this.act=act;
}
@Override
public void run() {
act.withdraw(1000.0);
System.out.println("取款1000.0成功,餘額: "+act.getBalance());
}
}
class Account {
private String actno;
private double balance;
public Account() {
super();
}
public Account(String actno, double balance) {
super();
this.actno = actno;
this.balance = balance;
}
public String getActno() {
return actno;
}
public void setActno(String actno) {
this.actno = actno;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
/**
* 對外提供一個取款的方法 對當前賬戶進行取款操作
*/
public void withdraw(double money) {
//把需要同步的代碼,放到同步語句塊中.
//遇到synchronized就找鎖,找到就執行,找不到就等
/**
* 原理: t1線程和t2線程
* t1線程執行到此處,遇到了synchronized關鍵字,就會去找this的對象鎖,
* 如果找到this對象鎖,則進入同步語句塊中執行程序,當同步語句塊中的代碼執行結束之後,
* t1線程歸還this的對象鎖.
*
* 在t1線程執行同步語句塊的過程中,如果t2線程也過來執行以下代碼,也遇到synchronized關鍵字,
* 所以也去找this對象鎖,但是該對象鎖被t1線程持有,只能在這等待this對象的歸還.
*
* synchronized關鍵字添加到成員方法上,線程拿走的也是this的對象鎖.
*
*/
synchronized (this) {
double after = balance - money;
try {
//延遲
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//更新
this.setBalance(after);
}
}
}
public class SynchronizedTest2 {
public static void main(String[] args) throws InterruptedException {
MyClass mc1=new MyClass();
MyClass mc2=new MyClass();
Thread t1=new Thread(new Runnable1(mc1));
Thread t2=new Thread(new Runnable1(mc2));
t1.setName("t1");
t2.setName("t2");
t1.start();
//延遲,保證t1先執行
Thread.sleep(1000);
t2.start();
}
}
class Runnable1 implements Runnable{
MyClass mc;
Runnable1(MyClass mc){
this.mc=mc;
}
@Override
public void run() {
if("t1".equals(Thread.currentThread().getName())){
MyClass.m1();//因爲是靜態方法,用的還是類鎖,和對象鎖無關
}
if("t2".equals(Thread.currentThread().getName())){
MyClass.m2();
}
}
}
class MyClass{
//synchronized添加到靜態方法上,線程執行此方法的時候會找類鎖,類鎖只有一把
public synchronized static void m1(){
try {
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("m1()............");
}
/**
* m2()不會等m1結束,因爲該方法沒有被synchronized修飾
*/
// public static void m2(){
// System.out.println("m2()........");
// }
/**
* m2方法等m1結束之後才能執行,該方法有synchronized
* 線程執行該方法需要"類鎖",而類鎖只有一個.
*/
public synchronized static void m2(){
System.out.println("m2()........");
}
}
死鎖
public class DeadLock {
public static void main(String[] args) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new Thread(new T1(o1, o2));
Thread t2 = new Thread(new T2(o1, o2));
t1.start();
t2.start();
}
}
class T1 implements Runnable {
Object o1;
Object o2;
T1(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o1) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
}
}
}
}
class T2 implements Runnable {
Object o1;
Object o2;
T2(Object o1, Object o2) {
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
}
}
}
}
守護線程
從線程分類上可以分爲:用戶線程(以上講的都是用戶線程),另一個是守護線程。守護線程是這樣的,所有的用戶線程結束生命週期,守護線程纔會結束生命週期,只要有一個用戶線程存在,那麼守護線程就不會結束,例如 java 中著名的垃圾回收器就是一個守護線程,只有應用程序中所有的線程結束,它纔會結束。
- 其它所有的用戶線程結束,則守護線程退出!
- 守護線程一般都是無限執行的.
public class DaemonThread {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable2());
t1.setName("t1");
// 將t1這個用戶線程修改成守護線程.在線程沒有啓動時可以修改以下參數
t1.setDaemon(true);
t1.start();
// 主線程
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "----->" + i);
Thread.sleep(1000);
}
}
}
class Runnable2 implements Runnable {
@Override
public void run() {
int i = 0;
while (true) {
i++;
System.out.println(Thread.currentThread().getName() + "-------->" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
設置爲守護線程後,當主線程結束後,守護線程並沒有把所有的數據輸出完就結束了,也即是說守護線程是爲用戶線程服務的,當用戶線程全部結束,守護線程會自動結束
Timer.schedule()
/**
* 關於定時器的應用 作用: 每隔一段固定的時間執行一段代碼
*/
public class TimerTest {
public static void main(String[] args) throws ParseException {
// 1.創建定時器
Timer t = new Timer();
// 2.指定定時任務
t.schedule(new LogTimerTask(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").parse("2017-06-29 14:24:00 000"), 10 * 1000);
}
}
// 指定任務
class LogTimerTask extends TimerTask {
@Override
public void run() {
System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
}
}