Java中關于線程安全的三種解決方式
三個窗口賣票的例子解決線程安全問題
- 問題:買票過程中,出現(xiàn)了重票、錯票-->出現(xiàn)了線程的安全問題
- 問題出現(xiàn)的原因:當某個線程操作車票的過程中,尚未操作完成時,其他線程參與進來,也操作車票
- 如何解決:當一個線程a在操作ticket的時候,其他線程不能參與進來,知道線程a操作完ticket時,其他線程才可以開始操作ticket,這種情況即使線程a出現(xiàn)了阻塞,也不能被改變
- 在Java中,我們通過同步機制,來解決線程的安全問題。(線程安全問題的前提:有共享數(shù)據(jù))
方式一:同步代碼塊
synchronized(同步監(jiān)視器){
//需要被同步的代碼(或操作共享數(shù)據(jù)的代碼)
}
說明:
1.操作共享數(shù)據(jù)的代碼,即為需要被同步的代碼(不能包含代碼多了(變成單線程會效率低,也有可能會出錯),也不能包含代碼少了(沒包的會阻塞))
2.共享數(shù)據(jù):多個線程共同操作的變量。比如:ticket就是共享數(shù)據(jù)
3.同步監(jiān)視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
要求:多個線程必須要共用同一把鎖。(特別注意?。。。?!)
補充:在實現(xiàn)Runnable接口創(chuàng)建多線程的方式中,我們可以考慮使用(具體問題具體分析)this充當同步監(jiān)視器
class window1 implements Runnable{
private int ticket = 100;
// Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (this) {//此時的this:唯一的Window1的對象
// synchronized(obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣票,票號為:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest1 {
public static void main(String[] args) {
window1 w = new window1();//只造了一個對象,所以100張票共享
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("線程1");
t2.setName("線程2");
t3.setName("線程3");
t1.start();
t2.start();
t3.start();
}
}
class Window extends Thread{
private static int ticket = 100;//三個窗口共享:聲明為static
private static Object obj = new Object();
@Override
public void run() {
while(true) {
// synchronized (obj) {
synchronized (Window.class){//Class clazz = Window.class,Window.class只會加載一次
// synchronized (this) {//錯誤的方式:this代表著t1,t2,t3三個對象
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + ":賣票,票號為:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式二:同步方法
如果操作共享數(shù)據(jù)的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的。
4.同步的方式,解決了線程的安全問題。---->好處
操作同步代碼時,只能有一個線程參與,其他線程等待。相當于是一個單線程的過程,效率低。--->局限性
關于同步方法的總結:
1.同步方法仍然涉及到同步監(jiān)視器,只是不需要我們顯式的聲明。
2.非靜態(tài)的同步方法,同步監(jiān)視器是:this
靜態(tài)的同步方法,同步監(jiān)視器是:當前類本身
class window3 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while (true){
show();
}
}
public synchronized void show(){//同步監(jiān)視器:this(未顯示聲明而已)
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "賣票,票號為:" + ticket);
ticket--;
}
}
}
public class WindowTest3 {
public static void main(String[] args) {
window3 w = new window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("線程1");
t2.setName("線程2");
t3.setName("線程3");
t1.start();
t2.start();
t3.start();
}
}
class Window4 extends Thread{
private static int ticket = 100;//三個窗口共享:聲明為static
@Override
public void run() {
while(true){
show();
}
}
private static synchronized void show() {//同步監(jiān)視器:Window4.class(類)
// private synchronized void show() {//同步監(jiān)視器:t1,t2,t3。此種解決方式是錯誤的
if(ticket > 0){
System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket);
ticket--;
}
}
}
public class WindowTest4 {
public static void main(String[] args) {
Window4 t1 = new Window4();
Window4 t2 = new Window4();
Window4 t3 = new Window4();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
方式三:Lock鎖---JDK5.0新增
JDK5.0開始,Java提供了更強大的線程同步機制---通過顯式定義同步鎖對象來實現(xiàn)同步,同步鎖使用Lock對象充當。
java.util.concurrent.locks接口是控制多個線程對共享資源進行訪問的工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象加鎖,線程開始訪問共享資源之前應先獲得Lock對象
ReentrantLock類實現(xiàn)了Lock,它擁有與synchronized相同的并發(fā)性和內(nèi)存語義,在實現(xiàn)線程安全的控制中,比較常用的是ReentrantLock,可以顯式加鎖,釋放鎖。
class Window implements Runnable{
private int ticket = 100;
//1.實例化ReentrantLock
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() + "售票,票號為:" + ticket);
ticket--;
}else{
break;
}
} finally{
//3.調(diào)用解鎖方法:unlock();
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window w = new Window();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
線程的死鎖問題
1.死鎖的理解:不同的線程分別占用對方需要的同步資源不放棄,都在等待對方放棄自己的需要
的同步資源,就形成了線程的死鎖。
2.說明:
>出現(xiàn)死鎖后,不會出現(xiàn)異常,不會出現(xiàn)提示,只是所有的線程都處于阻塞狀態(tài),無法繼續(xù)
>我們使用同步時,要避免出現(xiàn)死鎖
3.解決方法
A.專門的算法、原則 B.盡量減少同步資源的定義 C.盡量避免嵌套同步
public class ThreadTest {
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){//匿名的方式繼承
@Override
public void run() {
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);//增加死鎖出現(xiàn)概率
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable(){//匿名的方式實現(xiàn)Runnable接口
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);//增加死鎖出現(xiàn)概率
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
synchronized和Lock的對比
1.Lock是顯示鎖(手動開啟和關閉鎖,別忘記關閉鎖),synchronized是隱式鎖,出了作用域自動釋放
2.Lock只有代碼塊鎖,synchronized有代碼塊鎖和方法鎖
3.使用Lock鎖,JVM將花費較少的時間來調(diào)度線程,性能更好。并且具有更好的拓展性(提供更多的子類)
4.synchronized 與 Lock的異同?
相同:二者都可以解決線程安全問題
不同:synchronized機制在執(zhí)行完相應的同步代碼以后,自動的釋放同步監(jiān)視器 Lock需要手動的啟動同步(lock()),同時結束同步也需要手動的實現(xiàn)(unlock())
優(yōu)先使用順序: Lock ->同步代碼塊(已經(jīng)進入了方法體,分配了相應資源) ->同步方法(在方法體之外)


到此這篇關于Java中關于線程安全的三種解決方式的文章就介紹到這了,更多相關Java 線程安全內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
java 二進制數(shù)據(jù)與16進制字符串相互轉化方法
今天小編就為大家分享一篇java 二進制數(shù)據(jù)與16進制字符串相互轉化方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧2018-07-07
Springboot應用中線程池配置詳細教程(最新2021版)
這篇文章主要介紹了Springboot應用中線程池配置教程(2021版),本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-03-03
IntelliJ IDEA中折疊所有Java代碼,再也不怕大段的代碼了
今天小編就為大家分享一篇關于IntelliJ IDEA中折疊所有Java代碼,再也不怕大段的代碼了,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2018-10-10
mybatis中<if>標簽bool值類型為false判斷方法
這篇文章主要給大家介紹了關于mybatis中<if>標簽bool值類型為false判斷方法,文中通過示例代碼介紹的非常詳細,對大家學習或者使用mybatis具有一定的參考學習價值,需要的朋友們下面來一起學習學習吧2019-08-08
Spring Boot整合FTPClient線程池的實現(xiàn)示例
這篇文章主要介紹了Spring Boot整合FTPClient線程池的實現(xiàn)示例,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-12-12
使用spring-boot-admin對spring-boot服務進行監(jiān)控的實現(xiàn)方法
這篇文章主要介紹了使用spring-boot-admin對spring-boot服務進行監(jiān)控的實現(xiàn)方法,需要的朋友可以參考下2018-02-02
Spring中事務管理方案和事務管理器及事務控制的API詳解
這篇文章主要介紹了Spring中事務管理方案和事務管理器及事務控制的API詳解,事務管理是指對事務進行管理和控制,以確保事務的正確性和完整性,事務管理的作用是保證數(shù)據(jù)庫的數(shù)據(jù)操作的一致性和可靠性,需要的朋友可以參考下2023-08-08

