java多線程相關代碼


1.創建線程的三種方式

  使用Thread

package com.wpbxx.test;

//1.自定義一個類,繼承java.lang包下的Thread類
class MyThread extends Thread{
    //2.重寫run方法
    @Override
    public void run() {
        //3.將要在線程中執行的代碼編寫在run方法中
        for(int i = 0; i < 1000; i++) {
            System.out.println("wpb");
        }
    }
}
public class helloworld {

    public static void main(String[] args) {
        //4.創建上面自定義類的對象
        MyThread mt  = new MyThread();
        //5.調用start方法啟動線程
        mt.start();
        for(int i = 0; i< 1000; i++) {
            System.out.println("xx");
        }
    }

}

使用Runnable

package com.wpbxx.test;

//1.自定義一個類實現java.lang包下的Runnable接口
class MyRunnable implements Runnable{
    //2.重寫run方法
        @Override
    public void run() {
            //3.將要在線程中執行的代碼編寫在run方法中
        for(int i = 0; i < 1000; i++) {
            System.out.println("wpb");
        }
    }
}
public class helloworld {

    public static void main(String[] args) {
        //4.創建上面自定義類的對象
        MyRunnable mr = new MyRunnable();
        //5.創建Thread對象並將上面自定義類的對象作為參數傳遞給Thread的構造方法
        Thread t = new Thread(mr);
        //6.調用start方法啟動線程
        t.start();
        for(int i = 0; i < 1000; i++) {
            System.out.println("xx");
        }
    }

}

使用Callable接口創建的線程會獲得一個返回值並且可以聲明異常。

優點: 可以獲取返回值 可以拋出異常

線程池

線程池是初始化一個多線程應用程序過程中創建一個線程集合,然后在需要執行新的任務時直接去這個線程集合中獲取,而不是創建一個線程。任務執行結束后,線程回到池子中等待下一次的分配。

線程池的作用
解決創建單個線程耗費時間和資源的問題。

package com.wpbxx.test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
//1.自定義一個類實現java.util.concurrent包下的Callable接口
class MyCallable implements Callable<Integer>{
    private int count;
    public MyCallable(int count) {
        this.count = count;
    }
    //2.重寫call方法
        @Override
    public Integer call() throws Exception{
            //3.將要在線程中執行的代碼編寫在call方法中
        for(int i = 0; i < 100; i++) {
            count++;
        }
        return count;
    }
}
public class helloworld {

    public static void main(String[] args) {
        //4.創建ExecutorService線程池 里面為線程的數量
        ExecutorService es = Executors.newFixedThreadPool(2);
        ////創建一個線程池,里面的線程會根據任務數量進行添加
        //ExecutorService es = Executors.newCachedThreadPool();
    
        //5.將自定義類的對象放入線程池里面
        Future<Integer> f1= es.submit(new MyCallable(5));
        Future<Integer> f2 = es.submit(new MyCallable(3));
        try {
            //6.獲取線程的返回結果
            System.out.println(f1.get());
            System.out.println(f2.get());
            
        } catch (InterruptedException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } catch (ExecutionException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
        //7.關閉線程池,不再接收新的線程,未執行完的線程不會被關閉
        es.shutdown();
    }

}

繼承Thread
  優點:可以直接使用Thread類中的方法,代碼簡單
  缺點:繼承Thread類之后就不能繼承其他的類
實現Runnable接口
  優點:即時自定義類已經有父類了也不受影響,因為可以實現多個接口
  缺點: 在run方法內部需要獲取到當前線程的Thread對象后才能使用Thread中的方法
實現Callable接口
  優點:可以獲取返回值,可以拋出異常
  缺點:代碼編寫較為復雜

package com.wpbxx.test;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

//簡易寫法  使用匿名內部類創建多線程


public class helloworld {

    public static void main(String[] args) throws InterruptedException, ExecutionException {
        new Thread() {
            public void run() {
                for(int i = 0; i < 1000; i++) {
                    System.out.println("wpb");
                }
            }
        }.start();
        
        new Thread(new Runnable() {
            public void run() {
                for(int i = 0; i< 1000; i++) {
                    System.out.println("xx");
                }
            }
        }).start();
        
        ExecutorService exec = Executors.newCachedThreadPool();
        Future<Integer> result = exec.submit(new Callable<Integer>() {
            
        @Override
        public Integer call() throws Exception{
            return 1024;
        }
        });
        
        System.out.println(result.get());
    }

}

Thread設置線程的名字

方法一

new Thread("馬化騰") {                            //通過構造方法給name賦值
            public void run() {
                System.out.println("我是" + this.getName() + ",來騰訊工作吧 ");
            }
        }.start();

方法二

new Thread() {
            public void run() {
                this.setName("馬化騰");      //調用setName
                System.out.println("我是" + this.getName() + ",來騰訊啊");
            }
        }.start();

使用Thread.currentThread() 獲得正在運行的線程

可以這樣改變Runnable中線程名字

package com.wpbxx.test;

public class helloworld {

    public static void main(String[] args) {
      new Thread(new Runnable() {
          public void run() {
          System.out.println(Thread.currentThread().getName());
          Thread.currentThread().setName("wpb");
          System.out.println(Thread.currentThread().getName());
          }
      }).start();
    }

}

線程睡眠

Thread中的sleep方法可以使當前線程睡眠,線程睡眠后,里面的任務不會執行,待睡眠時間過后會自動蘇醒,從而繼續執行任務。

Thread.sleep(1000);   //讓當前線程睡眠1秒

線程的優先級

setPriority()方法接收一個int類型的參數,通過這個參數可以指定線程的優先級,取值范圍是整數1~10,優先級隨着數字的增大而增強。  但並不是一定執行優先級高的執行完之后  才執行別的

package com.wpbxx.test;

public class helloworld {

    public static void main(String[] args) {
        Thread t1 = new Thread() {
            public void run() {
                for(int i = 0; i<100; i++) {
                    System.out.println("wpb");
                }
            }
        };
        
        Thread t2 = new Thread() {
            public void run() {
                for(int i = 0; i < 100; i++) {
                    System.out.println("1024");
                }
            }
        };
        
        t1.setPriority(10);
        t2.setPriority(0);
        t1.start();
        t2.start();
        
        
    }

}

喚醒睡眠中的線程

  t1.interrupt();

用interrupt方法會拋出一個InterruptedException的異常。

 

同步方法

package com.wpbxx.test;

public class helloworld {

    public static void main(String[] args) {
    
        Task tk = new Task();
        
        Thread t1 = new Thread() {
            public void run() {
                tk.changeNum(true);
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                tk.changeNum(false);
            }
        };
        t1.start();
        t2.start();
    }

}
class Task{
    
    private int num = 0;
    public void changeNum(boolean flag) {
        if(flag) {
            num = 99;
            System.out.println(Thread.currentThread().getName() + "-------" + "begin");
            System.out.println(Thread.currentThread().getName() + "-------" + num);
            System.out.println(Thread.currentThread().getName() + "-------" + "end");
        }else {
            num = 22;
            System.out.println(Thread.currentThread().getName() + "-------" + "begin");
            System.out.println(Thread.currentThread().getName() + "-------" + num);
            System.out.println(Thread.currentThread().getName() + "-------" + "end");
        }
    }
}

正常情況下應該打印出一個88一個66,可是上面卻兩個線程打印出的兩個66,這樣就出現了線程安全的問題,出現這個問題的原因是成員變量存儲在堆內存中,兩個線程共享堆內存,即兩個線程可以對同一個num進行修改。
程序執行分析:
cpu執行t1線程,將num修改為88,之后cpu開始執行t2線程,將num修改為66,打印出66,cpu開始執行t1線程,打印num的值,此時num的值是66。

 

在方法上加入synchronized關鍵字,這樣在執行多個線程時看哪個線程先執行這個方法,假設有t1,t2,t3三個線程中都調用了changeNum方法,t1線程先執行了這個方法,那么t1會先在Task對象上面加鎖,加鎖后,別的線程就無法執行當前Task對象上的changeNum方法,直到t1執行結束changeNum方法之后,t2,t3中的一個線程才可以執行這個方法,這就保證了在某個時間段內只有一個線程執行changeNum方法,解決了線程安全問題。
注意:synchronized鎖住的是當前對象,如果t1線程和t2線程里面是不同的對象,則不需要同步,因為不會發生線程安全問題

 

public synchronized void changeNum(boolean flag) 加這一句就ok了


也可以對需要互斥訪問的代碼塊加上synchronized
    public void changeNum(boolean flag){

        try {
            Thread.sleep(3000);
            System.out.println("執行一個耗時較長的任務");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //這個方法中,需要同步的代碼塊是這部分,而上面耗時操作的代碼,不涉及到線程安全問題,所以不需要同步
        synchronized(obj){
            if(flag){
                num = 88;

                System.out.println(Thread.currentThread().getName() + "========== begin");
                System.out.println(Thread.currentThread().getName() + "==========" + num);
                System.out.println(Thread.currentThread().getName() + "========== end");
            }else{
                num = 66;

                System.out.println(Thread.currentThread().getName() + "========== begin");
                System.out.println(Thread.currentThread().getName() + "==========" + num);
                System.out.println(Thread.currentThread().getName() + "========== end");
            }
        }

    }

死鎖

發生死鎖原因就是兩個或多個線程都在等待對方釋放鎖導致,下面通過代碼來演示一下死鎖情況。

package com.wpbxx.test;

public class helloworld {

    private static Object obj1 = new Object();
    private static Object obj2 = new Object();
    public static void main(String[] args) {
        new Thread() {
            public void run() {
                synchronized(obj1) {
                    System.out.println(this.getName());
                    
                    synchronized(obj2) {
                        System.out.println(this.getName());
                    }
                }
            }
        }.start();
         new Thread() {
            public void run() {
                synchronized(obj2) {
                    System.out.println(this.getName());
                    
                    synchronized(obj1) {
                        System.out.println(this.getName());
                    }
                }
            }
        }.start();
    }
}

 

volatile關鍵字

package com.wpbxx.test;

public class helloworld {

    public static void main(String[] args) throws InterruptedException {
        
        Task task = new Task();
        
        Thread t1 = new Thread(task);
        
        t1.start();
        
        Thread.sleep(100);
        task.setFlag(false);
    }
}
class Task implements Runnable{
    
    private boolean flag = true;
    
    public boolean isFlag() {
        return flag;
    }
    
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    
    public void run() {
        while(flag) {
            System.out.println("while循環");
        }
        System.out.println("循環結束");
    }
}

上面程序中在64位的機器上以server模式運行時,有可能會出現死循環的現象。

JVM的運行可以分為下面兩種模式:

  • client:啟動快,運行后性能不如server模式,一般運行時默認是client模式
  • server:啟動慢,運行后性能比client模式好。

在eclipse中可以通過配置來使用server模式,右鍵—>run as—>run configurations。寫上-server。然后點擊run即可

上面程序出現問題的原因這樣的,雖然在主線程中將flag的設置為false,但是jvm為了提升效率,t1線程一直在私有內存中獲取flag的值,而私有內存中的flag值並沒有被改變,所以導致死循環的發生。

使用volatile修飾flag解決上面問題:

volatile private boolean flag = true; 

將flag聲明為volatile后,t1線程會從公共的內存中訪問flag的值,這樣在主線程將flag設置為false后,t1線程中的循環就會結束了。

注意:volatile只能修飾變量,不能修飾方法

原子性和非原子性

原子性:即一個操作或者多個操作 要么全部執行並且執行的過程不會被任何因素打斷,要么就都不執行。

非原子性:不符合原子性的就是非原子性

  int x = 1024; //原子性

    int y = x; //cpu先去內存中讀取x的值,讀取后在為y進行賦值,在讀取后給y賦值前的這段時間可能會切換到其他線程上面。

    x++; //包含了三個操作,先讀取x的值,然后進行加1操作,最后寫入新的值,在這三個操作的間隙可能會切換到其他線程上面。

    x = x + 1; //同上

volatile是非原子性的。
synchronized是原子性的。

 TimerTask

TimerTask是一個實現了Runnable接口的抽象類,需要編寫一個類繼承TimerTask類,將要在定時任務執行的代碼編寫在run方法中。

要想執行定時任務,需要創建Timer的對象並調用里面的schedule方法,在Timer類中有多個重載的schedule方法,這里咱們使用這個:

schedule(TimerTask task, Date firstTime, long period);

package com.wpbxx.test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;

public class helloworld {

    public static void main(String[] args) throws InterruptedException, ParseException {
        
        Timer t = new Timer();
        t.schedule(new MyTimerTask(), new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS").parse("2017-07-03 18:09:00 000"),
                5000);
    }
}
class MyTimerTask extends TimerTask{
    @Override
    public void run() {
        System.out.println("wpbxx");
    }
}

 

 

線程之間的通信

多線程環境下CPU會隨機的在線程之間進行切換,如果想讓兩個線程有規律的去執行,那就需要兩個線程之間進行通信,在Object類中的兩個方法wait和notify可以實現通信。

wait方法可以使當前線程進入到等待狀態,在沒有被喚醒的情況下,線程會一直保持等待狀態。
notify方法可以隨機喚醒單個在等待狀態下的線程

 

利用wait  和notify  進行交替打印

package com.wpbxx.test;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Timer;
import java.util.TimerTask;

public class helloworld {

    public static void main(String[] args) throws InterruptedException, ParseException {
        Print p = new Print();
        Thread t1 = new Thread() {
            public void run() {
                while(true) {
                    p.print1();
                }
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                while(true) {
                    p.print2();
                }
            }
        };
        t1.start();
        t2.start();
    }
}
class Print{
    private int flag = 1;
    
    public void print1() {
        synchronized(this) {
            if(flag != 1) {
                try {
                    this.wait();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            System.out.println("wpb");
            flag = 2;
            this.notify();
        }
    }
    public void print2() {
        synchronized(this) {
            if(flag != 2) {
                try {
                    this.wait();
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            
            System.out.println("xx");
            flag = 1;
            this.notify();
        }
    }
}

 

但這樣如果是三個線程以上的  就不行,  可能出現死鎖了

因為是隨機喚醒一個等待的線程,  假設一線程 進行玩后 隨即喚醒一個線程,並把flag = 2,  但這時喚醒了線程3  就會一直等待

notifyAll()  為喚醒所有的線程

package com.wpbxx.test;

/**
 * 三個(三個以上)線程之間的通信
 *
 */
public class helloworld {

    public static void main(String[] args) {

        Print1 p = new Print1();

        Thread t1 = new Thread(){
            public void run(){
                while(true){
                    p.print1();
                }

            }
        };

        Thread t2 = new Thread(){
            public void run(){
                while(true){
                    p.print2();
                }
            }
        };

        Thread t3 = new Thread(){
            public void run(){
                while(true){
                    p.print3();
                }
            }
        };

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

}

class Print1{

    private int flag = 1;

    public void print1(){
        synchronized(this){
            while(flag != 1){
                try {
                    //讓當前線程進入等待狀態
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("monkey");
            flag = 2;
            //喚醒所有等待的線程
            this.notifyAll();
        }

    }

    public void print2(){
        synchronized(this){
            while(flag != 2){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("1024");
            flag = 3;
            this.notifyAll();
        }

    }

    public void print3(){
        synchronized(this){
            while(flag != 3){
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("888");
            flag = 1;
            this.notifyAll();
        }

    }
}
View Code

這樣就可以實現三個線程的交替打印, 但會有問題  就是喚醒所有的線程  開銷太大。

 

 

上面notify() 或者 notifyAll()  並不能喚醒指定的線程,所以多出了  互斥鎖

新增了  ReenTrantLock類 和    Condition接口  來替換   synchronized關鍵字   和   wait、notify  方法。

ReenTrantLock類     和Condition接口    都在java.util.concurrent.locks包下。
可以使用       ReentrantLock類中    的  lock方法   和   unlock方法     進行上鎖和解鎖,用來替代synchronized關鍵字。
Condition接口中的await方法和signal方法用來讓線程等待和喚醒指定線程。用來替代wait方法和notify方法。

 

如 還是循環打印東西

package com.wpbxx.test;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

public class helloworld {

    public static void main(String[] args) throws InterruptedException {
        
        Print p = new Print();
        Thread t1 = new Thread() {
            public void run() {
                while(true) {
                    p.print1();
                }
            }
        };
        Thread t2 = new Thread() {
            public void run() {
                while(true) {
                    p.print2();
                }
            }
        };
        Thread t3 = new Thread() {
            public void run() {
                while(true) {
                    p.print3();
                }
            }
        };
        t1.start();
        t2.start();
        t3.start();
    }
}
class Print{
    private ReentrantLock r = new ReentrantLock();
    
    private Condition c1 = r.newCondition();
    private Condition c2 = r.newCondition();
    private Condition c3 = r.newCondition();
    
    private int flag = 1;
    public void print1() {
        r.lock();
        
        while(flag != 1) {
            try {
                c1.await();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("wpb1");
        flag = 2;
        c2.signal();
        r.unlock();
    }
    
    public void print2() {
        r.lock();
        
        while(flag != 2) {
            try {
                c2.await();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("wpb2");
        flag = 3;
        c3.signal();
        r.unlock();
    }
    public void print3() {
        r.lock();
        
        while(flag != 3) {
            try {
                c3.await();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
        System.out.println("wpb3");
        flag = 1;
        c1.signal();
        r.unlock();
    }
    
    
}

以后再補充


免責聲明!

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



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