Java多線程
正確的編程方法:首先使代碼正確運(yùn)行,然后再提高代碼的速度。
常見創(chuàng)建線程的三種方式:
- Thread
//1. Thread方式
MyThread thread=new MyThread();
thread.start();
- Runnable
//2. Runnable
Runnable runnable=new Runnable() {
@Override
public void run() {
for(int i=0;i<100;++i){
System.out.println(Thread.currentThread().getName()+Thread.currentThread().getId()+"---"+i);
}
}
};
new Thread(runnable).start();
- Callable
//3. Callable
Callable<Integer> callable=new Callable<Integer>() {
@Override
public Integer call() throws Exception {
for(int i=0;i<100;++i){
System.out.println(Thread.currentThread().getName()+Thread.currentThread().getId()+"---"+i);
}
return 15;
}
};
FutureTask<Integer> futureTask=new FutureTask(callable);
new Thread(futureTask).start();
try {
Integer rt = futureTask.get();
System.out.println(rt);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
區(qū)別:
- 性質(zhì):
- Runnable和Callable是接口
- Thread 是類,Thread 實(shí)現(xiàn)了 Runnable
- 調(diào)用:
- Callable 調(diào)用call(), 有返回值
- Runnable 調(diào)用run(),Runnable 無返回值,
- 異常:
- call 可以拋出異常到擁有者線程,也就是異常可知
- Thread和Runnable的異常無法在其他線程catch
- Callable 可以得到一個返回一次性任務(wù)FutureTask對象,表示一次性的異步計(jì)算
接口和類的區(qū)別:
- 接口可以多implement,Java類只能單繼承
- 接口的Class文件中superClass是空的,而類的superClass只要不是Object就一定非空
- 接口是不涉及實(shí)現(xiàn)的,所以對象的attributes中沒有Code屬性,而Class有
線程安全
什么是線程安全?
當(dāng)多個線程訪問某個類時,這個類始終都能表現(xiàn)出正確的行為,那么就稱這個類是線程安全的。
----- Java 并發(fā)編程實(shí)戰(zhàn)
這里的正確性包括:
- 不變性條件
- 用于判斷狀態(tài)是有效的還是無效的,也就是狀態(tài)的
狀態(tài)空間
- 用于判斷狀態(tài)是有效的還是無效的,也就是狀態(tài)的
如:long a ,a in [Long.MIN_VALUE,LONG.MAX_VALUE],
那么當(dāng)a>Long.MAX_VALUE時,就不滿足不變性條件
- 后驗(yàn)條件
- 判斷狀態(tài)遷移是否有效
如:兩個屬性定義滿足:a+b=5;
那么{a=2,b=3}就是有效狀態(tài),{a=3,b=3}就是無效狀態(tài)
這里其實(shí)Java并發(fā)編程關(guān)注的就是共享對象的狀態(tài)轉(zhuǎn)換。
線程安全類封裝了必要的同步機(jī)制,因此客戶端無須進(jìn)一步采取同步措施
編寫線程安全的代碼,其核心在于要對狀態(tài)操作進(jìn)行管理,特別是對
共享的和可變的狀態(tài)的訪問。
- 無狀態(tài)的對象一定是線程安全的。
- 線程間共享對象,實(shí)際上是對象所有權(quán)的傳遞,所有權(quán)唯一
封裝線程安全類的一些手段:
- 盡量使得屬性是
final的,因?yàn)闊o狀態(tài)的對象一定是線程安全的。 - 盡量不對外發(fā)布狀態(tài),因?yàn)榘l(fā)布后所有權(quán)的轉(zhuǎn)移是不可控的。
//反例:
private List<Integer> myList=new ArrayList<>();
public List getList(){
//對外發(fā)布對象,外部可以得到內(nèi)部對象的引用,之后對這個list的操作不受本類控制,myList的所有權(quán)不可控
return this.myList;
}
//正例
private List<Integer> myList=new ArrayList<>();
public List getList(){
//拷貝一份
ArrayList<Integer> list = new ArrayList<>();
for(Integer i:myList){
list.add(new Integer(i));
}
//拒絕修改
return Collections.unmodifiableList(myList);
}
- 線程封閉。保證只有一個線程在訪問當(dāng)前變量。最具體的表現(xiàn)就是方法中不對外發(fā)布的局部變量
- 加鎖。其實(shí)是為了保證對象所有權(quán)在一個時間點(diǎn)只有一個線程能得到。
- 獨(dú)立的狀態(tài),單獨(dú)加鎖就好了
- 組合的狀態(tài),狀態(tài)的轉(zhuǎn)換必須滿足后驗(yàn)條件,必須用同一把鎖來鎖住。
/**
* 不變性條件: blood,lostBlood in [0,100]
* 后驗(yàn)條件:blood+lostBlood=100
*/
public class Person {
private int blood=100;
private int lostBlood=0;
public void injured(int k){
//必須是同一把鎖
synchronized(this){
blood-=k;
lostBlood+=k;
}
}
}
- 記得把類和其方法的線程安全性寫入文檔
Servlet
Servlet在非分布式環(huán)境下默認(rèn)是單例的,可以配置為多實(shí)例。所以要注意它的線程安全問題。
-
創(chuàng)建的時機(jī):
- 第一次訪問Servlet的時候創(chuàng)建
- web服務(wù)器啟動時創(chuàng)建
配置:
<load-on-startup>1</load-on-startup>
- 創(chuàng)建:tomcat等服務(wù)器
Executors 框架
- Executor接口
- ExecutorService 接口擴(kuò)展Executor接口,增加了生命周期方法
實(shí)現(xiàn)一個支持并發(fā)的小型服務(wù)器:
public class TaskServer {
private static final int NTHREADS=100;
private static final int PORT=80;
//固定大小的線程池
private static final Executor exec= Executors.newFixedThreadPool(NTHREADS);
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(PORT);
while(true){
//阻塞
final Socket s=serverSocket.accept();
Runnable task=new Runnable() {
@Override
public void run() {
handleRequest(s);
}
};
exec.execute(task);
}
}
private static void handleRequest(Socket s) {
//do something
}
}
- ExecutorService 和Callable:支持返回值和捕捉異常
Callable task= new Callable() {
@Override
public Object call() throws Exception {
//do something and return
int rt=1;
return rt;
}
};
//提交上去的任務(wù)得到一個future,也即一次性任務(wù)
Future future = exec.submit(task);
//阻塞方法,只有任務(wù)執(zhí)行完畢才會返回值,且一次完成,之后返回都是一致的
int rt=funture.get();
- ComCompletionService
ComCompletionService包裝Executor對象,并且內(nèi)置一個已完成隊(duì)列。還有調(diào)度邏輯。
會將已完成的任務(wù)放到BlockQueue中,要用的時候直接take

image.png
package cc.siriuscloud;
import org.junit.Test;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.*;
public class Renderer {
private static final int NTHREADS=100;
private final ExecutorService executor;
public Renderer() {
this.executor = Executors.newFixedThreadPool(NTHREADS);
}
@Test
public void renderPage() {
List<String> infos = getList();
//包裝成completionService
CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(executor);
for (final String item : infos) {
//提交任務(wù)
completionService.submit(new Callable<Integer>() {
@Override
public Integer call() throws Exception {
int rt = Integer.parseInt(item);
synchronized (this){
//模仿耗時操作
Random random = new Random();
wait(Math.abs(random.nextInt(1000)));
}
return rt;
}
});
}
// 執(zhí)行完的任務(wù)結(jié)果放在已完成任務(wù)隊(duì)列中,
try {
for (int t = 0, n = infos.size(); t < n; ++t) {
//取出一個已完成的任務(wù),沒有任何結(jié)果時阻塞
Future<Integer> f = completionService.take();
Integer rt = f.get();
System.out.println(" 結(jié)果是:rt=" + rt);
}
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
private List<String> getList() {
ArrayList<String> list = new ArrayList<>();
list.add("1000");
list.add("110");
list.add("11");
list.add("1");
return list;
}
}
參考: