并發(fā)編程把我整的是服服氣氣的了
本文轉(zhuǎn)載自微信公眾號「Java極客技術(shù)」,作者鴨血粉絲 。轉(zhuǎn)載本文請聯(lián)系Java極客技術(shù)公眾號。
阿粉因為原來的編程習(xí)慣,已經(jīng)很久沒有去考慮并發(fā)的問題了,結(jié)果之前在面試的問題就回答的不是很完善,而阿粉也用心學(xué)習(xí)了并發(fā)編程這一塊的所有內(nèi)容,一起來分享給大家。
為什么需要并發(fā)編程
因為現(xiàn)在的CPU我們大家也都知道,什么幾核幾線程,各種各樣,而我們并發(fā)編程的目的是為了讓程序運行得更快,這里的更快說的并不是讓我們無限制啟動更多的線程就能讓程序進行最大可能的并發(fā)操作,但是我們在進行并發(fā)編程的時候,很容易遇到很多的問題,比如說死鎖問題,再比如說上下文的切換的問題,這都是問題所在。
實現(xiàn)多線程的幾種方式,面試中最簡單的題目
說起來這個面試題,很多回答都一樣,
- 繼承Thread類
- 實現(xiàn)Runnable接口
- 使用線程池
這是很多面試者回答的時候總是回答這三個,但是實際上,實現(xiàn)多線程的方式也不限于這幾種方式,還有比如說帶返回值的線程實現(xiàn),定時器實現(xiàn),內(nèi)部類實現(xiàn),這些方式都是可以實現(xiàn)多線程的。那我們今天就先來把這些不常用的方式來梳理一下。
使用匿名內(nèi)部類的方式實現(xiàn)多線程
其實說實話,這匿名內(nèi)部類的方式也不能算是一種新的實現(xiàn)方式,只不過是把這個實現(xiàn)方式放到了匿名類里面了,實現(xiàn)的總體內(nèi)部還是使用的繼承 Thread和實現(xiàn)Runnable接口。
案例實現(xiàn):
- public class TestClass {
- public static void main(String[] args) {
- // 基于子類的方式
- new Thread() {
- @Override
- public void run() {
- while (true) {
- printThreadInfo();
- }
- }
- }.start();
- // 基于接口的實現(xiàn)
- new Thread(new Runnable() {
- @Override
- public void run() {
- while (true) {
- printThreadInfo();
- }
- }
- }).start();
- }
- private static void printThreadInfo() {
- System.out.println("當(dāng)前運行的線程名為: " + Thread.currentThread().getName());
- try {
- Thread.sleep(1000);
- } catch (Exception e) {
- throw new RuntimeException(e);
- }
- }
- }
- 實現(xiàn)結(jié)果:
- 當(dāng)前運行的線程名為:Thread-1
- 當(dāng)前運行的線程名為:Thread-0
- 當(dāng)前運行的線程名為:Thread-1
- 當(dāng)前運行的線程名為:Thread-0
- 當(dāng)前運行的線程名為:Thread-1
- 當(dāng)前運行的線程名為:Thread-0
- 當(dāng)前運行的線程名為:Thread-0
- 當(dāng)前運行的線程名為:Thread-1
- 當(dāng)前運行的線程名為:Thread-1
- 當(dāng)前運行的線程名為:Thread-0
- 當(dāng)前運行的線程名為:Thread-0
- 當(dāng)前運行的線程名為:Thread-1
其實對于上述手段,大家也肯定都會,那么我們就說說這個定時器實現(xiàn)方式,這個方式實際上是也是大家經(jīng)常會使用的一種方式,因為我們很多時候都需要在我們不在的情況下進行一些操作,比如說,每天晚上對系統(tǒng)進行一下當(dāng)天的統(tǒng)計操作什么的。
使用定時器實現(xiàn)
- public class TestClass {
- private static final SimpleDateFormat dateFormat =
- new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
- public static void main(String[] args) throws Exception {
- // 創(chuàng)建定時器
- Timer timer = new Timer();
- // 提交計劃任務(wù)
- timer.schedule(new TimerTask() {
- @Override
- public void run() {
- System.out.println("定時任務(wù)執(zhí)行了...");
- }
- }, dateFormat.parse("2020-12-08 20:30:00"));
- }
- }
- 這段代碼大家可以復(fù)制一下,在你設(shè)定好的時間內(nèi)進行執(zhí)行
關(guān)于多線程的實現(xiàn)方式,阿粉就給大家講述到這里,畢竟這個東西在你使用的時候,一定是活學(xué)活用的,不是一成不變的,需要你看自己的需求來弄。
接下來我們就先從并發(fā)編程的線程安全性開始入手,接下來阿粉也會繼續(xù)給大家更新關(guān)于并發(fā)編程的各種技術(shù)內(nèi)容,讓大家能夠盡快的掌握好這個線程安全的問題,
線程的安全性操作
其實對于一個對象來說,他是否是線程安全的,完全取決于他是否被多個線程去訪問,而如果要讓我們的對象是線程安全的話,那么我們一定要采取一些方式,而方式都有哪些呢?
- 同步機制
- 加鎖機制
也就是大家所了解的同步 Synchronized 和加鎖的機制。還有就是使用Volatile類型的變量。
也就是說,如果多個線程去訪問同一個可變的狀態(tài)的變量的時候,沒有使用合適的同步,那么程序相對來說就會出現(xiàn)錯誤,而解決方式也有好幾種,
- 比如說不在線程之前共享這個變量
- 將狀態(tài)變量修改成為不可變的的變量
- 在訪問狀態(tài)變量的時候使用同步
而阿粉之前也看過一個圖片,就是說他從字節(jié)碼的角度去分析了線程不安全的操作,看下圖
用一個最簡單的案例給大家講解Synchronized,我們手動實現(xiàn)一個線程然后遞減,每次輸出這個變量,最終看效果圖
- public class TestClass implements Runnable{
- int i = 100;
- @Override
- public void run() {
- // TODO Auto-generated method stub
- while(true) {
- if(i>0) {
- try {
- Thread.sleep(10);//為了讓安全問題明顯,我們讓線程執(zhí)行的時間變長,故睡眠10毫秒
- } catch (InterruptedException e) {
- // TODO Auto-generated catch block
- e.printStackTrace();
- }
- System.out.println(i);
- i--;
- }
- }
- }
- }
- class Test{
- public static void main(String[] args) {
- TestClass testClass = new TestClass();
- Thread t1 = new Thread(testClass);
- Thread t2 = new Thread(testClass);
- Thread t3 = new Thread(testClass);
- t1.start();
- t2.start();
- t3.start();
- }
- }
不用說大家都知道,結(jié)果肯定是亂的一塌糊涂,有來回跳躍的,也有分段執(zhí)行的,反正就是不是從100到1的,結(jié)果大家可以把代碼拿過去使用一下自己看看。
那么我們加上Synchronized關(guān)鍵字之后呢?
- public class TestClass implements Runnable{
- int i = 100;
- @Override
- public void run() {
- while(true) {
- synchronized (this){
- if(i>0) {
- try {
- Thread.sleep(10);//為了讓安全問題明顯,我們讓線程執(zhí)行的時間變長,故睡眠10毫秒
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(i);
- i--;
- }
- }
- }
- }
- }
- class Test{
- public static void main(String[] args) {
- TestClass testClass = new TestClass();
- Thread t1 = new Thread(testClass);
- Thread t2 = new Thread(testClass);
- Thread t3 = new Thread(testClass);
- t1.start();
- t2.start();
- t3.start();
- }
- }
大家可以去執(zhí)行一下運行結(jié)果,順帶打印出執(zhí)行結(jié)果,是不是這次就很舒服了,終于看到自己心心念念的從100-1的內(nèi)容了,而實際上,我們只是通過加上了一個同步的關(guān)鍵字,來實現(xiàn)了線程的安全性操作,讓線程同步執(zhí)行,不再會出現(xiàn)那個不安全的行為,是不是很簡單?你學(xué)會了么?
下一篇文章阿粉將會帶給大家關(guān)于另外的一個關(guān)鍵字 Volatile 實現(xiàn)線程安全,并且告訴大家他的可見性,還有原子性。