聊聊java多線程創(chuàng)建方式及線程安全問(wèn)題
什么是線程
線程被稱為輕量級(jí)進(jìn)程,是程序執(zhí)行的最小單位,它是指在程序執(zhí)行過(guò)程中,能夠執(zhí)行代碼的一個(gè)執(zhí)行單位。每個(gè)程序程序都至少有一個(gè)線程,也即是程序本身。
線程的狀態(tài)
新建(New):創(chuàng)建后尚未啟動(dòng)的線程處于這種狀態(tài)運(yùn)行(Runable):Runable包括了操作系統(tǒng)線程狀態(tài)的Running和Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著CPU為它分配執(zhí)行時(shí)間。等待(Wating):處于這種狀態(tài)的線程不會(huì)被分配CPU執(zhí)行時(shí)間。等待狀態(tài)又分為無(wú)限期等待和有限期等待,處于無(wú)限期等待的線程需要被其他線程顯示地喚醒,沒(méi)有設(shè)置Timeout參數(shù)的Object.wait()、沒(méi)有設(shè)置Timeout參數(shù)的Thread.join()方法都會(huì)使線程進(jìn)入無(wú)限期等待狀態(tài);有限期等待狀態(tài)無(wú)須等待被其他線程顯示地喚醒,在一定時(shí)間之后它們會(huì)由系統(tǒng)自動(dòng)喚醒,Thread.sleep()、設(shè)置了Timeout參數(shù)的Object.wait()、設(shè)置了Timeout參數(shù)的Thread.join()方法都會(huì)使線程進(jìn)入有限期等待狀態(tài)。阻塞(Blocked):線程被阻塞了,“阻塞狀態(tài)”與”等待狀態(tài)“的區(qū)別是:”阻塞狀態(tài)“在等待著獲取到一個(gè)排他鎖,這個(gè)時(shí)間將在另外一個(gè)線程放棄這個(gè)鎖的時(shí)候發(fā)生;而”等待狀態(tài)“則是在等待一段時(shí)間或者喚醒動(dòng)作的發(fā)生。在程序等待進(jìn)入同步區(qū)域的時(shí)候,線程將進(jìn)入這種狀態(tài)。結(jié)束(Terminated):已終止線程的線程狀態(tài),線程已經(jīng)結(jié)束執(zhí)行。

多線程創(chuàng)建方法
繼承Thread
/**
* @Author GocChin
* @Date 2021/5/11 11:56
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class MyThread extends Thread{
@Override
public void run() {
System.out.println(currentThread().getName()+"運(yùn)行了");
}
}
class Test{
public static void main(String[] args) {
MyThread myThread = new MyThread();
System.out.println(Thread.currentThread().getName()+":運(yùn)行了");
myThread.start();
}
}實(shí)現(xiàn)Runable接口創(chuàng)建多線程
/**
* @Author GocChin
* @Date 2021/5/11 12:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 實(shí)現(xiàn)Runable接口的方式創(chuàng)建多線程
* 1.創(chuàng)建一個(gè)實(shí)現(xiàn)了Runable接口的類
* 2.實(shí)現(xiàn)類去實(shí)現(xiàn)Runable中的抽象方法,run();
* 3.創(chuàng)建實(shí)現(xiàn)類的對(duì)象
* 4.將此對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對(duì)象
* 5.通過(guò)Thread類的對(duì)象調(diào)用start()
*/
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i<100;i++){
if (i%2!=0){
System.out.println(i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.創(chuàng)建實(shí)現(xiàn)類的對(duì)象
MThread mThread = new MThread();
//4.將此對(duì)象作為參數(shù)傳遞到Thread類的構(gòu)造器中,創(chuàng)建Thread類的對(duì)象
Thread thread = new Thread(mThread);
thread.start();
}
}Thread和Runable創(chuàng)建多線程對(duì)比
開(kāi)發(fā)中:優(yōu)先使用Runable
1.實(shí)現(xiàn)的方式?jīng)]有類的單繼承的局限性。
2.實(shí)現(xiàn)的方式跟適合處理多個(gè)線程有共享數(shù)據(jù)的情況。
聯(lián)系:Thread類中也實(shí)現(xiàn)了Runable,兩種方式都需要重寫(xiě)run()。
實(shí)現(xiàn)Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Author GocChin
* @Date 2021/5/11 13:03
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class MCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<100;i++){
sum+=i;
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
//執(zhí)行Callable 方式,需要FutureTask 實(shí)現(xiàn)實(shí)現(xiàn),用于接收運(yùn)算結(jié)果
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new MCallable());
new Thread(integerFutureTask).start();
//接受線程運(yùn)算后的結(jié)果
Integer integer = null;
try {
integer = integerFutureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}與Runable相比,Callable功能更強(qiáng)大
相比run()方法可以有返回值
方法可以拋出異常
支持泛型的返回值
需要借助FutureTask類,比如獲取返回結(jié)果
使用線程池進(jìn)行創(chuàng)建
線程池創(chuàng)建的好處
- 提高響應(yīng)速度(減少了創(chuàng)建新線程的時(shí)間)
- 降低資源消耗(重復(fù)利用線程池中線程,不需要每次都創(chuàng)建)
- 便于線程管理:
corePoolSize:核心線程池的大小
maximumPoolSize:最大線程數(shù)
keepAliveTime:線程沒(méi)有任務(wù)時(shí)最多保持多長(zhǎng)時(shí)間后悔中止
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author GocChin
* @Date 2021/5/11 13:10
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class Thread1 implements Runnable{
@Override
public void run() {
for (int i=1;i<30;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//創(chuàng)建線程池
ExecutorService executorService= Executors.newFixedThreadPool(10);
Thread1 threadPool = new Thread1();
for (int i=0;i<5;i++){
//為線程池分配任務(wù)
executorService.submit(threadPool);
}
//關(guān)閉線程池
executorService.shutdown();
}
}Thread中的常用方法start():
- start():?jiǎn)?dòng)當(dāng)前線程;調(diào)用當(dāng)前線程的run();
- run():通常需要重寫(xiě)Thread類中的此方法,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中。
- currentThread():靜態(tài)方法,返回當(dāng)前代碼的線程。
- getName():獲取當(dāng)前線程的名字。
- setName():設(shè)置當(dāng)前線程的名字。
- yield():釋放當(dāng)前cpu的執(zhí)行權(quán),切換線程執(zhí)行。
- join():在線程a中調(diào)用線程b的join(),此時(shí)線程a會(huì)進(jìn)入阻塞狀態(tài),知道線程b完全執(zhí)行完畢,線程a 才結(jié)束阻塞狀態(tài)。
- stop():強(qiáng)制線程生命期結(jié)束。(過(guò)時(shí)了,不建議使用)
- isAlive():判斷線程是否還活著。
- sleep(long millitime):讓當(dāng)前線程睡眠指定的事milltime毫秒。在指定的millitime毫秒時(shí)間內(nèi),當(dāng)前線程是阻塞狀態(tài)。
線程的優(yōu)先級(jí)
線程的優(yōu)先級(jí)等級(jí)
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
涉及的方法
- getPriority():返回線程的優(yōu)先值
- setPriority(int newPriority):改變線程的優(yōu)先級(jí)
說(shuō)明
- 線程創(chuàng)建時(shí)繼承父線程的優(yōu)先級(jí)
- 低優(yōu)先級(jí)知識(shí)獲得調(diào)度的概率低,并非一定是在高優(yōu)先級(jí)線程之后才被調(diào)用
線程的同步

多線程賣票
基于實(shí)現(xiàn)Runable的方式實(shí)現(xiàn)多線程買票
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 13:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 創(chuàng)建三個(gè)窗口買票,總票數(shù)為100張,使用Runable接口的方式
* 存在線程安全問(wèn)題,待解決
*/
class Thread2 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket>0) {
System.out.println(Thread.currentThread().getName() + ":買票,票號(hào)為:" + ticket);
ticket--;
}else {
break;
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Thread2 thread2 = new Thread2();
Thread t1 = new Thread(thread2);
Thread t2 = new Thread(thread2);
Thread t3 = new Thread(thread2);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}實(shí)現(xiàn)結(jié)果,存在重復(fù)的票

如果在買票方法中加入sleep函數(shù)
public void run() {
while (true){
if (ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":買票,票號(hào)為:" + ticket);
ticket--;
}else {
break;
}
}
}則運(yùn)行結(jié)果可能會(huì)出現(xiàn)-1,表示也是不正常的

理想情況

極端情況

在java中,我們通過(guò)同步機(jī)制,來(lái)解決線程的安全問(wèn)題。
同步代碼塊
synchronized(同步監(jiān)視器){
//需要被同步的代碼
}說(shuō)明
- 操作共享數(shù)據(jù)的代碼就是需要被同步的代碼。
- 共享數(shù)據(jù):多個(gè)線程共同操作的變量,比如本題中的ticket就是共享數(shù)據(jù)。
- 同步監(jiān)視器:俗稱:鎖。任何一個(gè)類的對(duì)象都可以充當(dāng)鎖。要求:
多個(gè)線程必須要共用統(tǒng)一把鎖。 - 同步的方式,解決了線程的安全問(wèn)題---好處。但是操作同步代碼時(shí),只能有一個(gè)線程參與,其他線程等待。相當(dāng)于是一個(gè)單線程的過(guò)程,效率低。-----局限性
- 使用Runable接口創(chuàng)建多線程的方式中,可以使用
this關(guān)鍵字;在繼承Thread類中創(chuàng)建多線程中,慎用this充當(dāng)同步監(jiān)視器,可以考慮使用當(dāng)前類充當(dāng)同步監(jiān)視器。Class clazz = Windows.class因此 類也是一個(gè)對(duì)象 - 包裹操作共享數(shù)據(jù)的代碼
不能多也不能少
修改之后的代碼:
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 13:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 創(chuàng)建三個(gè)窗口買票,總票數(shù)為100張,使用Runable接口的方式
* 存在線程安全問(wèn)題,待解決
*/
class Thread2 implements Runnable{
private int ticket=100;
Object object = new Object();
@Override
public void run() {
while (true){
synchronized(object) { //括號(hào)中的內(nèi)容可以直接使用當(dāng)前對(duì)象this去充當(dāng)
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":買票,票號(hào)為:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Thread2 thread2 = new Thread2();
Thread t1 = new Thread(thread2);
Thread t2 = new Thread(thread2);
Thread t3 = new Thread(thread2);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
結(jié)果

繼承Thread的方式,去使用同步代碼塊,需要將聲明的鎖對(duì)象設(shè)為statci,否則創(chuàng)建的對(duì)象的同步監(jiān)視器不唯一,就無(wú)法實(shí)現(xiàn)。
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 14:45
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class WindowsTest2 extends Thread{
private static int ticket=100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){ //這里不能使用this去充當(dāng),可以直接寫(xiě)一個(gè)Test.class 類也是對(duì)象
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":買票,票號(hào)為:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class Test2{
public static void main(String[] args) {
WindowsTest2 w1 = new WindowsTest2();
WindowsTest2 w2 = new WindowsTest2();
WindowsTest2 w3 = new WindowsTest2();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}同步方法
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個(gè)方法中,可以將此方法聲明為同步的。
通過(guò)實(shí)現(xiàn)Runable的方式實(shí)現(xiàn)同步方法。
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 13:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 創(chuàng)建三個(gè)窗口買票,總票數(shù)為100張,使用Runable接口的方式
* 存在線程安全問(wèn)題,待解決
*/
class Thread3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":買票,票號(hào)為:" + ticket);
ticket--;
}
}
}
public class Test3 {
public static void main(String[] args) {
Thread3 thread3 = new Thread3();
Thread t1 = new Thread(thread3);
Thread t2 = new Thread(thread3);
Thread t3 = new Thread(thread3);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
通過(guò)實(shí)現(xiàn)繼承Thread的方式實(shí)現(xiàn)同步方法。使用的同步監(jiān)視器是this,則不唯一,就會(huì)報(bào)錯(cuò)。所以將該方法定義為static。當(dāng)前的同步換時(shí)期就變成Test4.class了
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 14:45
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class WindowsTest4 extends Thread{
private static int ticket=100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
show();
}
}
public static synchronized void show(){//同步監(jiān)視器不是this了,而是當(dāng)前的類
// public synchronized void show(){//同步監(jiān)視器是this ,t1,t2,t3
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":買票,票號(hào)為:"+ticket);
ticket--;
}
}
}
public class Test4{
public static void main(String[] args) {
WindowsTest4 w1 = new WindowsTest4();
WindowsTest4 w2 = new WindowsTest4();
WindowsTest4 w3 = new WindowsTest4();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
總結(jié)
- 同步方法仍然設(shè)計(jì)到同步監(jiān)視器,只是不需要我們?nèi)ワ@示的聲明。
- 非靜態(tài)的同步方法,同步監(jiān)視器是:this靜態(tài)的同步方法中,同步監(jiān)視器是類本身。
Lock鎖解決線程安全問(wèn)題
synchronize與lock的異同
相同
- 都可以解決線程安全問(wèn)題
不同
- synchronize機(jī)制在執(zhí)行相應(yīng)的同步代碼以后,自動(dòng)的釋放同步監(jiān)視器;Lock需要手動(dòng)的啟動(dòng)同步lock(),同時(shí)結(jié)束同步也需要手動(dòng)的實(shí)現(xiàn)unlock()。
建議優(yōu)先使用順序
Lock------>同步代碼塊(已經(jīng)進(jìn)入了方法體,分配了相應(yīng)資源)---->同步方法(在方法體之外)
package demo2;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author GocChin
* @Date 2021/5/11 15:58
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class Lock1 implements Runnable{
private int ticket=50;
//1.實(shí)例化
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
//2.調(diào)用lock鎖定方法
lock.lock();
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票號(hào)為:"+ticket);
ticket--;
}else{
break;
}
} finally {
//3.調(diào)用解鎖方法
lock.unlock();
}
}
}
}
public class LockTest1 {
public static void main(String[] args) {
Lock1 lock1 = new Lock1();
Thread t1 = new Thread(lock1);
Thread t2 = new Thread(lock1);
Thread t3 = new Thread(lock1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}到此這篇關(guān)于聊聊java多線程創(chuàng)建方式及線程安全問(wèn)題的文章就介紹到這了,更多相關(guān)java多線程創(chuàng)建內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- java線程池的四種創(chuàng)建方式詳細(xì)分析
- JavaEE的進(jìn)程,線程和創(chuàng)建線程的5種方式詳解
- Java的線程與進(jìn)程以及線程的四種創(chuàng)建方式
- Java線程的三種創(chuàng)建方式
- Java線程創(chuàng)建的四種方式總結(jié)
- 如何在Java中創(chuàng)建線程通信的四種方式你知道嗎
- Java創(chuàng)建線程及配合使用Lambda方式
- java實(shí)現(xiàn)/創(chuàng)建線程的幾種方式小結(jié)
- 很多人竟然不知道Java線程池的創(chuàng)建方式有7種
- java 創(chuàng)建線程的四種方式
- Java創(chuàng)建線程的方式解析
相關(guān)文章
詳解springboot項(xiàng)目帶Tomcat和不帶Tomcat的兩種打包方式
這篇文章主要介紹了詳解springboot項(xiàng)目帶Tomcat和不帶Tomcat的兩種打包方式,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-09-09
java實(shí)現(xiàn)紙牌游戲之小貓釣魚(yú)算法
這篇文章主要為大家詳細(xì)介紹了java實(shí)現(xiàn)紙牌游戲之小貓釣魚(yú)算法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2019-01-01
SpringBoot接口中如何直接返回圖片數(shù)據(jù)
這篇文章主要介紹了SpringBoot接口中如何直接返回圖片數(shù)據(jù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Java編程中的vector類用法學(xué)習(xí)筆記
Vector通常被用來(lái)實(shí)現(xiàn)動(dòng)態(tài)數(shù)組,即可實(shí)現(xiàn)自動(dòng)增長(zhǎng)的對(duì)象數(shù)組,和C++一樣vector類同樣被Java內(nèi)置,下面就來(lái)看一下vector類的基本用法.2016-05-05

