線程是操做系統可以進行運算調度的最小單位,它被包含在進程中,是進程中的實際運做單位。程序員能夠經過它進行多處理器編程。你能夠經過使用多線程對運算密集的任務提速。好比,若是一個線程完成一個任務須要100毫秒,那麼用十個線程完成此任務只需10毫秒。Java在語言層面對多線程提供了卓越的支持,他是一個很好的賣點。java
當一個網站遇到併發量很大的問題時,普通的系統很快就會達到性能瓶頸,而使用多線程能夠輕鬆的解決性能問題。程序員
There are two ways to create a new thread of execution. One is to declare a class to be a subclass of Thread. This subclass should override the run method of class Thread. An instance of the subclass can then be allocated and started. For example, a thread that computes primes larger than a stated value could be written as follows數據庫
The other way to create a thread is to declare a class that implements the Runnable interface. That class then implements the run method. An instance of the class can then be allocated, passed as an argument when creating Thread, and started. The same example in this other style looks like the following編程
上面的兩段引用出自Oracle官方文檔,將這兩段英文翻譯過來意思就是說實現線程的方式有兩個種,第一種是繼承Thread
類的方式,另外一種就是實現Runnable
接口。緩存
/**
* 用Thread方式實現線程
*/
public class ThreadStyle extends Thread {
public static void main(String[] args) {
ThreadStyle thread = new ThreadStyle();
thread.start();
}
@Override
public void run() {
System.out.println("用Thread類實現線程");
}
}
複製代碼
/**
* Runnable方式建立線程
*/
public class RunnableStyle implements Runnable {
public static void main(String[] args) {
Thread thread = new Thread(new RunnableStyle());
thread.start();
}
@Override
public void run() {
System.out.println("用Runnable方式實現線程");
}
}
複製代碼
Runnable
接口方式能夠下降代碼耦合度Thread
類後就沒法再繼承別的類,下降了類的擴展性Thread
類須要每實現一個線程類就進行一次建立,形成了較大的資源開銷。總結:綜上所述,實現線程採用Runnable
接口的方式比較好。安全
/**
* 同時使用Runnable和Thread兩種方式實現多線程
*/
public class BothRunnableThread {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("我來自Runnable");
}
}) {
@Override
public void run() {
System.out.println("我來自Thread");
}
}.start();
}
}
複製代碼
運行結果bash
@Override
public void run() {
if (target != null) {
target.run();
}
}
複製代碼
實現Runnable
接口方式會要求重寫run()
方法,因此會執行其中的三行代碼,其中target
是一個private Runnable target;
,由於後面又覆蓋了一次Thread類的run
方法,因此if
判斷也就消失了就會直接執行本身在run
方法中的打印語句。多線程
總結:建立線程的方式只有構造Thread
類一種方法,可是實現Thread
類中的run
方法有兩種方式。併發
/**
* 對比start和run這兩種啓動線程的方式
*/
public class StartAndRunMethod {
public static void main(String[] args) {
Runnable runnable = () ->{
System.out.println(Thread.currentThread().getName());
};
runnable.run();
new Thread(runnable).start();
}
}
複製代碼
運行結果dom
start()
方法
調用
start()
方法意味着向JVM發起通知,若是有空能夠來我這裏執行一下麼,本質也就是經過調用start()
方法請求JVM運行此線程,可是調用該方法以後不必定就會當即運行,而是須要等到JVM有空執行時纔會執行。
public synchronized void start() {
//進行線程狀態的檢查,默認值是0
if (threadStatus != 0)
throw new IllegalThreadStateException();
//加入線程組
group.add(this);
boolean started = false;
try {
//執行線程的方法,是一個native方法
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
複製代碼
總結:執行start()
要經歷的步驟
start0()
方法@Override
public void run() {
//判斷傳入的Runnable是否爲空
if (target != null) {
//不爲空則啓動
target.run();
}
}
複製代碼
從這裏能夠看出上面直接調用run()
方法爲何會在主線程中執行,這是由於target
是空的因此不會啓動,這樣就和調用一個普通的方法沒有區別了。
在Java中中止線程的最好方式是使用interrupt,可是這樣僅僅會對須要中止的線程進行通知而不是直接停掉,線程是否的中止的權利屬於須要被中止的線程(什麼時候中止以及是否中止),這就須要請求中止方和被中止方都遵循一種編碼規範。
run()
方法中的代碼運行完畢(最多見)/**
* run方法內沒有sleep或wait方法時,中止線程
*/
public class RightWayStopThreadWithoutSleep implements Runnable {
@Override
public void run() {
int num = 0;
//沒有收到通知時進行循環操做
while (!Thread.currentThread().isInterrupted() && num <= Integer.MAX_VALUE / 2){
if (num % 10000 == 0){
System.out.println(num + "是10000的倍數");
}
num++;
}
System.out.println("任務運行結束了");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadWithoutSleep());
thread.start();
Thread.sleep(2000);
//發起通知
thread.interrupt();
}
}
複製代碼
/**
* 帶有sleep的中斷線程的中止方法
*/
public class RightWayStopThreadWhthSleep {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () ->{
int num = 0;
try {
while (num <= 300 && !Thread.currentThread().isInterrupted()){
if (num % 100 == 0){
System.out.println(num + "是100的倍數");
}
num++;
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(500);
thread.interrupt();
}
}
複製代碼
/**
* 若是在每次循環中都會sleep或wait,須要如何中止線程
*/
public class RightWayStopThreadWithSleepEveryLoop implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadWithSleepEveryLoop());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
@Override
public void run() {
try {
int num = 0;
while (num <= 30){
if (num % 10 == 0){
System.out.println(num + "是10的倍數");
}
num++;
Thread.sleep(50);
}
System.out.println("任務完成了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
注意:若是每次迭代都有阻塞狀態,這樣就不須要判斷是否收到中斷請求,由於在sleep
過程當中會對中斷進行響應
while
內try/catch
的問題/**
* 若是while裏面放try/catch,會致使中斷失效
*/
public class CantInterrupt {
public static void main(String[] args) throws InterruptedException {
Runnable runnable = () ->{
int num = 0;
while (num <= 10000){
if (num % 100 == 0 && !Thread.currentThread().isInterrupted()){
System.out.println(num + "是100的倍數");
}
num++;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
Thread thread = new Thread(runnable);
thread.start();
Thread.sleep(5000);
thread.interrupt();
}
}
複製代碼
注意:在while
循環中加入try/catch
沒法中止線程,由於在try/catch
中異常被捕獲後仍是不知足跳出循環的條件,interrupt
標記位被清除也就沒法檢查到被中斷的跡象,因此會繼續執行線程
原則:
拋出式
/**
* 最佳實踐:catch住InterruptedException後優先選擇在方法簽名中拋出異常,
* 那麼在run()方法中就會強制try/catch
*/
public class RightWayStopThreadInProd implements Runnable {
@Override
public void run() {
try {
while (true){
System.out.println("go");
throwInMethod();
}
} catch (InterruptedException e) {
//保存日誌
//中止程序
System.out.println("保存日誌、中止程序");
e.printStackTrace();
}
}
private void throwInMethod() throws InterruptedException {
Thread.sleep(2000);
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
複製代碼
注意:在方法中遇到異常應該首先選擇拋出,異常由run
方法進行處理,這樣能夠增長代碼的健壯性
恢復中斷式
/**
* 最佳實踐2:在catch語句中調用Thread.currentThread.interrupt()
* 來恢復中斷狀態,以便於在後續的執行中依然可以檢查到剛纔發生了中斷
*/
public class RightWayStopThreadInProd2 implements Runnable {
@Override
public void run() {
while (true) {
if (Thread.currentThread().isInterrupted()){
System.out.println("Interrupted,程序運行結束");
break;
}
reInterrupt();
}
}
private void reInterrupt() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new RightWayStopThreadInProd2());
thread.start();
Thread.sleep(1000);
thread.interrupt();
}
}
複製代碼
注意:若是在調用的方法中不拋出異常的話也能夠在catch塊再次調用Thread.currentThread().interrupt();
,這樣能夠從新設置中斷表示已經有中斷髮生,從而讓run
方法感知
stop
方法中止/**
* 錯誤的中止方法:用stop中止線程,會致使線程運行一半忽然中止,這樣沒有辦法完成一個基本單位(一個連隊)的操做,
* 會形成髒數據(有的連隊多領取或少領取裝備)
*/
public class StopThread implements Runnable {
@Override
public void run() {
/**
* 模擬指揮軍隊:一個5個連隊,每一個連隊10人,以連隊爲單位發放彈藥,叫到號
* 的士兵去領取
*/
for (int i = 0; i < 5; i++) {
System.out.println("連隊" + i + "開始領取裝備");
for (int j = 0; j < 10; j++) {
System.out.println(j);
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("連隊" + i + "領取完畢");
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new StopThread());
thread.start();
//1s以後戰爭爆發須要奔赴戰場
Thread.sleep(1000);
//中止領取
thread.stop();
}
}
複製代碼
volatile
設置boolean
標記位/**
* 演示用volatile的侷限part2 陷入阻塞時volatile沒法中止
* 此例中生產者的生產速度很快,可是消費者的消費速度很慢,因此阻塞隊列滿了之後,
* 生產者會阻塞,生產者會等待消費者進一步消費
*/
public class WrongWayVolatileCantStop {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<Integer> storage = new ArrayBlockingQueue<>(10);
Producer producer = new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = new Consumer(storage);
while (consumer.needMoreNums()){
System.out.println(consumer.storage.take() + "被消費了");
Thread.sleep(100);
}
System.out.println("消費者不須要更多數據了");
//一旦消費者不須要更多數據了,咱們應當讓消費者也停下來,可是實際狀況。。。
producer.canceled = true;
System.out.println(producer.canceled);
}
}
class Producer implements Runnable {
public volatile boolean canceled = false;
BlockingQueue<Integer> storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !canceled) {
if (num % 100 == 0) {
//是100的倍數時,將num放入阻塞隊列
storage.put(num);
System.out.println(num + "是100的倍數,被放入阻塞隊列");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生產者中止運行");
}
}
}
class Consumer {
BlockingQueue<Integer> storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
複製代碼
注意:若是線程長時間阻塞,這種方法就會失效
對上面方式的修復
/**
* 用中斷修復剛纔一直等待的問題
*/
public class WrongWayVolatileFixed {
public static void main(String[] args) throws InterruptedException {
WrongWayVolatileFixed body = new WrongWayVolatileFixed();
ArrayBlockingQueue storage = new ArrayBlockingQueue(10);
Producer producer = body.new Producer(storage);
Thread producerThread = new Thread(producer);
producerThread.start();
Thread.sleep(1000);
Consumer consumer = body.new Consumer(storage);
while (consumer.needMoreNums()) {
System.out.println(consumer.storage.take() + "被消費了");
Thread.sleep(100);
}
System.out.println("消費者不須要更多數據了");
producerThread.interrupt();
}
class Producer implements Runnable {
BlockingQueue storage;
public Producer(BlockingQueue storage) {
this.storage = storage;
}
@Override
public void run() {
int num = 0;
try {
while (num <= 100000 && !Thread.currentThread().isInterrupted()) {
//若是num是100的倍數,就將他添加到阻塞隊列
if (num % 100 == 0) {
storage.put(num);
System.out.println(num + "是100的倍數,被放入阻塞隊列");
}
num++;
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("生產者線程阻塞");
}
}
}
class Consumer {
BlockingQueue storage;
public Consumer(BlockingQueue storage) {
this.storage = storage;
}
public boolean needMoreNums() {
if (Math.random() > 0.95) {
return false;
}
return true;
}
}
}
複製代碼
答:須要根據狀況的不一樣而採起不一樣的方法
若是線程阻塞是由於調用了sleep()、wait()或join()
致使的,能夠經過拋出InterruptException
異常來喚醒,可是不能響應InterruptException
異常則沒法經過這種方法進行喚醒。 可是咱們能夠利用其餘能夠響應中斷的方法,好比: ReentrantLock.lockInterruptibly()
,關閉套接字使線程當即返回等方法來達到目的。 因此如何處理不可中斷的阻塞要視狀況而定。
start()
方法start()
方法就會進入Runnable狀態。synchronized
修飾的代碼後,而且該鎖已經被其它線程拿走了就會進入阻塞狀態。timeout
參數的wait()
方法,須要等待喚醒不然不會醒來run()
方法被意外終止。注意:通常而言把Blocked(被阻塞)、Waiting(等待)、Timed_Waiting(計時等待)
都稱爲阻塞,而不只僅是Blocked
。
做用:wait()
方法會讓線程進入等待狀態,若是想讓線程繼續執行必須知足一下四種方式中的一種
notify()
方法,本線程正好被喚醒notifyAll()
方法,全部線程都會被喚醒wait(long timout)
達到了參數的時間,若是傳入0會進入永久等待interrupt()
方法進行喚醒/**
* 展現wait和notify的基本用法
* 1.研究代碼執行順序
* 2.證實wait是釋放鎖的
*/
public class Wait {
public static Object object = new Object();
static class Thread1 extends Thread{
@Override
public void run() {
synchronized (object){
System.out.println(Thread.currentThread().getName()+"開始執行");
try {
object.wait(); //等待期間若是遇到中斷會拋出InterruptedException
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程"+Thread.currentThread().getName()+"獲取到了鎖");
}
}
}
static class Thread2 extends Thread{
@Override
public void run() {
synchronized (object){
object.notify();
System.out.println("線程"+Thread.currentThread().getName()+"調用了notify()");
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread1().start();
Thread.sleep(200);
new Thread2().start();
}
}
複製代碼
總結:wait()方法會釋放對象鎖,只有釋放了對象鎖其餘線程才能夠進入synchronized
代碼塊
/**
* 3個線程,線程1和線程2首先被阻塞,線程3去喚醒線程1和線程2
* start先執行不表明線程先啓動
*/
public class WaitNotifyAll implements Runnable {
private static final Object resourceA = new Object();
@Override
public void run() {
synchronized (resourceA){
System.out.println(Thread.currentThread().getName()+"獲得對象鎖");
try {
System.out.println(Thread.currentThread().getName()+"等待下一次開始");
resourceA.wait();
System.out.println(Thread.currentThread().getName()+"立刻運行結束了");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
WaitNotifyAll waitNotifyAll = new WaitNotifyAll();
Thread threadA = new Thread(waitNotifyAll);
Thread threadB = new Thread(waitNotifyAll);
Thread threadC = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA){
resourceA.notifyAll();
System.out.println("線程C已經成功notify了");
}
}
});
threadA.start();
threadB.start();
Thread.sleep(200);
threadC.start();
}
}
複製代碼
總結:notify
只會喚醒等待線程中的一個而notifyAll
則會喚醒全部等待線程,在線程啓動時必定要等到線程進入等待狀態以後再進行喚醒
/**
* 證實wait只釋放當前那把鎖
*/
public class WaitNotifyReleaseOwnMonitor {
private static Object resourceA = new Object();
private static Object resourceB = new Object();
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (resourceA) {
System.out.println("ThreadA got resourceA lock.");
synchronized (resourceB) {
System.out.println("ThreadA got resourceB lock.");
try {
System.out.println("ThreadA releases resourceA lock.");
resourceA.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resourceA){
System.out.println("ThreadB got resourceA lock.");
System.out.println("ThreadB tries to ResourceB lock.");
synchronized (resourceB){
System.out.println("ThreadB got resourceB lock.");
}
}
}
});
thread1.start();
thread2.start();
}
}
複製代碼
總結:wait()
只釋放當前monitor
synchronized
時就會獲取到對象鎖,若是執行了
wait
方法後就會進入到等待集,在等待集中若是對某個線程執行了
notify
操做它就會再次回到入口集,若是使用的是
notifyAll
那麼等待集中的所有線程都會進入到入口集。
/**
* 用wait和notify來實現
*/
public class ProducerConsumerModel {
public static void main(String[] args) {
EventStorage eventStorage = new EventStorage();
Producer producer = new Producer(eventStorage);
Consumer consumer = new Consumer(eventStorage);
Thread thread1 = new Thread(producer);
Thread thread2 = new Thread(consumer);
thread1.start();
thread2.start();
}
}
class Producer implements Runnable {
private EventStorage storage;
public Producer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.put();
}
}
}
class Consumer implements Runnable {
private EventStorage storage;
public Consumer(EventStorage storage) {
this.storage = storage;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
storage.take();
}
}
}
class EventStorage {
private int maxSize;
private LinkedList<Date> storage;
public EventStorage() {
maxSize = 10;
storage = new LinkedList<>();
}
public synchronized void put() {
while (storage.size() == maxSize) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
storage.add(new Date());
System.out.println("生產出了一個商品,倉庫中有:" + storage.size() + "個商品");
notify();
}
public synchronized void take() {
while (storage.size() == 0) {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("取走了:" + storage.poll() + ",還剩下" + storage.size() + "個商品");
notify();
}
}
複製代碼
總結:生產者生產到10的時候就會進入wait
狀態,不然就會進行生產,消費者將隊列中的商品消費到0時就會進入wait
狀態,就這就會消費,當生產者生產出了商品就會notify
消費者,將其喚醒。反之消費者就會notify
喚醒生產者
/**
* 兩個線程交替打印0到100奇偶數
*/
public class WaitNotifyPrintOddEvenSyn {
private static int count;
private static final Object lock = new Object();
//新建2個線程
//1個只處理偶數,第2個處理奇數(用位運算)
//用synchronized進行通訊
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100){
synchronized (lock){
if ((count & 1) == 0){
System.out.println(Thread.currentThread().getName() + ":" + count);
count++;
}
}
}
}
},"偶數").start();
new Thread(new Runnable() {
@Override
public void run() {
while (count < 100){
synchronized (lock){
if ((count & 1) != 0){
System.out.println(Thread.currentThread().getName() + ":" + count);
count++;
}
}
}
}
},"奇數").start();
}
}
複製代碼
總結:這樣實現有可能形成同一個線程老是拿到對象鎖,可是if
判斷只會進一次,這樣就影響了效率,可使用wait/notify方式解決
/**
* 兩個線程交替打印0到100的奇偶數,使用wait/notify
*/
public class WaitNotifyPrintOddEvenWait {
public static void main(String[] args) throws InterruptedException {
new Thread(new TurningRunner(), "偶數").start();
Thread.sleep(100);
new Thread(new TurningRunner(), "奇數").start();
}
//1.一旦拿到鎖就打印
//2.打印完,喚醒其餘線程,而後再休眠
static class TurningRunner implements Runnable{
private static int count;
private static Object lock = new Object();
@Override
public void run() {
while (count < 100){
synchronized (lock){
//拿到鎖就打印
System.out.println(Thread.currentThread().getName() + ":" + count++);
lock.notify();
if (count < 100){
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
}
複製代碼
總結:拿到鎖就打印,打印完就喚醒若是知足條件就等待,這樣能夠提升程序的效率
做用:讓線程進入阻塞狀態,睡眠時不會佔用cpu資源。
注意:sleep
方法不會釋放synchronized
和lock
鎖
/**
* 展現線程sleep的時候不釋放synchronized的monitor,
* 等sleep的時間到了之後,正常結束後纔會釋放鎖
*/
public class SleepDontReleaseMonitor implements Runnable {
public static void main(String[] args) {
SleepDontReleaseMonitor sleepDontReleaseMonitor = new SleepDontReleaseMonitor();
new Thread(sleepDontReleaseMonitor).start();
new Thread(sleepDontReleaseMonitor).start();
}
@Override
public void run() {
syn();
}
private synchronized void syn() {
System.out.println("線程" + Thread.currentThread().getName() + "獲取到了monitor.");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("線程" + Thread.currentThread().getName() + "退出了同步代碼塊.");
}
}
複製代碼
/**
* 演示sleep不釋放lock(lock自己也須要手動釋放)
*/
public class SleepDontReleaseLock implements Runnable {
private static final Lock lock = new ReentrantLock();
@Override
public void run() {
lock.lock();
System.out.println("線程" +Thread.currentThread().getName()+ "獲取到了鎖");
try {
Thread.sleep(5000);
System.out.println("線程" +Thread.currentThread().getName()+ "睡眠結束");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public static void main(String[] args) {
SleepDontReleaseLock sleepDontReleaseLock = new SleepDontReleaseLock();
new Thread(sleepDontReleaseLock).start();
new Thread(sleepDontReleaseLock).start();
}
}
複製代碼
相同:
不一樣:
做用:由於新的線程加入了咱們,因此咱們要等他執行完再出發 用法:main等待thread1執行完畢,主線程等待子線程。
/**
* 演示join用法,注意語句輸出順序是否會變化
*/
public class Join {
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "已經執行完畢");
}
});
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "已經執行完畢");
}
});
thread1.start();
thread2.start();
System.out.println("開始等待子線程運行完畢");
// thread1.join();
// thread2.join();
System.out.println("全部子線程執行完畢");
}
}
複製代碼
/**
* 演示join期間被中斷的效果
*/
public class JoinInterrupt {
public static void main(String[] args) {
Thread mainThread = Thread.currentThread();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
mainThread.interrupt();
Thread.sleep(5000);
System.out.println("Thread1 finished.");
} catch (InterruptedException e) {
System.out.println("子線程中斷");
}
}
});
thread1.start();
System.out.println("等待子線程運行完畢");
try {
thread1.join();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "主線程被中斷了");
thread1.interrupt();
}
System.out.println("子線程已經運行完畢");
}
}
複製代碼
join源碼
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
複製代碼
當調動join
方法默認傳入的millis
是0,此時會進入永久等待,可是因爲JVM的緣由在Thread
類中每一個方法結束都會有一個notify
操做,因此join
纔不須要手動進行喚醒。
由於join
方法的底層是wait
因此使用以下代碼能夠與join
等價
synchronized (thread1){
thread1.wait();
}
複製代碼
爲Thread
類的對象加鎖,在結束時會自動進行釋放,因此能夠達到join
的效果。
做用:釋放個人CPU時間片,但不會釋放鎖也不會進入阻塞。
定位:JVM不保證遵循yield
sleep期間線程進入阻塞狀態因此不會再被調度。而yield只是暫時做出讓步但還能夠處於競爭狀態。
線程id是不可修改的,主線程的id從1開始
private static synchronized long nextThreadID() {
return ++threadSeqNumber;
}
複製代碼
線程的id是先++後返回,因此主線程的id爲1
查看子線程的id
/**
* Id從1開始,JVM運行起來以後,咱們本身建立的線程Id早已不是0
*/
public class Id {
public static void main(String[] args) {
Thread thread = new Thread();
System.out.println("主線程的ID:" + Thread.currentThread().getId());
System.out.println("子線程的ID:" + thread.getId());
}
}
複製代碼
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
private static synchronized int nextThreadNum() {
return threadInitNumber++;
}
複製代碼
經過Thread
類的構造方法能夠發現,若是不傳入線程的名字就會默認在Thread-
後面添加一個從0開始的數字,由於有synchronized
因此不會出現線程重名的狀況。
做用:給用戶線程提供服務(一共有用戶線程和守護線程兩大類)
若是程序中有用戶線程,JVM不會中止工做,可是若是程序中只有守護線程,那麼守護線程也就沒有了守護的對象,因此只有守護線程的狀況下JVM會中止工做。例如:垃圾處理器就是守護線程。
守護線程的3個特性
守護線程和用戶線程的區別
由於都是線程因此總體沒什麼區別
應該把用戶線程設置爲守護線程嗎?
不該該。若是把用戶線程設置爲守護線程了,那麼在執行任務時JVM發現此時沒有用戶線程,這樣就會中止虛擬機,從而致使數據不一致的狀況。
能夠經過設置優先級來增長某個線程的運行次數,優先級最高能夠設置爲10,默認是5,最低是1。
注意:程序設計不該該依賴於優先級
在程序的運行中有不少異常是不可預料的,若是在返回以前不被攔截而是直接返回給用戶的話這樣可能會引起安全性的問題。
使用UncaughtExceptionHandler
/**
* 單線程,拋出,處理,有異常堆棧
* 多線程狀況下子線程發生異常,會有什麼不一樣?
*/
public class ExceptionInChildThread implements Runnable {
public static void main(String[] args) {
new Thread(new ExceptionInChildThread()).start();
for (int i = 0; i < 1000; i++) {
System.out.println(i);
}
}
@Override
public void run() {
throw new RuntimeException();
}
}
複製代碼
當子線程拋出異常時,不會對主線程的運行產生影響。在真實的生產環境中由於有大量日誌的產生,可能會忽略子線程中出現的問題。
/**
* 1.不加try catch時拋出4個異常,都帶線程名
* 2.若是加了try catch,但願能夠捕獲到第一個線程的異常並處理,線程234不該該再運行,
* 但願看到打印出的Caught Exception
* 3.執行時發現,根本沒有Caught Exception,線程234依然運行,而且還拋出異常
*
* 說明線程的異常不能用傳統方法捕獲
*/
public class CantCatchDirectly implements Runnable {
public static void main(String[] args) throws InterruptedException {
new Thread(new CantCatchDirectly(), "MyThread-1").start();
Thread.sleep(300);
new Thread(new CantCatchDirectly(), "MyThread-2").start();
Thread.sleep(300);
new Thread(new CantCatchDirectly(), "MyThread-3").start();
Thread.sleep(300);
new Thread(new CantCatchDirectly(), "MyThread-4").start();
}
@Override
public void run() {
throw new RuntimeException();
}
}
複製代碼
/**
* 1.不加try catch時拋出4個異常,都帶線程名
* 2.若是加了try catch,但願能夠捕獲到第一個線程的異常並處理,線程234不該該再運行,
* 但願看到打印出的Caught Exception
* 3.執行時發現,根本沒有Caught Exception,線程234依然運行,而且還拋出異常
*
* 說明線程的異常不能用傳統方法捕獲
*/
public class CantCatchDirectly implements Runnable {
public static void main(String[] args) throws InterruptedException {
try {
new Thread(new CantCatchDirectly(), "MyThread-1").start();
Thread.sleep(300);
new Thread(new CantCatchDirectly(), "MyThread-2").start();
Thread.sleep(300);
new Thread(new CantCatchDirectly(), "MyThread-3").start();
Thread.sleep(300);
new Thread(new CantCatchDirectly(), "MyThread-4").start();
} catch (RuntimeException e) {
System.out.println("Caught Exception");
e.printStackTrace();
}
}
@Override
public void run() {
throw new RuntimeException();
}
}
複製代碼
run()
方法中進行try-catch
@Override
public void run() {
try {
throw new RuntimeException();
} catch (RuntimeException e) {
System.out.println("Caught Exception");
}
}
複製代碼
實現方案
建立MyUncaughtExceptionHandler
/**
* 實現本身的UncaughtExceptionHandler
*/
public class MyUncaughtExceptionHandler implements Thread.UncaughtExceptionHandler {
private String name;
public MyUncaughtExceptionHandler(String name) {
this.name = name;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
Logger logger = Logger.getAnonymousLogger();
logger.log(Level.WARNING, "線程異常,終止啦 : " + t.getName());
System.out.println(name + " 捕獲了" + t.getName() + "的"+ e +"異常");
}
}
複製代碼
使用MyUncaughtExceptionHandler
/**
* 使用本身建立的UncaughtExceptionHandler
*/
public class UseOwnUncaughtExceptionHandler implements Runnable {
public static void main(String[] args) throws InterruptedException {
Thread.setDefaultUncaughtExceptionHandler(new
MyUncaughtExceptionHandler("捕獲器1"));
new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-1").start();
Thread.sleep(300);
new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-2").start();
Thread.sleep(300);
new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-3").start();
Thread.sleep(300);
new Thread(new UseOwnUncaughtExceptionHandler(), "MyThread-4").start();
}
@Override
public void run() {
throw new RuntimeException();
}
}
複製代碼
線程是一把雙刃劍,他在提升程序執行效率的同時也會存在一些弊端,好比線程安全問題,這會致使數據發生錯亂,還有就是性能問題,好比服務響應慢、吞吐率低、資源開銷大。使用線程的目的就是爲了讓程序更好的運行,若是這些問題不解決就本末倒置了,在這裏學習一下如何解決吧!
當多個線程訪問一個對象時,若是不用考慮這些線程在運行時環境下的調度和交替執行,也不須要進行額外的同步,或者在調用方在進行任何其餘的協調操做,調用這個對象的行爲均可以得到正確的結果,那麼這個對象就是線程安全的。
線程安全問題主要分爲以下兩種狀況
/**
* 第一種狀況:運行結果出錯
* 演示計數不許確(減小),找出具體出錯的位置
*/
public class MultiThreadError implements Runnable {
private int index = 0;
static MultiThreadError instance = new MultiThreadError();
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
index++;
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(instance.index);
}
}
複製代碼
i+1
的操做後線程進行了切換,線程2不知道線程1已經執行了
+1
操做,依然執行+1,加完以後又切換到線程1,此時線程1的加操做已經完成了,i變成了2,線程2再次執行後,i的值也變成了2,這就是致使i出現少加的狀況的緣由。
/**
* 第一種狀況:運行結果出錯
* 演示計數不許確(減小),找出具體出錯的位置
*/
public class MultiThreadError implements Runnable {
private int index = 0;
static AtomicInteger realIndex = new AtomicInteger();
static AtomicInteger wrongCount = new AtomicInteger();
static volatile CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);
static volatile CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);
static MultiThreadError instance = new MultiThreadError();
final boolean[] marked = new boolean[10000000];
@Override
public void run() {
marked[0] = true;
for (int i = 0; i < 10000; i++) {
try {
cyclicBarrier2.reset();
cyclicBarrier1.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
index++;
try {
cyclicBarrier1.reset();
cyclicBarrier2.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
realIndex.incrementAndGet();
synchronized (instance){
if (marked[index] && marked[index-1]){
System.out.println("發生了錯誤" + index);
wrongCount.incrementAndGet();
}
marked[index] = true;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(instance);
Thread thread2 = new Thread(instance);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println("表面上的結果是:" + instance.index);
System.out.println("真正運行的次數是:" + realIndex.get());
System.out.println("錯誤的次數是:" + wrongCount.get());
}
}
複製代碼
/**
* 第二種線程安全問題,演示死鎖
*/
public class MultiThreadError2 implements Runnable {
int flag;
static Object o1 = new Object();
static Object o2 = new Object();
public static void main(String[] args) {
MultiThreadError2 r1 = new MultiThreadError2();
MultiThreadError2 r2 = new MultiThreadError2();
r1.flag = 1;
r2.flag = 0;
new Thread(r1).start();
new Thread(r2).start();
}
@Override
public void run() {
System.out.println("flag : " + flag);
if (flag == 1){
synchronized (o1){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2){ //想拿到o2卻始終拿不到
System.out.println("1");
}
}
}
if (flag == 0){
synchronized (o2){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1){ //想拿到o1卻始終拿不到
System.out.println("0");
}
}
}
}
}
複製代碼
什麼是發佈
對象可讓超出範圍以內的類進行使用,好比在方法結束後return 一個對象,從而讓外部可使用這個對象,這就是對象的發佈。
什麼是逸出
逸出是指將對象發佈到了不應發佈的地方,好比:
private
的對象(private正常只能在本類中使用)this
賦值,隱式逸出——註冊監聽事件,構造函數中運行線程return私有對象致使的逸出
/**
* 發佈逸出
*/
public class MultiThreadError3 {
private Map<String, String> states;
public MultiThreadError3(){
states = new HashMap<>();
states.put("1", "週一");
states.put("2", "週二");
states.put("3", "週三");
states.put("4", "週四");
}
public Map<String, String> getStates(){
return states;
}
public static void main(String[] args) {
MultiThreadError3 multiThreadError3 = new MultiThreadError3();
Map<String, String> states = multiThreadError3.getStates();
System.out.println(states.get("1"));
states.remove("1");
System.out.println(states.get("1"));
}
}
複製代碼
private
的本意就是不但願外部訪問到,能夠經過
return
以後能夠從外部對數據進行修改,這就很危險了!
構造方法未初始化完成就賦值致使逸出
/**
* 初始化未完畢就this賦值
*/
public class MultiThreadError4 {
static Point point;
public static void main(String[] args) throws InterruptedException {
new PointMaker().start();
Thread.sleep(10);
if (point != null){
System.out.println(point);
}
}
}
class Point{
private final int x, y;
public Point(int x, int y) throws InterruptedException {
this.x = x;
MultiThreadError4.point = this;
Thread.sleep(100);
this.y = y;
}
@Override
public String toString() {
return x + "," + y;
}
}
class PointMaker extends Thread{
@Override
public void run() {
try {
new Point(1, 1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
複製代碼
sleep
的時間增長到100ms以上便不會發生逸出
註冊監聽器致使逸出
/**
* 觀察者模式
*/
public class MultiThreadError5 {
int count;
public MultiThreadError5(MySource source){
source.registerListener(new EventListener() {
@Override
public void onEvent(Event e) {
System.out.println("\n我獲得的數字是:" + count);
}
});
for (int i = 0; i < 10000; i++) {
System.out.print(i);
}
count = 100;
}
static class MySource {
private EventListener listener;
void registerListener(EventListener eventListener) {
this.listener = eventListener;
}
void eventCome(Event e){
if (listener != null){
listener.onEvent(e);
}else{
System.out.println("還未初始化完畢");
}
}
}
interface EventListener {
void onEvent(Event e);
}
interface Event {
}
public static void main(String[] args) {
MySource mySource = new MySource();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
mySource.eventCome(new Event() {
});
}
}).start();
MultiThreadError5 multiThreadError5 = new MultiThreadError5(mySource);
}
}
複製代碼
在構造方法中使用線程致使逸出
/**
* 構造函數中新建線程
*/
public class MultiThreadError6 {
private Map<String, String> states;
public MultiThreadError6() {
new Thread(new Runnable() {
@Override
public void run() {
states = new HashMap<>();
states.put("1", "週一");
states.put("2", "週二");
states.put("3", "週三");
states.put("4", "週四");
}
}).start();
}
public Map<String, String> getStates() {
return states;
}
public static void main(String[] args) {
MultiThreadError6 multiThreadError6 = new MultiThreadError6();
Map<String, String> states = multiThreadError6.getStates();
System.out.println(states.get("1"));
}
}
複製代碼
/**
* 發佈逸出
*/
public class MultiThreadError3 {
private Map<String, String> states;
public MultiThreadError3(){
states = new HashMap<>();
states.put("1", "週一");
states.put("2", "週二");
states.put("3", "週三");
states.put("4", "週四");
}
public Map<String, String> getStates(){
return states;
}
public Map<String, String> getStatesImproved(){
return new HashMap<>(states); //建立一個states副本
}
public static void main(String[] args) {
MultiThreadError3 multiThreadError3 = new MultiThreadError3();
Map<String, String> states = multiThreadError3.getStates();
// System.out.println(states.get("1"));
// states.remove("1");
// System.out.println(states.get("1"));
System.out.println(multiThreadError3.getStatesImproved().get("1"));
multiThreadError3.getStatesImproved().remove("1");
System.out.println(multiThreadError3.getStatesImproved().get("1"));
}
}
複製代碼
/**
* 用工廠模式解決監聽器註冊問題
*/
public class MultiThreadError7 {
int count;
private EventListener listener;
private MultiThreadError7(MySource source){
listener = new EventListener() {
@Override
public void onEvent(MultiThreadError7.Event e) {
System.out.println("\n我獲得的數字是:" + count);
}
};
for (int i = 0; i < 10000; i++) {
System.out.print(i);
}
count = 100;
}
public static MultiThreadError7 getInstance(MySource source){
MultiThreadError7 safeListener = new MultiThreadError7(source);
source.registerListener(safeListener.listener);
return safeListener;
}
static class MySource {
private MultiThreadError7.EventListener listener;
void registerListener(MultiThreadError7.EventListener eventListener) {
this.listener = eventListener;
}
void eventCome(MultiThreadError7.Event e){
if (listener != null){
listener.onEvent(e);
}else{
System.out.println("還未初始化完畢");
}
}
}
interface EventListener {
void onEvent(MultiThreadError7.Event e);
}
interface Event {
}
public static void main(String[] args) {
MultiThreadError7.MySource mySource = new MultiThreadError7.MySource();
new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
mySource.eventCome(new MultiThreadError7.Event() {
});
}
}).start();
MultiThreadError7 multiThreadError7 = new MultiThreadError7(mySource);
}
}
複製代碼
HashMap
在併發中可能會出現問題)調度:上下文切換
上下文切換能夠認爲是內核在CPU上進行的如下活動:(1)掛起一個進程將進程中的狀態(上下文)存儲在內存中的某處。(2)在內存中檢索下一個進程的狀態將它在CPU寄存器中恢復。(3)跳轉到程序計數器所指向的位置以恢復該線程
緩存開銷:CPU從新緩存
什麼時候會致使密集的上下文切換:頻繁競爭鎖或者由於IO頻繁致使阻塞
協做:內存同步