Java基礎 線程
線程的概述
進程 正在運行的程序 負責這個程序的內存空間分配 代表內存中的執行區域
線程 就是在一個進程中負責一個執行路徑
多線程 就是在一個進程中多個路徑同時執行(例如殺毒軟件同時在做系統優化和垃圾清理)
自定義線程方式(一)
一種方式是將該類聲明爲Thread子類
(1) 該子類重寫Thread的run方法 將自定義線程的任務代碼放到run方法中
(2) 創建自定義線程對象 調用start方法 線程開啓後調用到run方法
(不能直接調用run方法 只有調用start方法纔算開啓一個線程)
public class Demo extends Thread {
@Override
public void run() {
for(int i=0;i<100;i++) {
System.out.println("自定義線程"+i);
}
}
public static void main(String[] args) {
Demo d=new Demo();
d.start();
for(int i=0;i<100;i++) {
System.out.println("main線程"+i);
}
}
}
結果可以看到交替出現
自定義線程方式(二)
自定義一個類去實現Runnable接口
(1)自定義一個類實現Runnable接口
(2)實現Runnable接口的run方法 把自定義線程的任務代碼定義在run方法上
(3)創建Runnable實現類對象
(4)創建Thread對象 把Runnable實現類對象作爲參數傳入
(5)調用Thread的start方法開啓線程
public class Demo implements Runnable {
public static void main(String[] args) {
Demo d=new Demo();
Thread t=new Thread(d,"Mythread");
t.start();
for(int i=0;i<100;i++) {
System.out.println("main線程"+i);
}
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
同樣的我們看到結果的交替出現
這是我們來看一個問題 爲什麼要把Runnable實現類對象作爲參數傳遞進去呢?
我們看兩段Thread源碼中的操作
//這個構造方法中 用target變量記錄了Runnable實現類對象
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
/*這個是Thread類的run方法 可以看到在Thread的run方法中 又調用了
tatget的run方法 這個run方法就是我們自己重寫的run方法*/
public void run() {
if (target != null) {
target.run();
}
}
這樣我們在加深一下理解
public class Demo implements Runnable {
public static void main(String[] args) {
Demo d=new Demo();
Thread t=new Thread(d,"Mythread");
t.start();
for(int i=0;i<100;i++) {
System.out.println("main線程"+i);
}
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
System.out.println(Thread.currentThread());//當前線程對象 t
System.out.println(this);//該函數的調用者對象 d
}
}
this關鍵字指的是該函數的調用者對象 這個run方法目前是由target在調用 而恰恰target就是記錄的Runnable實現類的對象 也就是d對象
線程生命週期
(害 本來想自己畫圖來 可是這個圖太漂亮了 就直接拿過來了)
線程安全問題
我們來模擬一個線程安全問題
現在有這樣一個需求 3個窗口要賣50張票 我們將3個窗口看做3個線程 來模擬實現這個問題
class TicketSales extends Thread{
static int num=50;
public TicketSales(String name) {
super(name);
}
@Override
public void run() {
while(true) {
if(num>0) {
System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
num--;
}else {
System.out.println("售完了");
break;
}
}
}
}
public class Demo {
public static void main(String[] args) {
TicketSales thread1=new TicketSales("窗口1");
TicketSales thread2=new TicketSales("窗口2");
TicketSales thread3=new TicketSales("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
這時候的結果 我們來看 出現了線程安全問題 50號票被賣了3次
我們來分析一下這個原因 可能的情況是當thread1剛剛執行到買票這句話是 還沒來得及做num–時 這時候的cpu執行權被thread3 搶走了 同樣的當thread3執行到買票這句話時 cpu的執行權又被thread2搶走了 這就造成了50號票被賣了3次的情況
同步代碼塊解決線程安全問題
class TicketSales extends Thread{
static int num=50;
//鎖對象必須是多個線程共享的對象 否則沒有意義 用static修飾
static Object o=new Object();
public TicketSales(String name) {
super(name);
}
@Override
public void run() {
while(true) {
synchronized (o) {//o是多個線程共享的鎖對象
if(num>0) {
System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
num--;
}else {
System.out.println("售完了");
break;
}
}
}
}
}
public class Demo {
public static void main(String[] args) {
TicketSales thread1=new TicketSales("窗口1");
TicketSales thread2=new TicketSales("窗口2");
TicketSales thread3=new TicketSales("窗口3");
thread1.start();
thread2.start();
thread3.start();
}
}
這時 利用同步代碼塊解決了線程安全問題
Lock鎖與Synchronized的區別(附加內容)
這篇博客 寫到這裏 又去b戰上搜了一下juc 於是又發現了新大陸 關於線程8鎖 以後一定會加強自己的學習 這裏先簡單記錄一下自己這個小的學習過程
(這裏簡單升級了一下代碼 現學現賣 害)
我們來對比一下lock鎖與synchronized的代碼的不同寫法以及區別
Synchronized方式
public class Demo {
public static void main(String[] args) {
Ticket ticket=new Ticket();
//lambda表達式 (參數)->{業務代碼}
//將資源類丟入到線程當中 這裏大家可以去csdn搜一下lambda創建線程和傳統方式創建線程
new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
}
}
//資源類
class Ticket{
private static int num=50;
public synchronized void sale() {
if(num>0) {
System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
num--;
}
}
}
Lock鎖方式
public class Demo {
public static void main(String[] args) {
Ticket ticket=new Ticket();
new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
new Thread(()->{for(int i=0;i<50;i++)ticket.sale();}).start();
}
}
//資源類
class Ticket{
private static int num=50;
Lock lock=new ReentrantLock();
public void sale() {
lock.lock();//加鎖
try {
if(num>0) {
System.out.println(Thread.currentThread().getName()+"賣出了"+num+"號票");
num--;
}
} catch (Exception e) {
}finally {
lock.unlock();
}
}
}
Synchronized和Lock的區別
Synchronized 是內置的Java 關鍵字 Lock是Java類
Synchronized 無法判斷鎖的狀態 Lock可以判斷是否獲取到了鎖
Synchronized 會自動釋放鎖 Lock要手動釋放鎖
Synchronized 線程1(獲得鎖 阻塞) 線程2(等待 傻傻的等 ) Lock鎖就不一定會等下去(會嘗試獲取鎖)
Synchronized 可重入鎖 不可中斷 非公平鎖 Lock 可重入鎖 可以中斷 默認非公平鎖(可以自己設置)