1. 概念
1.1 使用多線程目的
提高系統(tǒng)的吞吐速度(提高CPU的利用率):充分利用多CUP多核(一個核可以對應多個線程數(shù)樟氢,如四核八線程)焙贷,一個核對應一個線程堵第。單核CPU時代蜘腌,只能有一個線程利用CPU,使用多線程提高系統(tǒng)利用率不明顯辰如。
1.2 多任務多進程
過去單核CPU時代赞咙,計算機能在同一時間點并行執(zhí)行多任務或多進程责循。并不是真正意義上的“同一時間點”,而是多個任務或進程共享一個CPU谋逻,并交由操作系統(tǒng)來完成多任務間對CPU的運行切換终惑,以使得每個任務都有機會獲得一定的時間片運行失驶。
而現(xiàn)在多核CPU的情況下,同一時間點可以執(zhí)行多個任務歹垫,具體到這個任務在CPU哪個核上運行,這個就跟操作系統(tǒng)和CPU本身的設計相關了
1.3 程序線程數(shù)
比如說tomcat為每個請求啟動一個線程處理颠放,其中maxThreads設置為1000排惨,有1000個用戶并發(fā),那么Tomcat就會起1000個線程來處理慈迈。那么假如實際CPU線程數(shù)只有30若贮,1000個線程分時間片段使用30個CPU線程處理省有,從外部上看是同一時間。類似單核CUP多任務谴麦。
1.4如何設置程序線程數(shù)
(1)活躍線程數(shù)為 CPU(核)數(shù)時最佳蠢沿。
過少的活躍線程導致 CPU 無法被充分利用,過多的活躍線程導致過大的線程上下文切換開銷匾效,同時更多的內存開銷舷蟀,更多的CPU開銷。
活躍線程數(shù):處于 IO 的線程面哼,休眠的線程等均不消耗 CPU野宜,不屬于活躍線程。在實際環(huán)境中魔策,當前活躍線程數(shù)一直在變化匈子,很多活躍線程可能因為需要進行 IO 處理或等待資源而處于非活躍狀態(tài),假如說線程的數(shù)量等于 CPU(核)數(shù)闯袒,這就意味著活躍線程數(shù)小于 CPU(核)數(shù)虎敦。
(2)確定線程數(shù)的原則
提高CPU的中簽率:多線程編程中,在確保內存不溢出的情況下提升線程數(shù)是可以提高CPU中簽率的政敢,也就是能提高你的程序處理數(shù)據(jù)的速度其徙。
不是線程數(shù)越大越好:即使沒有溢出,也不是線程數(shù)越大越好喷户,線程切換畢竟需要時間唾那,應該找到瓶頸所在
(3)依據(jù)cpu核數(shù)設置合適的線程數(shù)
獲取cpu核心數(shù):Runtime.getRuntime().availableProcessors();
// 根據(jù)CPU數(shù)量創(chuàng)建線程池
private static ExecutorService printJobthreadPool = Executors
.newFixedThreadPool(Runtime.getRuntime().availableProcessors());
2. 線程安全
2.1 線程安全是由什么引起
線程安全問題都是由全局變量及靜態(tài)變量引起的。
若每個線程中對全局變量褪尝、靜態(tài)變量只有讀操作闹获,而無寫操作,一般來說恼五,這個全局變量是線程安全的昌罩;
若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步灾馒,否則就可能影響線程安全茎用。以下情況是線程安全的:
- 常量始終是線程安全的,因為只存在讀操作睬罗。
- 每次調用方法前都新建一個實例是線程安全的轨功,因為不會訪問共享的資源。
- 局部變量是線程安全的容达。因為每執(zhí)行一個方法古涧,都會在獨立的空間創(chuàng)建局部變量,它不是共享的資源花盐。局部變量包括方法的參數(shù)變量和方法內變量羡滑。
2.2 解決線程安全的方式
為了解決多線程中相同變量的訪問沖突問題菇爪,有兩種方法:ThreadLocal和線程同步機制。
(1)同步機制
對于多線程資源共享的問題柒昏,同步機制采用了“以時間換空間”的方式凳宙,在同步機制中,通過對象的鎖機制保證同一時間只有一個線程訪問變量职祷。這時該變量是多個線程共享的氏涩,使用同步機制要求程序慎密地分析什么時候對變量進行讀寫,什么時候需要鎖定某個對象有梆,什么時候釋放對象鎖等繁雜的問題是尖,程序設計和編寫難度相對較大。
(2)ThreadLocal
ThreadLocal采用了“以空間換時間”的方式泥耀。前者僅提供一份變量饺汹,讓不同的線程排隊訪問,而后者為每一個線程都提供了一份變量爆袍,因此可以同時訪問而互不影響首繁。ThreadLocal會為每一個線程提供一個獨立的變量副本,從而隔離了多個線程對數(shù)據(jù)的訪問沖突陨囊。因為每一個線程都擁有自己的變量副本,從而也就沒有必要對該變量進行同步了夹攒。ThreadLocal提供了線程安全的共享對象蜘醋,在編寫多線程代碼時,可以把不安全的變量封裝進ThreadLocal咏尝。
ThreadLocal具體使用:
private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>()
//我們有時候會使用靜態(tài)變量來存放某些值压语。但是并發(fā)情況下靜態(tài)變量是不安全的。因此使用ThreadLocal創(chuàng)建獨立的副本编检。
private static final ThreadLocal contextHolder=new ThreadLocal();
然后使用常用方法操作:
set:設置當前線程的線程局部變量的值胎食。
get:返回當前線程所對應的線程局部變量。
remove:將當前線程局部變量的值刪除允懂。
(3) 什么是線程安全的類
線程安全的類 厕怜,指的是類內共享的全局變量的訪問必須保證是不受多線程形式影響的。如果由于多線程的訪問(比如修改蕾总、遍歷粥航、查看)而使這些變量結構被破壞或者針對這些變量操作的原子性被破壞,則這個類就不是線程安全的生百。
2.4 無狀態(tài)Bean
(1)無狀態(tài)bean和有狀態(tài)的定義
- 無狀態(tài)就是一次操作递雀,不能保存數(shù)據(jù)。無狀態(tài)對象(Stateless Bean)蚀浆,就是
沒有實例變量的對象
,不能保存數(shù)據(jù)缀程,是不變類搜吧,是線程安全的。無狀態(tài)的Bean適合用不變模式杨凑,技術就是單例模式赎败,這樣可以共享實例,提高性能蠢甲。 - 有狀態(tài)對象:有狀態(tài)的Bean僵刮,多線程環(huán)境下不安全,那么適合用Prototype原型模式鹦牛。
package com.sw;
public class TestManagerImpl implements TestManager{
private User user; //實例變量
public void deleteUser(User e) throws Exception {
user = e ; //1
prepareData(e);
}
public void prepareData(User e) throws Exception {
user = getUserByID(e.getId()); //2
.....
//使用user.getId(); //3
.....
.....
}
}
TestManagerImpl是一個有狀態(tài)的Bean搞糕,如果該Bean配置為singleton,會出現(xiàn)什么樣的狀況呢?
3. 使用流程
多線程使用步驟主要有:
3.1 建立線程任務
實現(xiàn)方式主要有兩種:實現(xiàn)Runnable接口和繼承Thread類曼追。實現(xiàn)Runnable接口相比繼承Thread類有如下好處:
- 避免繼承的局限窍仰,一個類可以繼承多個接口。
- 適合于資源的共享礼殊。
在程序開發(fā)中只要是多線程肯定永遠以實現(xiàn)Runnable接口為主驹吮。兩種方式都要實現(xiàn)run()方法。
(1)實現(xiàn)Runnable接口
主要的實現(xiàn)步驟很簡單:構造函數(shù)和實現(xiàn)run方法
public class RefundNotify implements Runnable
{
private RefundNotifyMapper mRefundNotifyMapper;
private LinkedBlockingQueue<String> queue;
private String url;
private static Logger logger = LoggerFactory.getLogger(AutoRepayServiceImpl.class);
//構造函數(shù)
public RefundNotify(LinkedBlockingQueue<String> queue, RefundNotifyMapper mRefundNotifyMapper, String url01)
{
System.out.println("退款通知線程開始工作晶伦!");
this.queue = queue;
this.mRefundNotifyMapper = mRefundNotifyMapper;
this.url = url01;
}
// 實現(xiàn)run方法
@SneakyThrows
@Override
public void run()
{
}
}
Runnable里面沒有start方法可以通過Thread類進行啟動Runnable多線程碟狞,Runnable可以用同一個對象實例化的,可以資源共享婚陪,Thread不可以
//同一個實例
MyThreadWithImplements myRunnable = new MyThreadWithImplements();
hread thread1 = new Thread(myRunnable, "窗口一");
Thread thread2 = new Thread(myRunnable, "窗口二");
Thread thread3 = new Thread(myRunnable, "窗口三");
thread1.start();
thread2.start();
thread3.start();
(2)繼承Thread類
使用不多族沃,不詳細介紹
3.2 創(chuàng)建線程池
使用ThreadPoolExecutor創(chuàng)建線程池并執(zhí)行
//獲取系統(tǒng)處理器個數(shù),作為線程池數(shù)量
int nThreads = Runtime.getRuntime().availableProcessors();
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder().setNameFormat("member-pool-%d").build();
//創(chuàng)建線程池
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
//傳入任務泌参,執(zhí)行
pool.execute(refundNotify);
pool.execute(invoiceNotify);
pool.execute(timingRetryNotify);