天天看點

JAVA多線程的知識點概括(随筆)

JAVA多線程知識

        • 一、線程的基本概念
        • 二、線程的建立
          • (一)繼承Thread類
          • (二)實作Runnable接口
          • (三)實作Callable接口
          • (四)補充
        • 三、線程狀态
          • (一)線程各狀态間的關系:
          • (二)sleep
          • (三)join
          • (四)yield
          • (五)stop
          • (六)priority
          • (七)daemon
          • (八)其它常用方法
        • 四、線程同步
          • (一)synchronized方法
          • (二)synchronized塊
          • (三)死鎖
        • 五、線程協作
          • (一)管道法
          • (二)信号燈法
        • 六、進階主題
          • (一)任務定時排程
          • (二)HappenBefore(指令重排)
          • (三)Volatile
          • (四)單例模式
          • (五)可重入鎖
          • (六)CAS

一、線程的基本概念

  • 線程就是獨立的執行路徑;
  • 方法間的調用,即從哪裡來到哪裡去,是閉合的一條路徑;多線程的使用則開辟了多條路徑
  • 在程式運作時,即使沒有自己建立線程,背景也會存在多個線程,如gc程序、主線程(main為系統的入口點,用于執行整個程式);
  • 在一個程序中,如果開辟了多個線程,線程的運作由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為的幹預的;
  • 很多多線程是模拟出來的,真正的多線程是指有多個cpu,即多核。如果是模拟出來的多線程,即一個cpu的情況下,在同一個時間點,cpu隻能執行一個代碼,因為切換得很快,是以就有同時執行得錯覺;

二、線程的建立

(一)繼承Thread類

Thread的構造器:

JAVA多線程的知識點概括(随筆)

例題:

package thread_study01;
/*
建立線程方式一:
    1.建立:繼承Thread+重寫run
    2.啟動:建立子類對象+start
 */
public class StartThread extends Thread{
    //線程入口點
    public void run(){
        for(int i=0;i<10000;i++){
            System.out.println("一遍聽課");
        }
    }
    public static void main(String[] args){
        //啟動線程
        //建立子類對象
        StartThread st=new StartThread();
        //啟動
        st.start();
        //st.run();//普通方法調用
        for(int i=0;i<10000;i++){
            System.out.println("一邊coding");
        }
    }
}
           

注意:

  • 執行線程必須調用start(),加入到排程器中。
  • 加入後不一定立即執行,系統安排排程配置設定執行。
  • 直接調用run()不是開啟多線程,是普通方法調用。
(二)實作Runnable接口

例題:

package thread_study01;
/*
建立線程方式二:
    1.建立:實作Runnable+重寫run
    2.啟動:建立實作類對象+Thread對象+start
    推薦 避免單繼承的局限性,優先使用接口
    友善共享資源
 */
public class StartRun implements Runnable{
    //線程入口點
    public void run(){
        for(int i=0;i<10000;i++){
            System.out.println("一遍聽課");
        }
    }
    public static void main(String[] args){
        new Thread(new StartRun()).start();
        for(int i=0;i<10000;i++){
            System.out.println("一邊coding");
        }
    }
}
           
(三)實作Callable接口

例題:

package thread_study01;

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 CDownloader implements Callable<Boolean>{
	private String url; //遠端路徑
	private String name;  //存儲名字
	public CDownloader(String url, String name) {
		this.url = url; 
		this.name = name;
	}
	@Override
	public Boolean call() throws Exception {
		WebDownloader wd =new WebDownloader();
		wd.download(url, name);		
		System.out.println(name);
		return true;
	}
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		CDownloader cd1 =new CDownloader("http://upload.news.cecb2b.com/2014/0511/1399775432250.jpg","phone.jpg");
		CDownloader cd2 =new CDownloader("http://p1.pstatp.com/large/403c00037462ae2eee13","spl.jpg");
		CDownloader cd3 =new CDownloader("http://5b0988e595225.cdn.sohucs.com/images/20170830/d8b57e0dce0d4fa29bd5ef014be663d5.jpeg","success.jpg");
		//建立執行服務: 
		ExecutorService  ser=Executors.newFixedThreadPool(3);
		//送出執行: 
		Future<Boolean> result1 =ser.submit(cd1) ;
		Future<Boolean> result2 =ser.submit(cd2) ;
		Future<Boolean> result3 =ser.submit(cd3) ;
		//擷取結果:  
		boolean r1 =result1.get();
		boolean r2 =result1.get();
		boolean r3 =result1.get();
		System.out.println(r3);
		//關閉服務:  
		ser.shutdownNow();
	}
}
           
(四)補充

1.靜态代理:

例題:

package thread_study01;
/**
* 靜态代理
* 接口:
* 1.真實角色
* 2.代理角色
*/
public class StaticProxy {
   public static void main(String[] args) {
       new WeddingCompany(new You()).happyMarry();
   }
}
//接口
interface Marry{
   void happyMarry();
}
//真實角色
class You implements Marry{
   @Override
   public void happyMarry(){
       System.out.println("......you and 嫦娥終于奔月了......");
       }
}
//代理角色
class WeddingCompany implements Marry{
   //真實角色
   private Marry target;
   public WeddingCompany(Marry target){
       this.target=target;
   }
   @Override
   public void happyMarry() {
       ready();
       target.happyMarry();
       after();
   }
   private void  ready(){
       System.out.println("布置豬窩。。。。");
   }
   private void after(){
       System.out.println("鬧玉兔。。。。");
   }
}
           

2.lambda:

lambda的是為了避免匿名内部類定義過多,其實質屬于函數式程式設計的概念。

例題:

lambda表達式的推導過程

package thread_study01;
// Lambda表達式 簡化線程(用一次)的使用
public class LambdaThread{
   //靜态内部類
   static class Test implements Runnable{
       public void run(){
           for(int i=0;i<10000;i++){
               System.out.println("一遍聽課");
           }
       }
   }
   public static void main(String[] args){
       new Thread(new Test()).start();
       //局部内部類
       class Test2 implements Runnable{
           public void run(){
               for(int i=0;i<10000;i++){
                   System.out.println("一遍聽課");
               }
           }
       }
       new Thread(new Test2()).start();
       //匿名内部類 必須借助接口或者父類
       new Thread(new Runnable() {
           @Override
           public void run() {
               for(int i=0;i<10000;i++){
                   System.out.println("一遍聽課");
               }
           }
       }).start();
       //jdk8 簡化 lambda
       new Thread(()->{
               for(int i=0;i<10000;i++){
                   System.out.println("一遍聽課");
               }
           }
       ).start();
   }
}
           

三、線程狀态

(一)線程各狀态間的關系:
JAVA多線程的知識點概括(随筆)
JAVA多線程的知識點概括(随筆)
(二)sleep
package thread_study02;

 // sleep模拟網絡延時,放大了發生問題的可能性

public class BlockedSleep {
    public static void main(String[] args){
        //一份資源
        Web12306 web=new Web12306();
        //多個代理
        new Thread(web,"碼農").start();
        new Thread(web,"碼畜").start();
        new Thread(web,"碼蟥").start();
    }

}
class Web12306 implements Runnable {
    //票數
    private int ticketNum = 1000;
    @Override
    public void run() {
        while (true) {
            if (ticketNum < 0)
                break;
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->" + ticketNum--);
        }
    }
}
           
  • sleep(時間)指定目前線程阻塞的毫秒數;
  • sleep存在異常InterruptedException;
  • sleep到達後線程進入就緒狀态
  • 可模拟網絡延遲、倒計時等
  • 每一個對象都是一把鎖,sleep不會釋放鎖
(三)join

join合并線程,待此線程執行完成後,再執行其它線程,其它線程阻塞

例題:

package thread_study02;

 // join:合并線程, 插隊線程
public class BlockedJoin01 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(()->{
           for(int i=0;i<100;i++)
               System.out.println("lambda..."+i);
        });
        t.start();
        for (int i=0;i<100;i++){
            if(i==20){
                t.join();//插隊 main被阻塞了
            }
            System.out.println("main"+i);
        }
    }
}
           
(四)yield
package thread_study02;

public class YieldDemo02 {
    public static void main(String[] args) {
        new Thread(()->{
           for (int i=0;i<100;i++)
               System.out.println("lambda...");
        }).start();

        for (int i=0;i<100;i++) {
            if(i%20==0)
                Thread.yield();//main禮讓
            System.out.println("main....");
        }
    }
}
           
  • 禮讓線程,讓目前正在執行線程暫停
  • 不是阻塞線程,而是将線程從運作狀态轉入就緒狀态
  • 讓cpu排程器重新排程
(五)stop
  • 不使用JDK提供的stop()/destroy()方法(他們本身也被JDK廢棄了)
  • 提供一個boolean型的終止變量,當這個變量置為false,則終止線程的運作

    例題:

package thread_study02;

/**
 * 終止線程
 * 1.線程正常執行完畢-->次數
 * 2.外部幹涉-->辨別
 * 不要使用stop destroy
 */
public class TerminateThread implements Runnable {
    //1.建立辨別
    private boolean flag=true;
    private String name;

    public TerminateThread(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        int i=0;
        //關聯辨別,true-->運作 false-->停止
        while (flag)
            System.out.println(name+"-->"+i++);
    }
    //3.對外提供方法改變辨別
    public void terminate(){
        flag=false;
    }
    public static void main(String[] args) {
        TerminateThread tt=new TerminateThread("C羅");
        new Thread(tt).start();
        for(int i=0;i<=999;i++){
            if(i==888){
                tt.terminate();//線程的終止
                System.out.println("tt game over");
            }
            System.out.println("main"+i);
        }
    }
}
           
(六)priority

優先級低隻是意味着獲得排程的機率低。并不是絕對先調用優先級高後調用優先級低的線程。

例子:

package thread_study02;

/**
 * 線程的優先級1-10
 * 1.NORM_PRIORITY  5
 * 2.MIN_PRIORITY 1
 * 3.MAX_PRIORITY 10
 * 機率,不代表絕對先後
 */
public class PriorityTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getPriority());
        MyPriority mp=new MyPriority();

        Thread t1=new Thread(mp,"adidas");
        Thread t2=new Thread(mp,"NINE");
        Thread t3=new Thread(mp,"匡威");
        Thread t4=new Thread(mp,"puma");
        Thread t5=new Thread(mp,"李甯");
        Thread t6=new Thread(mp,"回力");
        //設定優先級要在運作前
        t1.setPriority(10);
        t2.setPriority(Thread.MAX_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);
        t4.setPriority(1);
        t5.setPriority(Thread.MIN_PRIORITY);
        t6.setPriority(Thread.MIN_PRIORITY);
        t1.start();t2.start();t3.start();t4.start();t5.start();t6.start();
    }
}
class MyPriority implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+Thread.currentThread().getPriority());
        Thread.yield();
    }
}
           
(七)daemon
  • 線程分為使用者程序和守護程序
  • 虛拟機必須確定使用者程序執行完畢
  • 虛拟機不用等待守護程序執行完畢

    例子:

package thread_study02;

/**
 * 守護線程:是為使用者線程服務的;jvm停止不用等待守護線程執行完畢
 * 預設:使用者線程 jvm等待使用者線程執行完畢
 */
public class DaemonTest {
    public static void main(String[] args) {
        God god=new God();
        You you=new You();
        Thread t=new Thread(god);
        t.setDaemon(true);//将使用者程序調整為守護程序
        t.start();
        new Thread(you).start();
    }
}
class You implements Runnable{
    @Override
    public void run() {
        for (int i=1;i<365*100;i++){
            System.out.println("happy life...");
        }
        System.out.println("ooooooo");
    }
}
class God implements Runnable{
    @Override
    public void run() {
        for(;true;){
            System.out.println("bless you");
        }
    }
}
           
(八)其它常用方法

例子:

package thread_study02;

/**
 * 其他方法
 * isAlive:線程是否還活着
 * Thread.currentThread();目前線程
 * setName、getName():代理名稱
 */
public class InfoTest {
    public static void main(String[] args) throws InterruptedException {
        System.out.println(Thread.currentThread().isAlive());

        //設定名稱:真實角色+代理角色
        MyInfo info=new MyInfo("戰鬥機");
        Thread t=new Thread(info);
        t.setName("公雞");
        t.start();
        Thread.sleep(1000);
        System.out.println(t.isAlive());
    }
}
class MyInfo implements Runnable{
    private String name;
    public MyInfo(String name) {
        this.name = name;
    }
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"-->"+name);
    }
}
           

四、線程同步

(一)synchronized方法

synchronized方法控制對“成員變量|類變量”對象的通路:每個對象對應一把鎖,每個synchronized方法都必須獲得調用該方法的對象的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法傳回時才将鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀态。

例子:

package thread_study03;

/**
 *線程安全:在并發時保證資料的正确性、效率盡可能高
 * synchronized同步方法
 */
public class SynsafeTest01 {
    public static void main(String[] args){
        //一份資源
        SafeWeb12306 web=new SafeWeb12306();
        //多個代理
        new Thread(web,"碼農").start();
        new Thread(web,"碼畜").start();
        new Thread(web,"碼蟥").start();
    }
}
class SafeWeb12306 implements Runnable {
    //票數
    private int ticketNum=10;
    private boolean flag=true;
    @Override
    public void run() {
        while(flag){
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            test();
        }
    }
    //線程安全 同步
    public synchronized void test(){
        if(ticketNum<0) {
            flag = false;
            return;
        }
        try {
            Thread.sleep(300);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"-->"+ticketNum--);
    }
}
           
(二)synchronized塊

同步塊:synchronized(obj){ },obj稱之為同步螢幕

  • obj可以是任何對象,但是推薦使用共享資源作為同步螢幕。
  • 同步方法中無需指定同步螢幕,因為同步方法的同步螢幕是this即該對象本身,或class即類的模子。

    同步螢幕的執行過程

  • 第一個線程通路,鎖定同步螢幕,執行其中代碼
  • 第二個線程通路,發現同步螢幕被鎖定,無法通路
  • 第一個線程通路完畢,解鎖同步螢幕
  • 第二個線程通路,發現同步螢幕未鎖,鎖定并通路

    例子:

package thread_study03;

public class SynBlockTest01 {
    public static void main(String[] args) {
        //賬戶
        Account account=new Account(100,"結婚禮金");
        SynDrawing you=new SynDrawing(account,80,"可悲的你");
        SynDrawing wife=new SynDrawing(account,90,"happy的她");
        you.start();
        wife.start();
    }
}
//模拟取款 線程安全
class SynDrawing extends Thread{
    Account account;//取錢的賬戶
    int drawingMoney;//取得錢數
    int packetTotal=0;//口袋的總數
    public SynDrawing(Account account, int drawingMoney, String name) {
        super(name);
        this.account = account;
        this.drawingMoney = drawingMoney;
    }
    @Override
    public void run() {
        test();
    }
    //目标鎖定account
    public void test(){
        //提高性能代碼
        if(account.money<=0){
            return;
        }
        synchronized (account) {
            if (account.money - drawingMoney < 0) {
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            account.money -= drawingMoney;
            packetTotal += drawingMoney;
            System.out.println(this.getName() + "口袋的錢為:" + packetTotal);
            System.out.println(this.getName() + "賬戶餘額為:" + account.money);
        }
    }
}
           
(三)死鎖

多個線程各自占有一些共享資源,并且互相等待其他線程占有的資源才能進行,而導緻兩個或多個線程都在等待對方釋放資源,都停止執行的情形。

例題:

package thread_study03;
/**
 * 死鎖: 過多的同步可能造成互相不釋放資源
 * 進而互相等待,一般發生于同步中持有多個對象的鎖
 * 避免: 不要在同一個代碼塊中,同時持有多個對象的鎖
 */
public class DeadLock {

	public static void main(String[] args) {
		Markup g1 = new Markup(1,"張柏芝");
		Markup g2 = new Markup(0,"王菲");
		g1.start();
		g2.start();
	}
}
//口紅
class Lipstick{
}
//鏡子
class Mirror{
}
//化妝
class Markup extends Thread{ 
	static Lipstick lipstick = new Lipstick();
	static Mirror mirror = new Mirror();
	//選擇
	int choice;
	//名字
	String girl;
	public Markup(int choice,String girl) {
		this.choice = choice;
		this.girl = girl;
	}

	@Override
	public void run() {
		//化妝
		markup();
	}
	//互相持有對方的對象鎖-->可能造成死鎖
	private void markup() {
		if(choice==0) {
			synchronized(lipstick) { //獲得口紅的鎖
				System.out.println(this.girl+"塗口紅");
				//1秒後想擁有鏡子的鎖
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				/*
				synchronized(mirror) {
					System.out.println(this.girl+"照鏡子");
				}*/			
			}
			synchronized(mirror) {
				System.out.println(this.girl+"照鏡子");
			}		
		}else {
				synchronized(mirror) { //獲得鏡子的鎖
					System.out.println(this.girl+"照鏡子");
					//2秒後想擁有口紅的鎖
					try {
						Thread.sleep(2000);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					/*
					synchronized(lipstick) {
						System.out.println(this.girl+"塗口紅");
					}	*/	
			}
				synchronized(lipstick) {
					System.out.println(this.girl+"塗口紅");
				}
		}
	}
}
           

五、線程協作

線程通信

(一)管道法

例題:

package thread_study04;
/**
 * 協作模型:生産者消費者實作方式一:管程法
 * 借助緩沖區
 */
public class CoTest01 {
	public static void main(String[] args) {
		SynContainer container = new SynContainer();
		new Productor(container).start();
		new Consumer(container).start();
	}
}
//生産者
class Productor extends Thread{
	SynContainer container  ;	
	public Productor(SynContainer container) {
		this.container = container;
	}
	public void run() {
		//生産
		for(int i=0;i<100;i++) {
			System.out.println("生産-->"+i+"個饅頭");
			container.push(new Steamedbun(i) );
		}
	}
}
//消費者
class Consumer extends Thread{
	SynContainer container  ;	
	public Consumer(SynContainer container) {
		this.container = container;
	}
	public void run() {
		//消費
		for(int i=0;i<100;i++) {
			System.out.println("消費-->"+container.pop().id+"個饅頭");
		}
	}
}
//緩沖區
class SynContainer{
	Steamedbun[] buns = new Steamedbun[10]; //存儲容器
	int count = 0; //計數器
	//存儲 生産
	public synchronized void push(Steamedbun bun) {
		//何時能生産  容器存在空間
		//不能生産 隻有等待
		if(count == buns.length) {
			try {
				this.wait(); //線程阻塞  消費者通知生産解除
			} catch (InterruptedException e) {
			}
		}
		//存在空間 可以生産
		buns[count] = bun;
		count++;
		//存在資料了,可以通知消費了
		this.notifyAll();
	}
	//擷取 消費
	public synchronized Steamedbun pop() {
		//何時消費 容器中是否存在資料
		//沒有資料 隻有等待
		if(count == 0) {
			try {
				this.wait(); //線程阻塞  生産者通知消費解除
			} catch (InterruptedException e) {
			}
		}
		//存在資料可以消費
		count --;
		Steamedbun bun = buns[count] ;		
		this.notifyAll(); //存在空間了,可以喚醒對方生産了
		return bun;
	}
}
//饅頭
class Steamedbun{
	int id;
	public Steamedbun(int id) {
		this.id = id;
	}	
}
           

提示:與sleep不同,wait會釋放鎖

(二)信号燈法

例題:

package thread_study04;
/**
 * 協作模型:生産者消費者實作方式二:信号燈法
 * 借助标志位
 */
public class CoTest02 {
	public static void main(String[] args) {
		Tv tv  =new Tv();
		new Player(tv).start();
		new Watcher(tv).start();
	}
}
//生産者 演員
class Player extends Thread{
	Tv tv;	
	public Player(Tv tv) {
		this.tv = tv;
	}
	public void run() {
		for(int i=0;i<20;i++) {
			if(i%2==0) {
				this.tv.play("奇葩說");
			}else {
				this.tv.play("太污了,喝瓶立白洗洗嘴");
			}
		}
	}
}
//消費者 觀衆
class Watcher extends Thread{
	Tv tv;	
	public Watcher(Tv tv) {
		this.tv = tv;
	}
	public void run() {
		for(int i=0;i<20;i++) {
			tv.watch();
		}
	}
}
//同一個資源 電視
class Tv{
	String voice;
	//信号燈
	//T 表示演員表演 觀衆等待
	//F 表示觀衆觀看 演員等待
	boolean flag = true;
	//表演
	public  synchronized void play(String voice) {
		//演員等待
		if(!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}		
		//表演
		System.out.println("表演了:"+voice);
		this.voice = voice;
		//喚醒
		this.notifyAll();
		//切換标志
		this.flag =!this.flag;
	}
	//觀看
	public synchronized  void watch() {
		//觀衆等待
		if(flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		//觀看
		System.out.println("聽到了:"+voice);
		//喚醒
		this.notifyAll();
		//切換标志
		this.flag =!this.flag;
	}
}
           

六、進階主題

(一)任務定時排程

例題一:

package thread_study04;

import java.util.Calendar;
import java.util.GregorianCalendar;
import java.util.Timer;
import java.util.TimerTask;
 // 任務排程: Timer(本身就是一個線程) 和TimerTask類(實作了Runnable接口,具備多線程的能力)
public class TimerTest01 {

	public static void main(String[] args) {
		Timer timer = new Timer();
		//執行安排
		//timer.schedule(new MyTask(), 1000);  //執行任務一次
		//timer.schedule(new MyTask(), 1000,200); //執行多次
		Calendar cal = new GregorianCalendar(2099999,12,31,21,53,54);
		timer.schedule(new MyTask(), cal.getTime(),200); //指定時間
	}

}
//任務類
class  MyTask extends TimerTask{
	@Override
	public void run() {
		for(int i=0;i<10;i++) {
			System.out.println("放空大腦休息一會");
		}
		System.out.println("------end-------");
	}
	
}
           

例題二(比較難,暫時先做了解):

package thread_study04;

import static org.quartz.DateBuilder.evenSecondDateAfterNow;
import static org.quartz.JobBuilder.newJob;
import static org.quartz.TriggerBuilder.newTrigger;
import static org.quartz.SimpleScheduleBuilder.simpleSchedule;
import org.quartz.JobDetail;
import org.quartz.Scheduler;
import org.quartz.SchedulerFactory;
import org.quartz.Trigger;
import org.quartz.impl.StdSchedulerFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;
 // quartz學習入門
public class QuartzTest {

  public void run() throws Exception {
    // 1、建立 Scheduler的工廠
    SchedulerFactory sf = new StdSchedulerFactory();
    //2、從工廠中擷取排程器
    Scheduler sched = sf.getScheduler();  
    // 3、建立JobDetail
    JobDetail job = newJob(HelloJob.class).withIdentity("job1", "group1").build();
    // 時間
    Date runTime = evenSecondDateAfterNow();
    // 4、觸發條件
    //Trigger trigger = newTrigger().withIdentity("trigger1", "group1").startAt(runTime).build();
    Trigger trigger  = newTrigger().withIdentity("trigger1", "group1").startAt(runTime)
            .withSchedule(simpleSchedule().withIntervalInSeconds(5).withRepeatCount(3)).build();
    // 5、注冊任務和觸發條件
    sched.scheduleJob(job, trigger);

    // 6、啟動
    sched.start();
    try {
      // 100秒後停止
      Thread.sleep(100L * 1000L);
    } catch (Exception e) {
    }
    sched.shutdown(true);
  }

  public static void main(String[] args) throws Exception {
    QuartzTest example = new QuartzTest();
    example.run();
  }
}
           
(二)HappenBefore(指令重排)

你寫的代碼很可能根本沒按你期望的順序執行,因為編譯器和CPU會嘗試重排指令使得代碼更快地運作

例題:

package thread_study04;
/**
 * 指令重排: 代碼執行順序與預期不一緻
 * 目的:提高性能
 */
public class HappenBefore {
	//變量1
	private  static int a = 0;
	//變量2
	private static boolean flag = false;
	public static void main(String[] args) throws InterruptedException {
		for(int i=0;i<10;i++) {
			a = 0;
			flag = false;
			
			//線程1 更改資料
			Thread t1 = new Thread(()->{
				a = 1;
				flag = true;
			}) ;
			//線程2 讀取資料
			Thread t2 = new Thread(()->{
				if(flag) {
					a *=1;
				}
				//指令重排
				if(a == 0) {
					System.out.println("happen before a->"+a);
				}
			}) ;
			t1.start();
			t2.start();
			//合并線程
			t1.join();
			t2.join();
		}
	}
}
           
(三)Volatile
  • 線程對變量進行修改之後,要立刻寫回主記憶體;
  • 線程對變量讀取的時候,要從主記憶體中讀,而不是緩存。
  • 它不能保證原子性。

    例題:

package com.sxt.others;
 // volatile用于保證資料的同步,也就是可見性
public class VolatileTest {
	private volatile static int num = 0;
	public static void main(String[] args) throws InterruptedException {
		new Thread(()->{
			while(num==0) { //此處不要編寫代碼
				
			}
		}) .start();
		
		Thread.sleep(1000);
		num = 1;
	}
}
           
(四)單例模式

ThreadLocal能夠放一個線程級别的變量,其本身能夠被多個線程共享使用,并且又能夠達到線程安全的目的。說白了,ThreadLocal就是想在多線程環境下去保證成員變量的安全。

例題:

package com.sxt.others;
/**
 * ThreadLocal:每個線程自身的存儲本地、局部區域
 *  get/set/initialValue
 */
public class ThreadLocalTest01 {
	//private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> ();
	//更改初始化值
	/*private static ThreadLocal<Integer> threadLocal = new ThreadLocal<> () {
		protected Integer initialValue() {
			return 200;
		}; 
	};*/
	private static ThreadLocal<Integer> threadLocal = ThreadLocal.withInitial(()-> 200);
	public static void main(String[] args) {
		//擷取值
		System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());		
		//設定值
		threadLocal.set(99);
		System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());
		
		new Thread(new MyRun()).start();
		new Thread(new MyRun()).start();
	}	
	public static  class MyRun implements Runnable{
		public void run() {
			threadLocal.set((int)(Math.random()*99));
			System.out.println(Thread.currentThread().getName()+"-->"+threadLocal.get());		
		}
	}	
}
           
  • 每個線程自身的資料,更改不會影響其他線程
  • ThreadLocal要分析上下文 環境 起點1.構造器: 哪裡調用 就屬于哪裡 找線程體 2.run方法:本線程自身的
  • InheritableThreadLocal:繼承上下文 環境的資料 ,拷貝一份給子線程
(五)可重入鎖

鎖作為并發共享資料保證一緻性的工具,大多數内置鎖都是可重入的,也就是說,如果某個線程試圖擷取一個已經由他持有的鎖時,那麼這個請求會立刻成功,并且會将這個鎖的計數值加1,而當線程退出同步代碼塊時,計數器将會遞減,當計數值等于0時,鎖釋放。如果沒有可重入鎖額支援,在第二次企圖獲得鎖時将會進入死鎖狀态。

例題:

package com.sxt.others;

 // 可重入鎖: 鎖可以延續使用 + 計數器
public class LockTest03 {
	ReLock lock = new ReLock();
	public void a() throws InterruptedException {
		lock.lock();
		System.out.println(lock.getHoldCount());
		doSomething();
		lock.unlock();
		System.out.println(lock.getHoldCount());
	}
	//不可重入
	public void doSomething() throws InterruptedException {
		lock.lock();
		System.out.println(lock.getHoldCount());
		//...................
		lock.unlock();
		System.out.println(lock.getHoldCount());
	}
	public static void main(String[] args) throws InterruptedException {
		LockTest03 test = new LockTest03();
		test.a();			
		Thread.sleep(1000);		
		System.out.println(test.lock.getHoldCount());
	}

}
// 可重入鎖
class ReLock{
	//是否占用
	private boolean isLocked = false;
	private Thread lockedBy = null; //存儲線程
	private int holdCount = 0;
	//使用鎖
	public synchronized void lock() throws InterruptedException {
		Thread t = Thread.currentThread();
		while(isLocked && lockedBy != t) {
			wait();
		}
		
		isLocked = true;
		lockedBy = t;
		holdCount ++;
	}
	//釋放鎖
	public synchronized void unlock() {
		if(Thread.currentThread() == lockedBy) {
			holdCount --;
			if(holdCount ==0) {
				isLocked = false;
				notify();
				lockedBy = null;
			}		
		}		
	}
	public int getHoldCount() {
		return holdCount;
	}
}
           
(六)CAS

鎖分為兩類:

  • 悲觀鎖:synchronized是獨占鎖,即悲觀鎖,會導緻其它所有需要鎖的線程挂起,等待持有鎖的線程釋放鎖。
  • 樂觀鎖:每次不加鎖而是假設沒有沖突而去完成某項操作,如果因為沖突失敗就重試,知道成功為止。

    例題:

package com.sxt.others;

import java.util.concurrent.atomic.AtomicInteger;

 // CAS:比較并交換
public class CAS {
	//庫存
	private static AtomicInteger stock = new AtomicInteger(5);
	public static void main(String[] args) {
		for(int i=0;i<5;i++) {
			new Thread(()->{
				//模拟網絡延時
				try {
					Thread.sleep(1000);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				Integer left = stock.decrementAndGet();
				if(left<1) {
					System.out.println("搶完了...");
					return ;
				}
				System.out.print(Thread.currentThread().getName()+"搶了一件商品");
				System.out.println("-->還剩"+left);
			}) .start();
		}
	}
}
           
  • 有三個值一個目前值V、舊的預期值A、将更新的值B。首先要擷取記憶體當中目前的記憶體值V,再将記憶體值V和原值A作比較,要是相等就修改為要修改的值B并傳回true,否則什麼也不做并傳回false。
  • CAS是一組原子操作,不會被外部打斷。
  • 屬于硬體級别的操作,效率比加鎖高。