爲什麼要學習併發編程?
•1.發揮多處理的強大能力
•2.建模的簡單性
•3.異步事件的簡化處理
•4.響應更加靈敏的用戶界面
•1.找工作,面試
•2.瞭解併發編程的原理,提高代碼的編寫能力
•3.解決工作中遇到的併發問題
......
線程與進程
首先要先學會區分線程和進程:
- 進程:運行中的程序,是資源分配的基本單位。進程中包含多個線程,線程共享進程的資源
- 線程是處理器調度的基本單位
併發的優缺點?
優點:
- 資源利用率更好
- 程序設計在某些情況下更簡單
- 程序響應更快
缺點:
- 安全性問題
- 活躍性問題(飢餓)
- 性能問題
多線程就一定快嗎?
/**
* ConcurrencyTest:多線程效率測試。
*
* @author YUSIR
* @version 2019-02-28
*/
public class ConcurrencyTest {
private static final long COUNT = 10001;
/**
* 多線程測試.
*/
private static void concurrency() throws InterruptedException {
long startTime = System.currentTimeMillis();
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
int a = 0;
for (long i = 0; i < COUNT; i++) {
a += 5;
}
}
});
thread.start();
int b = 0;
for (long i = 0; i < COUNT; i++) {
b--;
}
// 子線程正常運行,父線程會等待
thread.join();
long time = System.currentTimeMillis() - startTime;
System.out.println("concurrency :" + time + "ms,b=" + b);
}
/**
* 單線程串行執行.
*/
private static void serial() {
long startTime = System.currentTimeMillis();
int a = 0;
for (long i = 0; i < COUNT; i++) {
a += 5;
}
int b = 0;
for (long i = 0; i < COUNT; i++) {
b--;
}
long time = System.currentTimeMillis() - startTime;
System.out.println("serial:" + time + "ms,b=" + b + ",a=" + a);
}
public static void main(String[] args) throws InterruptedException {
concurrency();
serial();
}
運行結果:
concurrency :6ms,b=-10001
serial:0ms,b=-10001,a=50005
此時多線程運行效率並不比傳統的串行運行效率高
但是,當
COUNT = 100000001;
運行結果:
concurrency :38ms,b=-100000001
serial:62ms,b=-100000001,a=500000005
此時多線程就比串行快了
原因:線程有創建和上下文切換的開銷,首先要先理解什麼是上下文的切換——CPU通過時間片分配算法來循環執行任務,當前任務執行一個時間片後會切換到下一個任務.但是,在切換前會保存上一個任務的狀態,以便下次切換回這個任務是,可以再加載這個任務狀態.所以任務從保存到再加載的過程就是一次上下文切換,上下文切換會影響多線程的執行速度!
線程的狀態
1. 新建狀態(New) : 線程對象被創建後,就進入了新建狀態。例如,Thread thread = new Thread()。
2. 就緒狀態(Runnable): 也被稱爲“可執行狀態”。線程對象被創建後,其它線程調用了該對象的start()方法,從而來啓動該線程。例如,thread.start()。處於就緒狀態的線程,隨時可能被CPU調度執行。
3. 運行狀態(Running) : 線程獲取CPU權限進行執行。需要注意的是,線程只能從就緒狀態進入到運行狀態。
4. 阻塞狀態(Blocked) : 阻塞狀態是線程因爲某種原因放棄CPU使用權,暫時停止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態。阻塞的情況分三種:
(01) 等待阻塞 -- 通過調用線程的wait()方法,讓線程等待某工作的完成。
(02) 同步阻塞 -- 線程在獲取synchronized同步鎖失敗(因爲鎖被其它線程所佔用),它會進入同步阻塞狀態。
(03) 其他阻塞 -- 通過調用線程的sleep()或join()或發出了I/O請求時,線程會進入到阻塞狀態。當sleep()狀態超時、join()等待線程終止或者超時、或者I/O處理完畢時,線程重新轉入就緒狀態。
5. 死亡狀態(Dead) : 線程執行完了或者因異常退出了run()方法,該線程結束生命週期。
1.wait(),notify(),notifyAll()等方法的介紹
創建線程的多種方式
- 繼承Thread類
public class Demo1 extends Thread {
@Override
public void run() {
// 測試當前線程是否已被中斷
while(!interrupted()) {
System.out.println("線程執行了 .. ");
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
- 實現Runnable接口
/**
* 作爲線程任務存在
*
* @author worker
*
*/
public class Demo2 implements Runnable {
@Override
public void run() {
while(true) {
System.out.println("thread running ...");
}
}
public static void main(String[] args) {
Thread thread = new Thread(new Demo2());
thread.start();
}
}
- 匿名內部類的方式
public class Demo3 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable");
}
}) {
public void run() {
System.out.println("sub");
};
}.start();
}
}
使用匿名內部類創建線程是不符合規範的,線程資源必須通過線程池提供,不允許在應用中自行顯式創建線程。 說明:使用線程池的好處是減少在創建和銷燬線程上所花的時間以及系統資源的開銷,解決資源不足的問題。如果不使用線程池,有可能造成系統創建大量同類線程而導致消耗完內存或者“過度切換”的問題。
- 帶返回值的線程
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
* 1.方法可以有返回值,並且可以拋出異常
* 2、執行Callable方式,需要FutureTask實現類的支持,用於接收運算結果
*/
public class Demo4 implements Callable<Integer> {
public static void main(String[] args) throws Exception {
Demo4 d = new Demo4();
// 1.執行Callable方式,需要FutureTask實現類的支持,用於接收運算結果
FutureTask<Integer> task = new FutureTask<>(d);
Thread t = new Thread(task);
t.start();
System.out.println("我先乾點別的。。。");
// 2.接收線程運算後的結果。等所有線程執行完,獲取值,因此FutureTask可用於閉鎖
Integer result = task.get();
System.out.println("線程執行的結果爲:" + result);
}
@Override
public Integer call() throws Exception {
System.out.println("正在進行緊張的計算....");
Thread.sleep(3000);
return 1;
}
}
- 定時器(quartz)
1.使用Timer()創建定時器
import java.util.Timer;
import java.util.TimerTask;
import java.util.concurrent.ScheduledExecutorService;
public class Demo5 {
public static void main(String[] args) {
// Timer的內部只有一個線程,多線程並行處理定時任務時,Timer運行多個TimeTask時,
// 只要其中之一沒有捕獲拋出的異常,其它任務便會自動終止運行,使用ScheduledExecutorService則沒有這個問題。
Timer timer = new Timer();
timer.schedule(new TimerTask() {
@Override
public void run() {
// 實現定時任務
System.out.println("timertask is run");
}
}, 0, 1000);
}
}
2. 使用ScheduledExecutorService創建
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* Demo5_1
*
* @author YUSIR
* @version 2019-02-28
*/
public class Demo5_1 {
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(4);
// 延時任務
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
//do something
}
}, 1, TimeUnit.SECONDS);
// 循環任務,按照上一次任務的發起時間計算下一次任務的開始時間
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
//do something
}
}, 1, 1, TimeUnit.SECONDS);
// 循環任務,以上一次任務的結束時間計算下一次任務的開始時間
scheduledExecutorService.scheduleWithFixedDelay(new Runnable() {
@Override
public void run() {
//do something
}
}, 1, 1, TimeUnit.SECONDS);
}
}
- 線程池的實現
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Demo6 {
public static void main(String[] args) {
ExecutorService threadPool = Executors.newCachedThreadPool();
for (int i = 0; i < 1000; i++) {
threadPool.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
threadPool.shutdown();
}
}
線程池不允許使用Executors去創建,而是通過ThreadPoolExecutoer的方式去創建,這樣的處理方式可以避免資源耗盡的風險。
1).newFixedThreadPool和newSingleThreadExecutor:主要問題是堆積的請求處理隊列可能會消耗非常大的內存
2).newCachedThreadPool和newScheduledThreadPool:主要問題是線程數最大是Integer.MAX_VALUE。可能會創建非常多的線程。
- Lambda表達式實現
import java.util.Arrays;
import java.util.List;
public class Demo7 {
public static void main(String[] args) {
List<Integer> values = Arrays.asList(10,20,30,40);
int res = new Demo7().add(values);
System.out.println("計算的結果爲:" + res);
}
public int add (List<Integer> values) {
// values.parallelStream().forEach(System.out :: println);
return values.parallelStream().mapToInt( i -> i * 2).sum();
}
}
- Spring實現多線程
import java.util.Random;
import java.util.UUID;
import java.util.concurrent.Executor;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
/**
* Demo8:spring創建線程池.
*
* @author YUSIR
* {@link EnableAsync}啓用異步任務
* @version 2019-02-28
*/
@EnableAsync
public class Demo8 {
@Bean
public Executor executor() {
ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
//核心線程數
executor.setCorePoolSize(5);
//最大線程數
executor.setMaxPoolSize(10);
//隊列最大長度
executor.setQueueCapacity(25);
executor.initialize();
return executor;
}
@Service
public class AsyncTaskService {
// 這裏可以注入spring中管理的其他bean,這也是使用spring來實現多線程的一大優勢
@Async // 這裏進行標註爲異步任務,在執行此方法的時候,會單獨開啓線程來執行
public void f1() {
System.out.println("f1 : " + Thread.currentThread().getName() + " " + UUID.randomUUID().toString());
try {
Thread.sleep(new Random().nextInt(100));
} catch (InterruptedException e) {
e.printStackTrace();
}
}
@Async
public void f2() {
System.out.println("f2 : " + Thread.currentThread().getName() + " " + UUID.randomUUID().toString());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(Demo8.class);
AsyncTaskService service = context.getBean(AsyncTaskService.class);
for (int i = 0; i < 10; i++) {
service.f1();
service.f2();
}
context.close();
}
}
基於xml配置
<bean id="taskExecutor" class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<!-- 核心線程數 -->
<property name="corePoolSize" value="5" />
<!-- 最大線程數 -->
<property name="maxPoolSize" value="10" />
<!-- 隊列最大長度 >=mainExecutor.maxSize -->
<property name="queueCapacity" value="25" />
<!-- 線程池維護線程所允許的空閒時間 -->
<property name="keepAliveSeconds" value="3000" />
<!-- 線程池對拒絕任務(無線程可用)的處理策略 ThreadPoolExecutor.CallerRunsPolicy策略 ,調用者的線程會執行該任務,如果執行器已關閉,則丟棄. -->
<property name="rejectedExecutionHandler">
<bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" />
</property>
</bean>