在java.lang.Thread
下已經(jīng)有封裝好的線程類(lèi)
線程的生命周期
1.新建狀態(tài)(NEW)
實(shí)例化線程的時(shí)候
2.運(yùn)行狀態(tài)(RUNNABLE)
運(yùn)行狀態(tài)中又分就緒狀態(tài)和可運(yùn)行狀態(tài)
(1)就緒狀態(tài):調(diào)用線程的start()
方法之后喜最,等待JVM調(diào)度的過(guò)程,要注意的是一個(gè)線程只能調(diào)用一次start()
方法庄蹋,再次調(diào)用則會(huì)報(bào)錯(cuò)瞬内,然后如果需要再次執(zhí)行就需要重新實(shí)例化一個(gè)線程
(2)可運(yùn)行狀態(tài):線程對(duì)象獲得JVM調(diào)度
3.阻塞狀態(tài)(BLOCKED)
線程放棄CPU分配,暫時(shí)停止運(yùn)行限书,此時(shí)JVM不會(huì)給線程分配CPU虫蝶,需等線程進(jìn)入就緒狀態(tài)后才能再得到分配調(diào)度
4.等待狀態(tài)(WAITING)
該狀態(tài)下只能等待其他線程喚醒,使用wait()
方法進(jìn)入該狀態(tài)
5.計(jì)時(shí)等待狀態(tài)(TIMED_WAITING)
進(jìn)入一定時(shí)間的等待狀態(tài)倦西,使用wait(time)
/Thread.sleep(time)
進(jìn)入該狀態(tài)
6.終止?fàn)顟B(tài)(TERMINATED)
當(dāng)執(zhí)行完run()
方法或遇到異常退出后的狀態(tài)
創(chuàng)建線程
方法一
1.定義一個(gè)類(lèi)繼承于Thread
2.重寫(xiě)public void run(){}
方法
3.生成該對(duì)象
舉例:
public class Test {
public static void main(String[] args){
Count c = new Count();
c.start(); //直接啟動(dòng)
for(int i=0; i<100; i++)
System.out.println("Main:"+i);
}
}
class Count extends Thread { //繼承Thread
public void run(){ //重寫(xiě)run()
for(int i=0; i<100; i++)
System.out.println("Count:"+i);
}
}
方法二
1.定義線程類(lèi)繼承Runnable
接口能真,里面只有一個(gè)public void run(){}
方法
2.實(shí)例化一個(gè)線程類(lèi),然后將Runnable
接口傳入
其中使用Runnable
接口可以為多個(gè)線程提供共享的數(shù)據(jù)扰柠,且在Runnable
接口類(lèi)的run
方法中可以使用Thread
的靜態(tài)方法粉铐,如currentThread()
為獲取當(dāng)前線程的引用
舉例:
public class Test {
public static void main(String[] args){
Count c = new Count(); //實(shí)例化
Thread t = new Thread(c); //實(shí)例化線程
t.start(); //啟動(dòng)線程
for(int i=0; i<100; i++)
System.out.println("Main:"+i);
}
}
class Count implements Runnable { //繼承Runnable
public void run(){ //重寫(xiě)run()
for(int i=0; i<100; i++)
System.out.println("Count:"+i);
}
}
兩種方式對(duì)比
在第一種方式中,每次都得實(shí)例化一個(gè)對(duì)象卤档,所以每個(gè)的數(shù)據(jù)都是單獨(dú)的蝙泼,而且類(lèi)是單繼承的,此時(shí)就不能再繼承其他類(lèi)了劝枣,但是繼承線程類(lèi)后就可以直接調(diào)用其下的一些方法汤踏,操作上也就更加方便织鲸;在第二種方式中則可以實(shí)例化一個(gè)對(duì)象,然后將這一個(gè)對(duì)象放入多個(gè)線程當(dāng)中溪胶,即可實(shí)現(xiàn)數(shù)據(jù)共享搂擦,但是因?yàn)椴皇抢^承線程類(lèi),所以沒(méi)有線程類(lèi)的方法控制哗脖,如果需要使用那些方法瀑踢,則需要通過(guò)Thread.currentThread()
來(lái)獲得當(dāng)前線程,然后調(diào)用那些方法才避。第二種線程數(shù)據(jù)共享及調(diào)用線程方法舉例:
public class Test {
public static void main(String[] args) {
System.out.println(1);
ThreadTest tt = new ThreadTest();
new Thread(tt).start(); //不取名則默認(rèn)名Thread-0
new Thread(tt, "bbb").start();
new Thread(tt, "ccc").start();
}
}
class ThreadTest implements Runnable{
public int i = 10;
public void run() {
for(;i>0;i--)
System.out.println(Thread.currentThread().getName() + ":" + i); //獲取當(dāng)前線程
}
}
因?yàn)槿齻€(gè)線程都是公用i的值丘损,所以總共也只輸出10次
內(nèi)部類(lèi)創(chuàng)建線程
當(dāng)線程只是一次性使用的情況下,可以使用該方式創(chuàng)建線程工扎,舉例:
//方法1的內(nèi)部類(lèi)線程創(chuàng)建
new Thread() {
public void run() {
for (int i = 0; i < 500; i++)
System.out.println("Runnable:" + i);
}
}.start();
//方法2的內(nèi)部類(lèi)線程創(chuàng)建
new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 500; i++)
System.out.println("Thread:" + i);
}
}).start();
啟動(dòng)線程
使用start()
方法啟動(dòng)一個(gè)新線程,而run()
則是調(diào)用線程里的方法體衔蹲,并不會(huì)啟動(dòng)一個(gè)新線程
線程控制方法
即Thread
類(lèi)下的常用方法如下
getName()
獲取當(dāng)前線程名稱(chēng)
isAlive()
線程是否啟動(dòng)或者未終止
getPriority()
獲取線程優(yōu)先級(jí)數(shù)值
setPriority()
設(shè)置線程優(yōu)先級(jí)數(shù)值肢娘,優(yōu)先級(jí)越高,那么分配到的時(shí)間片越多舆驶,其中優(yōu)先級(jí)范圍為1~10橱健,默認(rèn)為5,一些優(yōu)先級(jí)常量:
Thread.MIN_PRIORITY = 1
Thread.MAX_PRIORITY = 10
Thread.NORM_PRIORITY = 5
sleep()
線程睡眠多少毫秒沙廉,一旦睡眠期間被打斷則會(huì)拋出InterruptedException
異常
interrupt()
打斷線程
join()
將當(dāng)前線程與該線程合并拘荡,即把join的線程先執(zhí)行完再執(zhí)行當(dāng)前線程,會(huì)拋出InterruptException
撬陵,例如在main函數(shù):
Count c = new Count();
c.start();
try{
c.join();
}catch(InterruptedException e){
;
}
于是會(huì)先把c線程的內(nèi)容執(zhí)行完然后繼續(xù)執(zhí)行主線程
setDaemon()
設(shè)置守護(hù)線程珊皿,此時(shí)只要主線程沒(méi)結(jié)束,該線程就不會(huì)結(jié)束巨税,必須在線程啟動(dòng)前設(shè)置蟋定,舉例:
Thread thread = new Thread(new Runnable() {
public void run() {
for (int i = 0; i < 100; i++)
System.out.println(Thread.currentThread().getName() + ":" + i);
}
});
thread.setDaemon(true); // 設(shè)置守護(hù)線程
thread.start();
isDaemon()
判斷是否為守護(hù)線程
yield()
讓出CPU,讓線程進(jìn)入就緒隊(duì)列等待調(diào)度草添,舉例:
public void run(){
for(int i=0; i<100; i++){
System.out.println("Count:"+i);
if(i%10 == 0)
yield(); //當(dāng)i為10的倍數(shù)時(shí)讓給別的線程調(diào)度
}
}
wait()
讓當(dāng)前線程進(jìn)入對(duì)象的等待池驶兜,釋放同步鎖,如果無(wú)參則是永久等待远寸,有參則可以設(shè)置等待時(shí)間抄淑。要注意的是這個(gè)和下面的notify()
方法都是java.lang.Object
下的方法,而不是Thread
下的方法驰后,并且需要在同步鎖的情況下才有用驻债,即synchronized (this){}
包著才行
notify()/notifyAll()
喚醒等待池的任意一個(gè)/所有等待線程,結(jié)合該方法和上面的方法定页,基本的生產(chǎn)者消費(fèi)者模型如下:
public class Test {
public static void main(String[] args) throws Exception {
Resource r = new Resource();
new Thread(new Producer(r), "Producer").start(); // 生產(chǎn)者
new Thread(new Consumer(r), "Consumer1").start(); // 消費(fèi)者
new Thread(new Consumer(r), "COnsumer2").start(); // 消費(fèi)者
}
}
class Resource {
int num = 0;
synchronized public void push() {
try {
if (num >= 5) { // 當(dāng)數(shù)量大于5個(gè)時(shí)進(jìn)入等待
this.wait();
}
System.out.println(Thread.currentThread().getName() + ":+1,"
+ ++num);
Thread.sleep(1000);
this.notifyAll(); // 喚醒所有消費(fèi)者線程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
synchronized public void pop() {
while (num <= 0) { // 當(dāng)數(shù)量大于0的時(shí)候取出,否則線程等待
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + ":-1," + --num);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.notifyAll(); // 喚醒生產(chǎn)者線程
}
}
class Producer implements Runnable {
Resource r = null;
public Producer(Resource r) {this.r = r;}
public void run() {
while (true)
this.r.push();
}
}
class Consumer implements Runnable {
Resource r = null;
public Consumer(Resource r) {this.r = r;}
public void run() {
while (true)
this.r.pop();
}
}
stop()
殺死線程贱枣,基本已經(jīng)被廢棄的方法
同步問(wèn)題
當(dāng)程序執(zhí)行時(shí)可能會(huì)因?yàn)榫€程的同步機(jī)制而導(dǎo)致一些問(wèn)題,比如下面的代碼:
public class Test {
public static void main(String[] args) {
Count c1 = new Count();
Count c2 = new Count();
c1.start();
c2.start();
}
}
class Count extends Thread{
static int num = 0; //計(jì)數(shù)變量
public void run(){
num++; //執(zhí)行自增后休眠一秒
try{
Thread.sleep(1000);
}catch (InterruptedException e) {
;
}
System.out.println(getName() + ":" + num);
}
}
結(jié)果為:
Thread-1:2
Thread-0:2
可以發(fā)現(xiàn)兩個(gè)并非一個(gè)是1一個(gè)是2颤专,而都是2纽哥,這是因?yàn)榈谝粋€(gè)自增后休眠,于是第二個(gè)也對(duì)num進(jìn)行了自增栖秕,結(jié)果導(dǎo)致輸出時(shí)數(shù)據(jù)都為2春塌,因此為了防止這種問(wèn)題,在執(zhí)行該部分(自增+休眠)時(shí)我們不應(yīng)該讓另一個(gè)線程摻合進(jìn)來(lái)簇捍,所以需要對(duì)這塊加一個(gè)同步鎖(保證同一時(shí)間內(nèi)只允許一個(gè)線程執(zhí)行的代碼塊)只壳。
同步修飾符
使用synchronize(當(dāng)前對(duì)象類(lèi)型){}
來(lái)將某一部分代碼塊包起來(lái)即可實(shí)現(xiàn)互斥,舉例對(duì)上面的類(lèi)進(jìn)行修改:
class Count extends Thread{
static int num = 0;
public void run(){
System.out.println(getName()); //這里不會(huì)互斥
synchronized (this.getClass()) { //這塊會(huì)互斥暑塑,用Count.class也行
num++;
try{Thread.sleep(1000);}
catch (InterruptedException e) {}
System.out.println(getName() + ":" + num);
}
}
}
結(jié)果為:
Thread-0
Thread-1
Thread-0:1
Thread-1:2
要注意這里的兩個(gè)線程里是不同的實(shí)例化對(duì)象吼句,所以用synchronized (this.getClass())
對(duì)其進(jìn)行互斥,即對(duì)當(dāng)前對(duì)象設(shè)置同步鎖事格,如果是對(duì)同一個(gè)對(duì)象進(jìn)行互斥則直接synchronize(this)
就行了惕艳,舉例:
public class Test {
public static void main(String[] args) {
Count c1 = new Count();
Thread t1 = new Thread(c1);
Thread t2 = new Thread(c1); //同一個(gè)c1
t1.start();
t2.start();
}
}
class Count implements Runnable{
static int num = 0;
public void run(){
System.out.println(Thread.currentThread().getName());
synchronized (this) { //用Count.class也行
num++;
try{Thread.sleep(1000);}
catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName() + ":" + num);
}
}
}
結(jié)果為:
Thread-0
Thread-1
Thread-0:1
Thread-1:2
注:
synchronized
雖然安全,但是性能損耗更低驹愚,所以盡量減少其的作用域远搪,原來(lái)的ArrayList
/Vector
、HashMap
/HashTable
逢捺、StringBuilder
/StringBuffer
等一個(gè)效率高谁鳍,一個(gè)更安全,其主要區(qū)別就是是否有使用synchronized
修飾符
同步方法
用synchronized
修飾的方法劫瞳,對(duì)于非靜態(tài)方法倘潜,同步鎖就是this
;對(duì)于靜態(tài)方法志于,則使用當(dāng)前方法所在類(lèi)的字節(jié)碼對(duì)象窍荧,即類(lèi).class
。
線程執(zhí)行的就是run()
方法恨憎,如果給該方法加上同步修飾符蕊退,那么整個(gè)run()
方法期間都無(wú)法執(zhí)行其他線程,也就相當(dāng)于單線程了憔恳,因此不要給run()
方法添加同步修飾符瓤荔。一般都是給要同步的部分單獨(dú)寫(xiě)一個(gè)方法,然后加上同步修飾符
同步鎖
在java.util.concurrent.locks
下提供了同步鎖的接口Lock
钥组,是比同步修飾符有更多功能
lock()/unlock()
獲取鎖/釋放鎖输硝,舉例:
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) throws Exception {
ThreadTest tt = new ThreadTest();
new Thread(tt, "aaa").start();
new Thread(tt, "bbb").start();
new Thread(tt, "ccc").start();
}
}
class ThreadTest implements Runnable {
public int num = 100;
public final Lock lock = new ReentrantLock(); //先定義同步鎖
public void run() {
for (int i = 0; i < 100; i++) {
// synchronized (this) {
lock.lock(); //換成lock()同步鎖
try {
if (num > 0) {
System.out.println(Thread.currentThread().getName() + ":" + num);
Thread.sleep(100);
num--;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock(); //最后記得關(guān)閉鎖
}
// }
}
}
}
newCondition()
返回一個(gè)Condition
接口對(duì)象,用來(lái)監(jiān)視線程程梦,里面有await()
/signal()
/signalAll()
方法用來(lái)替代wait()
/notify()
/notifyAll()
方法点把,舉例:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class test {
static int num = 100;
static int flag = 1;
static Lock lock = new ReentrantLock(); //一個(gè)鎖
static Condition c1 = lock.newCondition(); //三個(gè)監(jiān)視器
static Condition c2 = lock.newCondition();
static Condition c3 = lock.newCondition();
public static void main(String[] args) throws Exception {
new Thread() {
public void run() {
while (true)
print1();
};
}.start();
new Thread() {
public void run() {
while (true)
print2();
};
}.start();
new Thread() {
public void run() {
while (true)
print3();
};
}.start();
}
public static void print1() {
lock.lock();
while(flag != 1) {
try {
c1.await(); //等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(num == 0){
System.exit(0);
}
System.out.println("1:" + --num);
flag = 2;
c2.signalAll(); //喚醒
lock.unlock();
}
public static void print2() {
lock.lock();
while(flag != 2) {
try {
c2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(num == 0){
System.exit(0);
}
System.out.println("2:" + --num);
flag = 3;
c3.signalAll();
lock.unlock();
}
public static void print3() {
lock.lock();
while(flag != 3) {
try {
c3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(num == 0){
System.exit(0);
}
System.out.println("3:" + --num);
flag = 1;
c1.signalAll();
lock.unlock();
}
}
await()
相當(dāng)于wait()
signal()/signalAll()
相當(dāng)于notify()
/notifyAll()
線程組
使用ThreadGroup
類(lèi)來(lái)創(chuàng)建線程組對(duì)象橘荠,默認(rèn)情況下所有線程都屬于主線程組
getThreadGroup()
線程下的方法,用于獲取線程對(duì)象所屬組郎逃,其下還有getName()
方法用于獲取線程名哥童,舉例:
Thread t = new Thread(new ThreadTest());
System.out.println(t.getThreadGroup().getName()); //main
Thread(ThreadGroup, thread)
線程的實(shí)例化方法當(dāng)中支持傳入線程組和線程,此時(shí)該線程就會(huì)被加入到指定的線程組褒翰,舉例:
ThreadGroup tg = new ThreadGroup("no-main");
Thread t = new Thread(tg, new ThreadTest());
System.out.println(t.getThreadGroup().getName()); //no-main
線程池
通過(guò)ExecutorService
來(lái)存儲(chǔ)線程池對(duì)象贮懈,通過(guò)Executors
下newFixedThreadPool(int)
方法創(chuàng)建一定數(shù)量的線程池
submit()
將線程放入線程池并執(zhí)行,舉例:
ExecutorService pool = Executors.newFixedThreadPool(2); //線程池里有2個(gè)線程
Thread t1 = new Thread(new ThreadTest());
pool.submit(t1);
pool.submit(t1);
結(jié)果:
pool-1-thread-1
pool-1-thread-2
shutdown()
上面的代碼執(zhí)行結(jié)束后优训,線程池并沒(méi)有關(guān)閉朵你,所以需要該方法來(lái)關(guān)閉線程池