Java多線程、線程池和線程安全整理


    多線程

1.1      多線程介紹

進程指正在運行的程序。確切的來說,當一個程序進入內存運行,即變成一個進程,進程是處於運行過程中的程序,並且具有一定獨立功能。

 

 

 

 

1.2      Thread類

通過API中搜索,查到Thread類。通過閱讀Thread類中的描述。Thread是程序中的執行線程。Java 虛擬機允許應用程序並發地運行多個執行線程。

l  構造方法

 

 

 

l  常用方法

 

發現創建新執行線程有兩種方法。

    一種方法是將類聲明為 Thread 的子類。該子類應重寫 Thread 類的 run 方法。創建對象,開啟線程。run方法相當於其他線程的main方法。

    另一種方法是聲明一個實現 Runnable 接口的類。該類然后實現 run 方法。然后創建Runnable的子類對象,傳入到某個線程的構造方法中,開啟線程。

 

創建線程的步驟:

1 定義一個類繼承Thread。

2 重寫run方法。

3 創建子類對象,就是創建線程對象。

4 調用start方法,開啟線程並讓線程執行,同時還會告訴jvm去調用run方法。

 

package com.oracle.xiancheng;

 

public class Demo01 extends Thread {

    public static void main(String[] args) {

        //創建線程

        MyThread mt=new MyThread();

        //創建線程

        mt.start();

        //獲取正在執行的對象名稱  調用 getname

       

            for(int i=0;i<100;++i){

                System.out.println("main-------"+i);

            }

    }

}

 

自定義線程類

 

package com.oracle.Runnable;

 

public class MyRunnable implements Runnable{

 

    @Override

    public void run() {

       for(int i=0;i<50;i++){

           System.out.println("run-----"+i);

       }

      

    }

   

}

 

 

1.2.1    實現Runnable的原理

實現Runnable接口,避免了繼承Thread類的單繼承局限性。覆蓋Runnable接口中的run方法,將線程任務代碼定義到run方法中。

創建Thread類的對象,只有創建Thread類的對象才可以創建線程。線程任務已被封裝到Runnable接口的run方法中,而這個run方法所屬於Runnable接口的子類對象,所以將這個子類對象作為參數傳遞給Thread的構造函數,這樣,線程對象創建時就可以明確要運行的線程的任務。

1.2.2    實現Runnable的好處

第二種方式實現Runnable接口避免了單繼承的局限性,所以較為常用。實現Runnable接口的方式,更加的符合面向對象,線程分為兩部分,一部分線程對象,一部分線程任務。繼承Thread類,線程對象和線程任務耦合在一起。一旦創建Thread類的子類對象,既是線程對象,有又有線程任務。實現runnable接口,將線程任務單獨分離出來封裝成對象,類型就是Runnable接口類型。Runnable接口對線程對象和線程任務進行解耦。

 

1.3      線程的匿名內部類使用

 

 

 

 

package com.oracle.Runnable;

 

public class MyRunnable implements Runnable{

 

    @Override

    public void run() {

       for(int i=0;i<50;i++){

           System.out.println("run-----"+i);

       }

      

    }

   

}

 

package com.oracle.Runnable;

 

public class Demo02 {

    public static void main(String[] args) {

      

       //創建線程子類對象

       //匿名內部類對象

       //創建線程對象時,直接重寫Thread類中的run方法

       Thread th=new Thread(){

           public void run() {

              System.out.println(Thread.currentThread().getName()+"run");

           };

       };

       //開啟線程

       th.start();

      

       //使用匿名內部類的方式實現Runnable接口,重新Runnable接口中的run方法

       /*Runnable r=new Runnable(){

           public void run() {

              System.out.println(Thread.currentThread().getName()+"run");

           };

       };

       //創建線程任務對象

       Thread th=new Thread(r);

       //開啟線程

       th.start();*/

      

    }

}

運行結果:

 

 

 

 

    線程池

2.1      線程池概念

線程池,其實就是一個容納多個線程的容器,其中的線程可以反復使用,省去了頻繁創建線程對象的操作,無需反復創建線程而消耗過多資源

 

 

2.2      使用線程池方式--Runnable接口

l  Executors:線程池創建工廠類

public static ExecutorService newFixedThreadPool(int nThreads):返回線程池對象

l  ExecutorService:線程池類

Future<?> submit(Runnable task):獲取線程池中的某一個線程對象,並執行

l  Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用

 

l  使用線程池中線程對象的步驟:

創建線程池對象

創建Runnable接口子類對象

提交Runnable接口子類對象

關閉線程池

 

 

 

package com.oracle.Runnable;

 

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

 

public class Demo03 {

     public static void main(String[] args) {

         // Executors 線程池工廠類

         // ExecutorService 線程池工廠類

         // 獲取線程池對象

         ExecutorService es = Executors.newFixedThreadPool(2);

         // 創建線程任務對象

         MyRunnable mr = new MyRunnable();

         // 執行線程任務

         es.submit(mr);

         es.submit(mr);

         es.submit(mr);

         //釋放資源

         es.shutdown();

        

     }

}

 

package com.oracle.Runnable;

 

public class MyRunnable implements Runnable{

 

    @Override

    public void run() {

        for(int i=0;i<50;i++){

            System.out.println("run-----"+i);

        }

       

    }

   

}

運行結果:

 

等等

2.3      使用線程池方式—Callable接口

Callable接口:與Runnable接口功能相似,用來指定線程的任務。其中的call()方法,用來返回線程任務執行完畢后的結果,call方法可拋出異常。

ExecutorService:線程池類

<T> Future<T> submit(Callable<T> task):獲取線程池中的某一個線程對象,並執行線程中的call()方法

Future接口:用來記錄線程任務執行完畢后產生的結果。線程池創建與使用

 

使用線程池中線程對象的步驟:

創建線程池對象

創建Callable接口子類對象

提交Callable接口子類對象

關閉線程池

 

 

package com.oracle.Runnable;

 

import java.util.concurrent.Callable;

 

public class MyCallable implements Callable<String> {

 

    @Override

    public String call() throws Exception {

       

        return "abc";

    }

   

}

 

package com.oracle.Runnable;

 

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorCompletionService;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

 

public class Demo04 {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        //創建線程任務

        MyCallable mc=new MyCallable();

        //獲取線程池工廠

        ExecutorService es=Executors.newFixedThreadPool(2);

        Future<String> f=es.submit(mc);

        //創建返回值

        String  str=f.get();

        System.out.println(str);

    }

}

 

運行結果:

 

 

2.4      線程池練習:返回兩個數相加的結果和乘積的結果

 

package com.oracle.Demo01;

 

import java.util.concurrent.Callable;

 

public class MyCallables implements Callable<Integer> {

    private int num1;

    private int num2;

    public MyCallables(int num1,int num2) {

        this.num1=num1;

        this.num2=num2;

    }

    @Override

    public Integer call() throws Exception {

       

        return num1+num2;

       

    }

 

}

package com.oracle.Demo01;

 

import java.math.BigInteger;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

 

public class Test1 {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        //和

        MyCallables mc1=new MyCallables(100,150);

        MyCallables mc2=new MyCallables(10,15);

        ExecutorService es=Executors.newFixedThreadPool(2);

        Future<Integer> num1=es.submit(mc1);

        Future<Integer> num2=es.submit(mc2);

        int s1=num1.get();

        int  s2=num2.get();

        System.out.println(s1);

        System.out.println(s2);

        es.shutdown();

       

    }

}

 

運行結果:

 

 

 

 

 

積:

 

package com.oracle.Demo01;

 

import java.math.BigDecimal;

import java.math.BigInteger;

import java.util.concurrent.Callable;

 

public class MyCallablesr implements Callable<String > {

    private int num;

    public MyCallablesr(int num) {

        this.num=num;

       

    }

    @Override

    public String call() throws Exception {

        String  base="1";//超long的范圍

        for(int i=1;i<=num;i++){

            //用BigDecimal轉換

            BigDecimal stra=new BigDecimal(base);

            BigDecimal end=new BigDecimal(i);

            BigDecimal re=end.multiply(stra);

            base=re.toString();

           

        }

        return base ;

    }

 

}

package com.oracle.Demo01;

 

import java.math.BigInteger;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.ExecutorService;

import java.util.concurrent.Executors;

import java.util.concurrent.Future;

 

public class Test1 {

    public static void main(String[] args) throws InterruptedException, ExecutionException {

       

        //積

        MyCallablesr ms1=new  MyCallablesr(100);

        MyCallablesr ms2=new MyCallablesr(200);

        ExecutorService es=Executors.newFixedThreadPool(2);

        Future<String> base1=es.submit(ms1);

        Future<String> base2=es.submit(ms2);

        String s1=base1.get();

        String s2=base2.get();

        System.out.println(s1);

        System.out.println(s2);

        es.shutdown();

       

    }

}

運行結果:

 

 

 

    多線程

3.1      線程安全

如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。

l  我們通過一個案例,演示線程的安全問題:

電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “功夫熊貓3”,本次電影的座位共100個(本場電影只能賣100張票)。

我們來模擬電影院的售票窗口,實現多個窗口同時賣 “功夫熊貓3”這場電影票(多個窗口一起賣這100張票)

需要窗口,采用線程對象來模擬;需要票,Runnable接口子類來模擬

 

3.2      線程同步(線程安全處理Synchronized)

java中提供了線程同步機制,它能夠解決上述的線程安全問題。

         線程同步的方式有兩種:

方式1:同步代碼塊

方式2:同步方法

3.2.1    同步代碼塊

同步代碼塊: 在代碼塊聲明上 加上synchronized

synchronized (鎖對象) {

    可能會產生線程安全問題的代碼

}

同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。

 

 

模擬售票:

 

package com.oracle.xianchengchi;

 

 

public class MyRunnable implements Runnable {

    // 賣電影票

    private int ticket = 100;

    private Object obj = new Object();

 

    @Override

    public void run() {

        while (true) {

            synchronized (obj) {

                if (ticket > 0) {

 

                    try {

                        Thread.sleep(50);

                    } catch (InterruptedException e) {

                        // TODO Auto-generated catch block

                        e.printStackTrace();

                    }

                    System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "張票");

 

                }

 

            }

        }

 

    }

 

}

 

測試:

package com.oracle.xianchengchi;

 

public class Test01 {

    public static void main(String[] args) {

        //明確線程任務

        MyRunnable mr=new MyRunnable();

        Thread t0=new Thread(mr);

        Thread t1=new Thread(mr);

        Thread t2=new Thread(mr);

        //開啟線程

        t0.start();

        t1.start();

        t2.start();

       

       

    }

}

 

運行結果:

 

 

3.2.2    同步方法

l  同步方法:在方法聲明上加上synchronized

public synchronized void method(){

   可能會產生線程安全問題的代碼

}

         同步方法中的鎖對象是 this

        

使用同步方法,對電影院賣票案例中Ticket類進行如下代碼修改:

package com.oracle.xianchengchi;

 

public class MyRunnables implements Runnable {

    // 賣電影票

    private int ticket = 100;

    private Object obj = new Object();

 

    @Override

    public void run() {

        while (true) {

           

            sale();

        }

       

 

    }

 

    public synchronized void sale() {

       

            if (ticket > 0) {

 

                try {

                    Thread.sleep(50);

                } catch (InterruptedException e) {

                    // TODO Auto-generated catch block

                    e.printStackTrace();

                }

                System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "張票");

 

            }

 

        }

   

 

}

package com.oracle.xianchengchi;

 

public class Test02 {

    public static void main(String[] args) {

        //明確線程任務

        MyRunnables mr=new MyRunnables();

        Thread t0=new Thread(mr);

        Thread t1=new Thread(mr);

        Thread t2=new Thread(mr);

        //開啟線程

        t0.start();

        t1.start();

        t2.start();

       

       

    }

}

運行結果:

 

 

 

3.3      Lock接口

查閱API,查閱Lock接口描述,Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作。

l  Lock接口中的常用方法

 

 

Lock提供了一個更加面對對象的鎖,在該鎖中提供了更多的操作鎖的功能。

我們使用Lock接口,以及其中的lock()方法和unlock()方法替代同步,對電影院賣票案例中Ticket類進行如下代碼修改:

 

package com.oracle.xianchengchi;

 

import java.util.concurrent.locks.Lock;

import java.util.concurrent.locks.ReentrantLock;

 

public class MyRunnable2 implements Runnable {

    // 賣電影票

    private int ticket = 100;

    private Lock lock=new ReentrantLock();

    @Override

    public void run() {

        while (true) {

            lock.lock();

                if (ticket > 0) {

 

                    try {

                        Thread.sleep(50);

                    } catch (InterruptedException e) {

                        // TODO Auto-generated catch block

                        e.printStackTrace();

                    }

                    System.out.println(Thread.currentThread().getName() + "出售第" + ticket-- + "張票");

 

                }

 

            lock.unlock();

        }

 

    }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM