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