前言:
在面試過程中 關(guān)于多線程編程這一塊是經(jīng)常問到的 為了更好的理解關(guān)于多線程編程基礎(chǔ)特地的記錄此文章把思路理清楚
線程的生命周期
- 首先線程一般是這樣創(chuàng)建的:
new Thread(){run(){....}}.start();
new Thread(new Runnable(){run().....}).start();
2.來看一個(gè)經(jīng)典案例 生產(chǎn)者和消費(fèi)者的問題
static class Product{
public static String value;
}
//生產(chǎn)者線程
static class Producer extends Thread{
@Override
public void run() {
while (true){
if (Product.value==null){
Product.value="No"+System.currentTimeMillis();
System.out.println("產(chǎn)品:"+Product.value);
}
}
}
}
//消費(fèi)者線程
static class Consumer extends Thread{
@Override
public void run() {
while (true){
if (Product.value!=null){
Product.value=null;
System.out.println("產(chǎn)品已消費(fèi)");
}
}
}
}
//調(diào)用:
public static void main(String[] args){
new Producer().start();//開啟生產(chǎn)
new Consumer().start();//開啟消費(fèi)
}
產(chǎn)品:null
產(chǎn)品已消費(fèi)
產(chǎn)品:null
產(chǎn)品:No1492225732628
產(chǎn)品已消費(fèi)
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492225732628
產(chǎn)品:No1492225732629
讀操作會(huì)優(yōu)先讀取工作內(nèi)存的數(shù)據(jù)缔刹,如果工作內(nèi)存中不存在球涛,則從主內(nèi)存中拷貝一份數(shù)據(jù)到工作內(nèi)存中;寫操作只會(huì)修改工作內(nèi)存的副本數(shù)據(jù)校镐,這種情況下亿扁,其它線程就無法讀取變量的最新值
解決:
在解決這個(gè)問題的時(shí)候先來了解下Java中關(guān)于多線程中使用的一些關(guān)鍵字和一些方法的作用
關(guān)鍵字 | 作用 |
---|---|
volatile | 線程操作變量可見 |
Lock | Java6.0增加的線程同步鎖 |
synchronized | 線程同步鎖 |
wait() | 讓該線程處于等待狀態(tài) |
notify() | 喚醒處于wait的線程 |
notifyAll() | 喚醒所有處于wait狀態(tài)的線程 |
sleep() | 線程休眠 |
join() | 使當(dāng)線程處于阻塞狀態(tài) |
yield() | 讓出該線程的時(shí)間片給其他線程 |
注意:
1. wait()、notify()鸟廓、notifyAll()都必須在synchronized中執(zhí)行从祝,否則會(huì)拋出異常
2. wait()、notify()引谜、notifyAll()都是屬于超類Object的方法
2. 一個(gè)對(duì)象只有一個(gè)鎖(對(duì)象鎖和類鎖還是有區(qū)別的)
一. 使用volatile關(guān)鍵字:
static class Product{
//添加volatile關(guān)鍵字
public volatile static String value;
}
//調(diào)用
public static void main(String[] args){
new Producer().start();//開啟生產(chǎn)
new Consumer().start();//開啟消費(fèi)
}
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492229533263
產(chǎn)品已消費(fèi)
Volatile: 保證了不同線程對(duì)這個(gè)變量進(jìn)行操作時(shí)的可見性牍陌,即一個(gè)線程修改了某個(gè)變量的值,這新值對(duì)其他線程來說是立即可見的员咽。volatile關(guān)鍵字會(huì)強(qiáng)制將修改的值立即寫入主存毒涧,使線程的工作內(nèi)存中緩存變量行無效。
缺點(diǎn):但是不具備原子特性贝室。這就是說線程能夠自動(dòng)發(fā)現(xiàn) volatile 變量的最新值契讲。Volatile 變量可用于提供線程安全,但是只能應(yīng)用于非常有限的一組用例:多個(gè)變量之間或者某個(gè)變量的當(dāng)前值與修改后值之間沒有約束滑频。
二. 使用synchronized線程同步鎖
static class Product{
public static String value;
}
//生產(chǎn)者線程
static class Producer extends Thread{
Object object;
public Producer(Object object) {
this.object = object;
}
@Override
public void run() {
while (true) {
synchronized (object) {
if (Product.value == null) {
Product.value = "No" + System.currentTimeMillis();
System.out.println("產(chǎn)品:" + Product.value);
}
}
}
}
}
//消費(fèi)者線程
static class Consumer extends Thread{
Object object;
public Consumer(Object object) {
this.object=object;
}
@Override
public void run() {
while (true) {
synchronized (object) {
if (Product.value != null) {
Product.value = null;
System.out.println("產(chǎn)品已消費(fèi)");
}
}
}
}
}
//調(diào)用
public static void main(String[] args){
Object object=new Object();
new Producer(object).start();//開啟生產(chǎn)
new Consumer(object).start();//開啟消費(fèi)
}
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492244495050
上面通過synchronized對(duì)Object object=new Object();
加鎖 也叫對(duì)象鎖 實(shí)現(xiàn)鎖的互斥捡偏,當(dāng)生產(chǎn)線程生產(chǎn)產(chǎn)品的時(shí)候會(huì)對(duì)object
加鎖 消費(fèi)者線程會(huì)進(jìn)入阻塞狀態(tài) 直到生產(chǎn)線程完成產(chǎn)品的生產(chǎn)釋放鎖 反之也是同樣的道理。但是這樣只能被動(dòng)的喚醒線程的執(zhí)行 可以使用wait和notify來進(jìn)行主動(dòng)喚醒線程繼續(xù)執(zhí)行
//生產(chǎn)者線程
static class Producer extends Thread {
Object object;
public Producer(Object object) {
this.object = object;
}
@Override
public void run() {
while (true) {
//對(duì)象鎖
synchronized (object) {
if (Product.value != null) {
try {
object.wait();//產(chǎn)品還未消費(fèi) 進(jìn)入等待狀態(tài)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Product.value = "No" + System.currentTimeMillis();
System.out.println("產(chǎn)品:" + Product.value);
object.notify();//產(chǎn)品已生產(chǎn) 喚醒消費(fèi)者線程
}
}
}
}
//消費(fèi)者線程
static class Consumer extends Thread {
Object object;
public Consumer(Object object) {
this.object = object;
}
@Override
public void run() {
while (true) {
synchronized (object) {
if (Product.value == null) {
try {
object.wait();//產(chǎn)品為空 進(jìn)入等待狀態(tài)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Product.value = null;
System.out.println("產(chǎn)品已消費(fèi)");
object.notify();//產(chǎn)品已經(jīng)消費(fèi) 喚醒生產(chǎn)者線程生產(chǎn)
}
}
}
}
public static void main(String[] args){
Object object=new Object();
new Producer(object).start();//開啟生產(chǎn)
new Consumer(object).start();//開啟消費(fèi)
}
產(chǎn)品:No1492246274190
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492246274190
產(chǎn)品已消費(fèi)
產(chǎn)品:No1492246274190
產(chǎn)品已消費(fèi)
通過添加wait notify關(guān)鍵字去主動(dòng)喚醒生產(chǎn)者或消費(fèi)者線程的執(zhí)行
三.線程同步之ReentrantLock鎖
與synchronized關(guān)鍵字類似的同步功能峡迷,只是在使用時(shí)需要顯式地獲取和釋放鎖银伟,缺點(diǎn)就是缺少像synchronized那樣隱式獲取釋放鎖的便捷性,但是卻擁有了鎖獲取與釋放的可操作性绘搞,可中斷的獲取鎖以及超時(shí)獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性彤避。
Lock lock=new ReentrantLock();
public void setSpData(String name){
lock.lock();
try {
//線程同步操作 比如IO讀寫 等
}finally {
lock.unlock();
}
}
注意:
注意的是,千萬不要忘記調(diào)用unlock來釋放鎖看杭,否則可能會(huì)引發(fā)死鎖等問題,
而用synchronized忠藤,JVM將確保鎖會(huì)獲得自動(dòng)釋放,這也是為什么Lock沒有完全替代掉synchronized的原因
四.sleep(),join(),yield()的使用
sleep()讓線程休息指定的時(shí)間楼雹,時(shí)間一到就繼續(xù)運(yùn)行
new Thread(){
@Override
public void run() {
try {
Thread.sleep(3000);//休眠3秒 毫秒為單位
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}.start();
join()讓指定的線程先執(zhí)行完再執(zhí)行其他線程模孩,而且會(huì)阻塞主線程
static class B extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("線程一:" + 1);
}
}
}
static class A extends Thread {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("線程二:" + 1);
}
}
}
public static void main(String[] args) {
A a = new A();
B b = new B();
try {
a.start();//啟動(dòng)線程
a.join();//join 該線程優(yōu)先執(zhí)行 其他線程進(jìn)入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
b.start();//啟動(dòng)線程
b.join();//join 該線程優(yōu)先執(zhí)行 其他線程進(jìn)入等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
線程二:0
線程二:1
線程二:2
線程二:3
線程二:4
線程一:0
線程一:1
線程一:2
線程一:3
線程一:4
yield()將指定線程先禮讓一下別的線程的先執(zhí)行
注意:yield()會(huì)禮讓給相同優(yōu)先級(jí)的或者是優(yōu)先級(jí)更高的線程執(zhí)行尖阔,不過yield()這個(gè)方法只是把線程的執(zhí)行狀態(tài)打回準(zhǔn)備就緒狀態(tài),所以執(zhí)行了該方法后榨咐,有可能馬上又開始運(yùn)行介却,有可能等待很長時(shí)間
static class B extends Thread {
String name;
public B(String name) {
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + i);
if(i==3){
System.out.println("將時(shí)間片禮讓給別的線程");
Thread.yield();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public static void main(String[] args) {
new B("線程一:").start();
new B("線程二:").start();
}
線程一:0
線程一:1
線程一:2
線程一:3
將時(shí)間片禮讓給別的線程
線程二:0
線程二:1
線程二:2
線程二:3
將時(shí)間片禮讓給別的線程
線程二:4
線程一:4
其他:
- 線程優(yōu)先級(jí):通過setPriority(int priority)設(shè)置線程優(yōu)先級(jí)提高線程獲取時(shí)間的幾率(這只是提高線程優(yōu)先獲取時(shí)間片的幾率 而不是肯定優(yōu)先執(zhí)行) getPriority()獲取線程的優(yōu)先級(jí) 最高為10 最低為1 默認(rèn)為5
public static void main(String[] args) {
A a = new A("線程一:");
B b = new B("線程二:");
a.setPriority(3);
b.setPriority(10);
a.start();
b.start();
}
- 守護(hù)線程:前面所講的是用戶線程 其實(shí)還有一個(gè)守護(hù)線程,起作:用只要當(dāng)前JVM實(shí)例中尚存在任何一個(gè)非守護(hù)線程沒有結(jié)束,守護(hù)線程就全部工作块茁;只有當(dāng)最后一個(gè)非守護(hù)線程結(jié)束時(shí)齿坷,守護(hù)線程隨著JVM一同結(jié)束工作。守護(hù)線程的作用是為其他線程的運(yùn)行提供便利服務(wù)数焊,守護(hù)線程最典型的應(yīng)用就是 GC (垃圾回收器)永淌,它就是一個(gè)很稱職的守護(hù)者,在編寫程序時(shí)也可以自己設(shè)置守護(hù)線程佩耳。
static class B extends Thread {
String name;
public B(String name) {
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(name + i);
}
}
}
static class A extends Thread {
String name;
public A(String name) {
this.name=name;
}
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
System.out.println(name + i);
}
}
}
public static void main(String[] args) {
A a = new A("守護(hù)線程執(zhí)行:");
B b = new B("用戶線程執(zhí)行:");
a.setDaemon(true);//設(shè)置a線程為守護(hù)線程 必須在start()前設(shè)置 不然或有異常
a.isDaemon();//判斷a線程是否為守護(hù)線程
a.start();
b.start();
}
守護(hù)線程執(zhí)行:0
守護(hù)線程執(zhí)行:1
守護(hù)線程執(zhí)行:2
守護(hù)線程執(zhí)行:3
守護(hù)線程執(zhí)行:4
守護(hù)線程執(zhí)行:5
守護(hù)線程執(zhí)行:6
守護(hù)線程執(zhí)行:7
守護(hù)線程執(zhí)行:8
用戶線程執(zhí)行:0
用戶線程執(zhí)行:1
用戶線程執(zhí)行:2
用戶線程執(zhí)行:3
用戶線程執(zhí)行:4
守護(hù)線程執(zhí)行:9
守護(hù)線程執(zhí)行:10
守護(hù)線程執(zhí)行:11
守護(hù)線程執(zhí)行:12
守護(hù)線程執(zhí)行:13
守護(hù)線程執(zhí)行:14
守護(hù)線程執(zhí)行:15
守護(hù)線程執(zhí)行:16
守護(hù)線程執(zhí)行:17
Process finished with exit code 0
當(dāng)用戶線程執(zhí)行完畢后 JVM虛擬了退出前 守護(hù)線程隨著JVM一同結(jié)束工作
設(shè)置線程為守護(hù)線程 必須在start()前設(shè)置 不然或有異常