JAVA多線程知識
-
-
-
- 一、線程的基本概念
- 二、線程的建立
-
- (一)繼承Thread類
- (二)實作Runnable接口
- (三)實作Callable接口
- (四)補充
- 三、線程狀态
-
- (一)線程各狀态間的關系:
- (二)sleep
- (三)join
- (四)yield
- (五)stop
- (六)priority
- (七)daemon
- (八)其它常用方法
- 四、線程同步
-
- (一)synchronized方法
- (二)synchronized塊
- (三)死鎖
- 五、線程協作
-
- (一)管道法
- (二)信号燈法
- 六、進階主題
-
- (一)任務定時排程
- (二)HappenBefore(指令重排)
- (三)Volatile
- (四)單例模式
- (五)可重入鎖
- (六)CAS
-
-
一、線程的基本概念
- 線程就是獨立的執行路徑;
- 方法間的調用,即從哪裡來到哪裡去,是閉合的一條路徑;多線程的使用則開辟了多條路徑
- 在程式運作時,即使沒有自己建立線程,背景也會存在多個線程,如gc程序、主線程(main為系統的入口點,用于執行整個程式);
- 在一個程序中,如果開辟了多個線程,線程的運作由排程器安排排程,排程器是與作業系統緊密相關的,先後順序是不能人為的幹預的;
- 很多多線程是模拟出來的,真正的多線程是指有多個cpu,即多核。如果是模拟出來的多線程,即一個cpu的情況下,在同一個時間點,cpu隻能執行一個代碼,因為切換得很快,是以就有同時執行得錯覺;
二、線程的建立
(一)繼承Thread類
Thread的構造器:

例題:
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();
}
}
三、線程狀态
(一)線程各狀态間的關系:
(二)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是一組原子操作,不會被外部打斷。
- 屬于硬體級别的操作,效率比加鎖高。