多執行緒
- 4.4 題外話
- 5 執行緒的死鎖問題
- 5.1 概念
- 5.1.1 死鎖的理解
- 5.1.2 說明
- 5.1.3舉例
- 5.2 解決方法
- 5.3 演示執行緒的死鎖問題
- 6.JDK5.0 新增解決執行緒安全問題
- 6.1 概念
- 6.2 步驟
- 6.3 舉例
- 6.4 synchronized與Lock 的異同?(面試題)
- 7.執行緒的通信
- 7.1 執行緒通信的例子
- 7.2 涉及到三個方法及注意點
- 7.2.1 方法
- 7.2.2 注意點:
- 7.2.3 面試題: sleep() 和 wait() 的異同?
- 7.3 舉例
- 8.JDK5.0新增執行緒創建方式
- 8.1 多執行緒創建,方式三:實作Callable介面
- 8.1.1 步驟
- 8.1.2 Callable比Runnable更強大
- 8.1.3 舉例
- 8.2 多執行緒創建,方式四:使用執行緒池(開發常用的)
- 8.2.1 介紹
- 8.2.2 步驟
- 8.2.3 好處
- 8.2.4 舉例
- 9. 面試題
4.4 題外話
根據多執行緒詳解(一)的同步,
我們可以使用同步機制將單例模式中的懶漢式改寫為執行緒安全的,
舉例:
public class BankTest { }
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance() {
//方式一:在方法上 加 synchronized 效率稍差
//方式二:效率稍差 只要判斷一次同步一次 后面就不用同步了
//解決方案就相當于只有一臺手機 買完人走了 出公告 手機賣完了 不用再進來了
// synchronized (Bank.class) {
// if (instance == null){
// instance = new Bank();
// }
// return instance;
// }
//方式三:效率更高
if (instance == null){
synchronized (Bank.class) {
if (instance == null){
instance = new Bank();
}
}
}
return instance;//這行就不算操作
}
}
5 執行緒的死鎖問題
5.1 概念
5.1.1 死鎖的理解
不同的執行緒分別占用對方需要的同步資源不放棄,
都在等待對方放棄自己需要的同步資源,就形成了執行緒的死鎖
5.1.2 說明
1.出現死鎖后,不會出現例外,不會出現提示,
只是所有的執行緒都處于阻塞狀態,無法繼續
2.我們使用同步時,要避免出現死鎖
5.1.3舉例
一人一個筷子,互不相讓,就打起來了,
5.2 解決方法
- 專門的演算法、原則
- 盡量減少同步資源的定義
- 盡量避免嵌套同步
5.3 演示執行緒的死鎖問題
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){//s1是鎖
s1.append("a");
s2.append("1");
//讓死鎖的概率高一點
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
一個等著拿s2 一個拿著s2等著拿s1 互相僵持
6.JDK5.0 新增解決執行緒安全問題
6.1 概念
解決執行緒安全問題的方式三: Lock鎖 —JDK5.0新增
》通過顯式定義同步鎖物件來實作同步,
》同步鎖使用Lock物件充當,
6.2 步驟
- 實體化ReentrantLock
- 呼叫鎖定方法lock()
- 呼叫解鎖方法:unlock()
6.3 舉例
使用Lock鎖解決實作Runnable介面的執行緒安全問題
import java.util.concurrent.locks.ReentrantLock;
class Window implements Runnable{
private int ticket = 100;
//1.實體化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
//默認值是false 引數是boolean fair(公平)
// 設定true 先進先出 一個個執行 不會出現有個執行了下一刻又搶到了
@Override
public void run() {
while (true){
try {
//2.呼叫鎖定方法lock()
lock.lock();//類似同步監視器 下面變成了單執行緒
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":售票,票號為:" + ticket);
ticket --;
}else {
break;
}
}finally {
//3.呼叫解鎖方法:unlock()
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
t1.start();
t2.start();
t3.start();
}
}
如果你用的是繼承于Thread類 lock要加個靜態 要用同一個
6.4 synchronized與Lock 的異同?(面試題)
相同:
二者都可以解決執行緒安全問題
不同:
synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器,
Lock需要手動的啟動同步(Lock()) ,同時結束同步也需要手動的實作(unLock()),
優先使用順序:
Lock >同步代碼塊(已經進入了方法體,分配了相應資源)>同步方法(在方法體之外)
7.執行緒的通信
7.1 執行緒通信的例子
使用兩個執行緒列印1-100,執行緒1,執行緒2交替列印(一個一個互動進入)
7.2 涉及到三個方法及注意點
7.2.1 方法
wait():
一旦執行此方法,當前執行緒就進入阻塞狀態,并釋放同步監視器,
notify():
一旦執行此方法,就會喚醒被wait的一個執行緒,
如果有多個執行緒被wait,就喚醒優先級高的那個,
notifyAll():
一旦執行此方法,就會喚醒所有被wait的執行緒
7.2.2 注意點:
1.wait(), notify(), notifyAll() 三個方法必須使用在同步代碼塊或同步方法中,
2.wait(), notify(), notifyAll() 三個方法的呼叫者必須是同步代碼塊或同步方法中的同步監視器,
否則,會出現IllegaLMonitorStateException例外
3.wait(), notify(), notifyAll() 三個方法是定義在java.lang.Object類中,
7.2.3 面試題: sleep() 和 wait() 的異同?
相同點:
一旦執行方法, 都可以使得當前的執行緒進入阻塞狀態,
不同點:
1)兩個方法宣告的位置不同: Thread類中宣告sleep(),object類中宣告wait()
2)呼叫的要求不同:
sleep()可以在任何需要的場景下呼叫,
wait()必須使用在同步代碼塊或同步方法中
3)關于是否釋放同步監視器:
如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait() 會釋放鎖,
7.3 舉例
class Number implements Runnable {
private int number = 1;//共享資料
@Override
public void run() {
while (true){
synchronized (this) { //this代表number物件
notify();//執行緒一把執行緒二喚醒 執行緒二把執行緒一喚醒 notifyAll()就是喚醒所有 省略了this
if (number <= 100) {
try {
Thread.sleep(10);//sleep 不會釋放鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
//使得呼叫如下wait() 方法的執行緒進入阻塞狀態 wait會釋放鎖
try {
wait();// 省略了this
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("執行緒1");
t2.setName("執行緒2");
t1.start();
t2.start();
}
}
8.JDK5.0新增執行緒創建方式
8.1 多執行緒創建,方式三:實作Callable介面
8.1.1 步驟
Future介面最重要
- 創建一個實作Callable的實作類
- 實作call方法,將此執行緒需要執行的操作宣告在call()中
- 創建Callable介面實作類的物件
- 將此Callable介面實作類的物件作為傳遞到FutureTask構造器中,
創建FutureTask 的物件 - 將Future Task的物件作為引數傳遞到Thread類的構造器中,
創建Thread物件,并呼叫start() - 獲取Callable中call方法的回傳值
8.1.2 Callable比Runnable更強大
如何理解實作Callable介面的方式創建多執行緒
比實作Runnable介面創建多執行緒方式要強大
- call()可以有回傳值的
- call() 可以拋出例外,被外面的操作捕獲,獲取例外的資訊
- Callable是支持泛型的
8.1.3 舉例
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.創建一個實作Callable的實作類
class NumThread implements Callable<Integer> {
//2.實作call方法,將此執行緒需要執行的操作宣告在call()中
@Override
public Integer call() throws Exception {//回呼方法
int sum = 0;
for (int i = 1 ; i <= 100 ; i++) {
if (i % 2 == 0){
System.out.println(i);//分執行緒
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.創建Callable介面實作類的物件
NumThread numThread = new NumThread();
//4.將此Callable介面實作類的物件作為傳遞到FutureTask構造器中,創建FutureTask 的物件
FutureTask<Integer> futureTask = new FutureTask<>(numThread);
//5.將Future Task的物件作為引數傳遞到Thread類的構造器中,創建Thread物件,并呼叫start()
new Thread(futureTask).start();
try {
//6.獲取Callable中call方法的回傳值
//get()回傳值即為FutureTask構造器引數Callable實作類重寫的calL()的回傳值,
Integer sum = futureTask.get();//調get方法獲取Callable介面實作類的回呼方法
System.out.println("總和為:" + sum);//主執行緒
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
8.2 多執行緒創建,方式四:使用執行緒池(開發常用的)
8.2.1 介紹
背景:
經常創建和銷毀、使用量特別大的資源,比如并發情況下的執行緒,對性能影響很大,
解決方案:
提前創建好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中,
可以避免頻繁創建銷毀、實作重復利用,類似生活中的公共交通工具,
8.2.2 步驟
- 提供指定執行緒數量的執行緒池
- 執行指定的執行緒的操作,
需要提供實作Runnable介面 或 Callable介面實作類的物件 - 關閉執行緒池
8.2.3 好處
- 提高回應速度(減少了創建新執行緒的時間)
- 降低資源消耗(重復利用執行緒池中執行緒,不需要每次都創建)
- 便于執行緒管理
corePoolSize:核心池的大小
maximumPoolSize:最大執行緒數
keepAliveTime:執行緒沒有任務時最多保持多長時間后會終止
8.2.4 舉例
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
class NumberThread implements Runnable {
@Override
public void run() {
for (int i = 1 ; i <= 100 ; i++) {
if (i % 2 == 0){
System.out.println(Thread.currentThread().getName() + ":" + i);//分執行緒
}
}
}
}
class NumberThread1 implements Runnable {
@Override
public void run() {
for (int i = 1 ; i <= 100 ; i++) {
if (i % 2 != 0){
System.out.println(Thread.currentThread().getName() + ":" + i);//分執行緒
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定執行緒數量的執行緒池
ExecutorService service = Executors.newFixedThreadPool(10);//造了個執行緒池
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
//設定執行緒池的屬性
// System.out.println(service.getClass());//獲取是哪個類造的
service1.setCorePoolSize(15);
//2.執行指定的執行緒的操作,需要提供實作Runnable介面 或 Callable介面實作類的物件
//執行緒要干什么不知道 所以還是要提供實作 Runnable介面的 實作類
service.execute(new NumberThread());//適合適用于Runnable
service.execute(new NumberThread1());//適合適用于Runnable
// service.submit(Callable callable);//適合適用于Callable
service.shutdown();//3.關閉執行緒池
}
}
9. 面試題
一共有幾種多執行緒創建方式? 》 4種
解決執行緒安全問題? 》3種
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/375212.html
標籤:其他
