線程

1.程序、進(jìn)程、線程
進(jìn)程可以細(xì)化為多個(gè)線程;每個(gè)線程擁有自己獨(dú)立的:棧、程序計(jì)數(shù)器;多個(gè)線程共享一個(gè)進(jìn)程中的結(jié)構(gòu):方法區(qū)、堆
2.單核cpu、多核cpu
單核cpu:是一種假的多線程;多核cpu,能夠更好的發(fā)揮多線程的效率
一個(gè)java.exe至少有3個(gè)線程:main()主線程、gc()垃圾回收線程、異常處理線程
3.并行、并發(fā)
并行:多個(gè)cpu同時(shí)執(zhí)行多個(gè)任務(wù)
并發(fā):一個(gè)cpu同時(shí)執(zhí)行多個(gè)任務(wù)

4.創(chuàng)建線程的方式

(1)創(chuàng)建線程的方式一

/*
*創(chuàng)建多線程步驟:
* 1.繼承Thread類
* 2.重寫THread類的run方法--》將線程執(zhí)行的操作聲明在run()中
* 3.創(chuàng)建Thread的子類的對(duì)象
* 4.使用子類調(diào)用start()方法
*
* */
class MyThread extends Thread{
    @Override
    public void run() {
        for(int i = 0;i < 20;i++){
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName() +"...."+ i);
            }
        }
    }
}
public static void main(String[] args) {
        MyThread thread_01 = new MyThread();
        //thread_01線程中執(zhí)行
        thread_01.start();
}

使用匿名類創(chuàng)建多個(gè)線程

public static void main(String[] args) {
        //new Thread().start();//此種方式并不是創(chuàng)建匿名的thread對(duì)象,而是開啟Thread這個(gè)父類線程
        new Thread(){//創(chuàng)建匿名子類線程1
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "....");
            }
        }.start();
        new Thread(){//創(chuàng)建匿名子類線程2
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + "....");
            }
        }.start();
    }
Thread中常用的方法

1.設(shè)置name
方式1.setName()

MyThread thread_01 = new MyThread();
thread_01.setName("xiancheng");

方式2.構(gòu)造器中調(diào)用父類構(gòu)造器

MyThread thread_01 = new MyThread("xian");
class MyThread extends Thread{
    public MyThread(String name){
        super(name);
    }

2.yield:線程讓步,即釋放當(dāng)前cpu執(zhí)行權(quán)
3.join:在線程a中調(diào)用線程b的join(),此時(shí)線程a就進(jìn)入阻塞狀態(tài),直到線程b完全執(zhí)行完以后,線程a才結(jié)束阻塞狀態(tài)
4.sleep(time):讓當(dāng)前線程睡眠指定毫秒,在指定時(shí)間內(nèi),當(dāng)前線程是阻塞狀態(tài)
5.isAlive:判斷當(dāng)前線程是否存活

線程的調(diào)度

線程優(yōu)先級(jí):
1.獲取線程優(yōu)先級(jí):thread_01.getPriority();
2.設(shè)置線程優(yōu)先級(jí): MAX_PRIORITY=10;MIN_PRIORITY=1,NORM_PRIORITY=5(默認(rèn)值)
thread_01.setPriority(Thread.MAX_PRIORITY);
同優(yōu)先級(jí)線程:先到先服務(wù)
對(duì)高優(yōu)先級(jí):高優(yōu)先級(jí)的線程搶占cpu,也有可能搶不到,只是概率高而已

(2)創(chuàng)建線程的方式2:實(shí)現(xiàn)Runnable接口

/*
* 方式二創(chuàng)建多線程
* 1.實(shí)現(xiàn)Runnable接口
* 2.重寫run方法
* 3.創(chuàng)建實(shí)現(xiàn)類的對(duì)象
* 4.將此對(duì)象作為參數(shù),傳遞到Thread類的構(gòu)造器中
* 5.使用Thread類對(duì)象調(diào)用start方法
* */
public class Thread_02 {
    public static void main(String[] args) {
        //創(chuàng)建實(shí)現(xiàn)類對(duì)象
        MyThread_02 my = new MyThread_02();
        //將對(duì)象作為參數(shù),放入Thread類的構(gòu)造器中
        Thread t = new Thread(my);
        //使用Thread類的對(duì)象調(diào)用start
        t.start();
    }
}
class MyThread_02 implements Runnable{

    @Override
    public void run() {
        for(int i = 0;i < 100;i++){
            if(i % 2 == 0){
//        System.out.println(getName()+".........");不能直接getName,原因是該類并沒有繼承Thread類
                System.out.println(Thread.currentThread().getName()+"........."+i);
            }
        }

    }
}

售票:

/*
* 創(chuàng)建3個(gè)賣票窗口,一共有100張票
* 存在線程安全問題
* */

//實(shí)現(xiàn)接口方式
public class WindowTest_02 {
    public static void main(String[] args) {
        //創(chuàng)建1個(gè)Window_02
        Window_02 w  = new Window_02();

        Thread t1 = new Thread(w);//不同線程使用同一個(gè)對(duì)象
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        //保證任意一個(gè)窗口售票時(shí),其他窗口不運(yùn)行,且獲得公有的對(duì)象,即static
        t1.setName("1號(hào)");
        t2.setName("2號(hào)");
        t3.setName("3號(hào)");

        t1.start();
        t2.start();
        t3.start();

    }
}

class Window_02 implements Runnable {
    private  int i = 100;//未使用static方法
    ......
}

//繼承方式
public class WindowTest {
    public static void main(String[] args) {
        //創(chuàng)建3個(gè)賣票窗口
        Window w1  = new Window();
        Window w2  = new Window();
        Window w3  = new Window();
        //保證任意一個(gè)窗口售票時(shí),其他窗口不運(yùn)行,且獲得公有的對(duì)象,即static
        w1.setName("1號(hào)");
        w2.setName("2號(hào)");
        w3.setName("3號(hào)");

        w1.start();
        w2.start();
        w3.start();

    }
}
class Window extends Thread {
    private static int i = 100;
     .......
}
比較線程的兩種方式

開發(fā)中,優(yōu)先選擇實(shí)現(xiàn)Runnable接口的方式
原因:1.實(shí)現(xiàn)方式?jīng)]有單繼承的局限性
2.實(shí)現(xiàn)的方式更適合處理多個(gè)線程有共享數(shù)據(jù)的情況
聯(lián)系:public class Thread implements Runnable
相同點(diǎn):兩種方式都要重寫run方法

線程通信:

wait()、notify()、notifyAll():此三種方法定義在Object類中
線程分類:守護(hù)線程、分類線程

線程的生命周期

線程生命周期.png

線程同步,解決線程安全

1.以上述售票為例,會(huì)出現(xiàn)的問題:重票、錯(cuò)票---》即線程安全問題
2.問題出現(xiàn)原因:當(dāng)某個(gè)線程還未完成操作時(shí),有新的線程進(jìn)入,也對(duì)其進(jìn)行操作
3.解決辦法:當(dāng)某個(gè)線程還未完成操作時(shí),其他線程必須在外等待,知道該線程完成操作才其它線程才可以運(yùn)行。這種情況即使在線程出現(xiàn)阻塞時(shí),也不能被改變

4.在java中,使用同步機(jī)制,解決線程安全問題:

(1)同步代碼塊:要求多個(gè)線程必須共用同一把鎖;壞處,相當(dāng)于單線程,效率低
sychronized(同步監(jiān)視器){
//需要被同步的代碼,不能多也不能少,即操作共享數(shù)據(jù)(多個(gè)線程共同操作的變量)的代碼
}
同步監(jiān)視器:俗稱鎖。任何一個(gè)對(duì)象都可以充當(dāng)鎖
1).實(shí)現(xiàn)Runnable接口方式的鎖

class Window_02 implements Runnable {
    private  int i = 100;//未使用static方法
    Object obj = new Object();//所有線程共享一個(gè)
    @Override
    public void run() {
        while(true){
            synchronized(obj){//鎖;
            synchronized(this){//此時(shí)的this,表示當(dāng)前Window_02對(duì)象,implements Runnable接口時(shí),多個(gè)線程公用一個(gè)線程子類對(duì)象
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i > 0){
                    System.out.println(Thread.currentThread().getName()+"賣票:票號(hào)為"+i);
                    i--;
                }else{
                    break;
                }
            }

        }
    }

2).繼承Thread方式的鎖

class Window extends Thread {
    private static int i = 100;//必須使用static
    private static Object obj = new Object();//必須使用static
    @Override
    public void run() {
        while(true){
            synchronized(obj){//正確
            synchronized(Window.class){//正確,以類為對(duì)象,只會(huì)加載一次,即Class clazz = Window.class
            //synchronized(this){//錯(cuò)誤,因?yàn)閑xtends Thread時(shí),會(huì)創(chuàng)建多個(gè)線程對(duì)象,即,t1,t2,t3,他們不共用一個(gè)線程
              ......
            }
        }
    }
}

(2)同步方法
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個(gè)方法中,則將此方法聲明為同步的,此方法為同步方法
總結(jié):同步方法不需要顯示的聲明;非靜態(tài)同步方法,同步監(jiān)視器是this,靜態(tài)同步方法,同步監(jiān)視器是當(dāng)前類本身
1)繼承Thread

class Window extends Thread {
    private static int i = 100;
    @Override
    public void run() {
       show();
    }
    private static synchronized void show() {//使用static關(guān)鍵字修飾該鎖,則所有成員共享同一個(gè)鎖;該鎖是當(dāng)前類Window.class
//    private synchronized void show() {//錯(cuò)誤,此鎖代表this,但創(chuàng)建的子類Thread對(duì)象創(chuàng)建了好幾個(gè),表示不同對(duì)象
        while(true){
            if(i > 0){
                    System.out.println(Thread.currentThread().getName()+"賣票:票號(hào)為"+i);
                    i--;
                }else{
                    break;
                }
            }
    }
}

2)實(shí)現(xiàn)Runnable接口

class Window_02 implements Runnable {
    private  int i = 100;//未使用static方法
    @Override
    public void run() {
        show();
    }

    private synchronized void show() {//此時(shí)鎖代表this
        while(true){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i > 0){
                    System.out.println(Thread.currentThread().getName()+"賣票:票號(hào)為"+i);
                    i--;
                }else{
                    break;
                }
            }
    }
}

死鎖

1.不同線程分別占用對(duì)方需要的同步資源不放棄,都在等待對(duì)方釋放資源;出現(xiàn)死鎖后,不會(huì)出現(xiàn)異常和提示,只是所有線程都阻塞,無法繼續(xù)
2.解決辦法:盡量避免同步或者使用算法

5.lock鎖解決線程安全(jdk5.0新特性)

同步鎖使用Lock充當(dāng)對(duì)象;使用ReentranLock的對(duì)象實(shí)現(xiàn)Lock

class MyLock extends Thread {
    private static int i = 40;
    private ReentrantLock lock = new ReentrantLock();//創(chuàng)建lock鎖對(duì)象
    @Override
    public void run() {
        try {
            //打開鎖
            lock.lock();
            while (true) {
                if (i > 0) {
                    System.out.println(getName() + "...." + i);
                    i--;
                } else {//跳出來,否則程序一直在運(yùn)行
                    break;
                }
            }
        }finally {
            //釋放鎖
            lock.unlock();
        }
    }
}
synchronized和lock異同:

相同點(diǎn):都能解決線程安全問題
不同點(diǎn):synchronized機(jī)制在執(zhí)行完相應(yīng)的同步代碼塊后,自動(dòng)釋放同步監(jiān)視器;lock需要手動(dòng)啟動(dòng)(lock)或結(jié)束(unlock)同步;lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
優(yōu)先使用順序:lock--》同步代碼塊--》同步方法

例題:

銀行有一個(gè)賬戶,有兩個(gè)用戶分別向同一個(gè)賬戶存3000元,每次存1000,存3次。每次存完打印賬戶余額

public class Bank {
    public static void main(String[] args) {
        //此種方式類似于實(shí)現(xiàn)runnable接口的創(chuàng)建調(diào)用start方法
        Account acct = new Account(0);
        Customer c1 = new Customer(acct);//創(chuàng)建名為甲的客戶
        Customer c2 = new Customer(acct);

        c1.start();
        c2.start();
    }
}

//存儲(chǔ)用戶,每個(gè)用戶共存款3次
class Customer extends Thread {
    private Account acct;//創(chuàng)建公有的Account賬戶

    //創(chuàng)建Account的構(gòu)造方法
    public Customer(Account acct) {
        this.acct = acct;
    }
     @Override
    public void run() {
        for (int i = 1; i <= 3; i++) {      
                //調(diào)用Account存錢的方法
                acct.deposit(1000);
          }
    }
}

//銀行賬戶,共存到6000,即balance是共有數(shù)據(jù)
class Account {
    private int balance = 0;
    //創(chuàng)建Account的構(gòu)造器,即一創(chuàng)建Account賬戶,便初始化其balance
    Account(int balance){
        this.balance = balance;
    }
//方式一:lock
鎖
 private static ReentrantLock lock  = new ReentrantLock();
 void deposit(int money){
        try{
            lock.lock();
            if(money >= 0){
                balance += money;
                System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
            }
        }finally {
            lock.unlock();
        }
    }
//方式二,同步方法
 synchronized void deposit(int money){

            if(money >= 0){
                balance += money;
                System.out.println(Thread.currentThread().getName() + "存款,共存" + balance);
            }
    }
}

線程通信

wait()、notify()/notifyAll()必須搭配使用,且都屬于Object方法;必須使用在同步代碼塊或同步方法中;調(diào)用者必須是同步代碼塊或同步方法中的同步監(jiān)視器,否則出現(xiàn)IllegalMonitorStateException
wait:一旦執(zhí)行此方法,當(dāng)前線程就進(jìn)入阻塞狀態(tài),并釋放同步監(jiān)視器
notify:喚醒被wait的一個(gè)線程,若有多個(gè)線程,則喚醒優(yōu)先級(jí)高的
notifyAll:喚醒所有wait的線程

/*
* 使用線程1,2交替打印1~100的數(shù)
* */
public class Communication {
    public static void main(String[] args) {
        new Number().start();
        new Number().start();
    }
}
class Number extends Thread{
    private static int num = 1;
    private static Object obj = new Object();
    @Override
    public void run() {
        while(true){
            //繼承方式的不能使用this,因?yàn)槊看蝨his的對(duì)象不同
            synchronized (obj){
                obj.notify();
                if(num <= 20){
                    System.out.println(getName() + "打印" + num);
                    num++;
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
}
例題:

sleep和wait的異同:
相同點(diǎn):都能使當(dāng)前線程進(jìn)入阻塞狀態(tài)
不同點(diǎn):(1)位置不同,Thread類中聲明sleep,Object類中聲明wait
(2)調(diào)用要求不同,sleep可以在任意場景,wait必須在同步代碼塊或同步方法中
(3)是否釋放同步監(jiān)視器,若兩者都使用在同步代碼塊或同步方法中,sleep不會(huì)釋放鎖,wait會(huì)釋放鎖

/*
* 線程通信的應(yīng)用:生產(chǎn)者/消費(fèi)者問題(經(jīng)典例題)
* 生產(chǎn)者(Product)將產(chǎn)品交給店員(Clerk),而消費(fèi)者(Customer)從店員處取走產(chǎn)品,店員一次只能持有固定數(shù)量的產(chǎn)品(如:20)
* 如果生產(chǎn)者試圖生產(chǎn)更多的產(chǎn)品,店員會(huì)讓生產(chǎn)者停止,若店中有空位再通知生產(chǎn)者繼續(xù)生產(chǎn);如果店中沒有產(chǎn)品,店員會(huì)讓消費(fèi)者
* 等一下,若有產(chǎn)品了再通知消費(fèi)者取走產(chǎn)品
*
* 分析:
* 1.多線程:生產(chǎn)者、消費(fèi)者
* 2.線程安全:共享數(shù)據(jù),產(chǎn)品或店主
* 3.解決線程安全:同步(3種)
* 4.線程間相互通信:無產(chǎn)品,無空位時(shí)
*
* */
public class ProductList {
    public static void main(String[] args) {
        Clerk clerk = new Clerk();
        Productor p = new Productor(clerk);
        Thread t = new Thread(p);
        Customer_P c = new Customer_P(clerk);

        t.setName("生產(chǎn)");
        c.setName("顧客");

        t.start();
        c.start();
    }
}
//共享數(shù)據(jù)
class Clerk{
    private int num = 0;
    //消費(fèi)者消費(fèi)產(chǎn)品
    public synchronized void cust_Product() {//解決同步問題
        if(num > 0){
            System.out.println(Thread.currentThread().getName() + "..."+num);
            num--;
            notify();//消費(fèi)者消費(fèi)產(chǎn)品后,就喚醒生產(chǎn)者
        }else {
            try {
                wait();//當(dāng)沒有產(chǎn)品,消費(fèi)者處于等待狀態(tài)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
    //生產(chǎn)者生產(chǎn)產(chǎn)品
    public synchronized void pro_Product() {//解決同步問題
        if(num <= 20){
            num++;
            System.out.println(Thread.currentThread().getName() + "...."+num);
            notify();//生產(chǎn)了產(chǎn)品,就喚醒消費(fèi)者
        }else {
            try {
                wait();//當(dāng)產(chǎn)品多于20,生產(chǎn)者停止生產(chǎn)
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
//implements Runnable設(shè)置Productor線程
class Productor implements Runnable{
    private Clerk clerk;
    Productor(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        while (true){
            clerk.pro_Product();
        }

    }
}
//extends Thread設(shè)置Customer線程
class Customer_P extends Thread{
    private Clerk clerk;
    Customer_P(Clerk clerk){
        this.clerk = clerk;
    }
    @Override
    public void run() {
        while (true){
            clerk.cust_Product();
        }
    }
}

jdk5.0新增線程創(chuàng)建方式

新增方式1:實(shí)現(xiàn)Callable接口

與實(shí)現(xiàn)Runnable接口相比較,Callable功能更強(qiáng)大
(1)相比run方法,可以有返回值
(2)方法可以拋出異常
(3)支持泛型返回值
(4)需要借助FutureTask類,比如獲得返回結(jié)果

/*
* 創(chuàng)建多線程方式3:實(shí)現(xiàn)Callable接口
* 1.實(shí)現(xiàn)Callable接口
* 2.重寫call方法
* 3.創(chuàng)建接口實(shí)現(xiàn)類的對(duì)象
* 4.將該對(duì)象傳入FutureTask的構(gòu)造器中
* 5.將FutureTask對(duì)象傳遞到Thread類的構(gòu)造器中
* 6.使用該對(duì)象的get方法得到重寫call的返回值
*
* */
public class Callable_01 {
    public static void main(String[] args) {
        //3.創(chuàng)建接口實(shí)現(xiàn)類的對(duì)象
        NumThread num = new NumThread();
        //4.將接口實(shí)現(xiàn)類的對(duì)象作為參數(shù)傳遞到FutureTask構(gòu)造器中,創(chuàng)建FutureTask的對(duì)象
        FutureTask task = new FutureTask(num);
        //5.將FutureTask的對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread對(duì)象,并調(diào)用start方法
        new Thread(task).start();

        try {
            //6.獲得FutureTask構(gòu)造器參數(shù)Callable實(shí)現(xiàn)類的重寫方法call的返回值
            Object o = task.get();
            System.out.println("總和為" + o);

        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class NumThread implements Callable{
    private int sum = 0;
    @Override
    public Object call() throws Exception {
        for (int i = 0; i <= 100; i++) {
            if(i % 2 == 0) {
                sum += i;
                System.out.println( sum);

            }
        }
        return sum;
    }
}

新增方式2:使用線程池(開發(fā)中常用)

好處:響應(yīng)速度提高;提高資源重用率;便于管理

public class ThreadPool {
    public static void main(String[] args) {
        //1.提供指定數(shù)量的線程池
        ExecutorService service = Executors.newFixedThreadPool(5);//創(chuàng)建線程的個(gè)數(shù)
        //ExecutorService是一個(gè)接口,使用子類ThreadPoolExecutor可以調(diào)用其屬性方法
//        ThreadPoolExecutor service1= (ThreadPoolExecutor) service;
//        service1.getMaximumPoolSize();
                System.out.println(service.getClass());
        //2.執(zhí)行指定線程的操作,需要提供Runnable或Callable接口
        service.execute(new NumPool());//適合Runnable接口
        //service.submit(new Callable);//適合Callable接口

        //3.關(guān)閉線程池
        service.shutdown();

    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 本文主要講了java中多線程的使用方法、線程同步、線程數(shù)據(jù)傳遞、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等。 首先講...
    李欣陽閱讀 2,599評(píng)論 1 15
  • Java多線程學(xué)習(xí) [-] 一擴(kuò)展javalangThread類 二實(shí)現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 3,108評(píng)論 1 18
  • 一、進(jìn)程和線程 進(jìn)程 進(jìn)程就是一個(gè)執(zhí)行中的程序?qū)嵗總€(gè)進(jìn)程都有自己獨(dú)立的一塊內(nèi)存空間,一個(gè)進(jìn)程中可以有多個(gè)線程。...
    阿敏其人閱讀 2,705評(píng)論 0 13
  • 林炳文Evankaka原創(chuàng)作品。轉(zhuǎn)載自http://blog.csdn.net/evankaka 本文主要講了ja...
    ccq_inori閱讀 735評(píng)論 0 4
  • 一擴(kuò)展javalangThread類二實(shí)現(xiàn)javalangRunnable接口三Thread和Runnable的區(qū)...
    和帥_db6a閱讀 593評(píng)論 0 1

友情鏈接更多精彩內(nèi)容