java多线程同步锁_Java多线程同步锁的理解

本文通过一个模拟售票的多线程程序,展示了未同步操作导致的问题,如卖出负数票。然后介绍了如何使用Java的synchronized关键字进行同步控制,以避免线程安全问题。首先通过在代码块中使用synchronized,然后将同步操作封装为同步方法,最后探讨了静态同步方法和静态同步代码块中使用的锁对象,分别是this和类的Class对象。
摘要由CSDN通过智能技术生成

java主要通过synchronized的关键字来实现的。让我们从一个买票程序说起吧。

packagecom.day04;/***

*@authorAdministrator 问题描述:使用多线程的方式来模拟多个窗口买票

**/

public class SaleWindow implementsRunnable {//初始化票数10

private int ticket = 10;

@Overridepublic voidrun() {//获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称

int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));

String saleWindowName= "销售窗口" +threadNum;//开始买票

while (true) {if (ticket > 0) {//这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)

try{

Thread.sleep(1000L);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(saleWindowName+ " 卖 出 了 " + ticket-- + " 号 票 !");

}else{break;

}

}

}public static voidmain(String[] args) {//创建了销售窗口对象

SaleWindow sw = newSaleWindow();//启动线程,让第一个窗口开始买票

newThread(sw).start();//启动线程,让第二个窗口开始买票

newThread(sw).start();//启动线程,让第三个窗口开始买票

newThread(sw).start();

}

}

运行结果如下所示:

销售窗口2 卖 出 了 10 号 票 !

销售窗口1 卖 出 了 8 号 票 !

销售窗口0 卖 出 了 9 号 票 !

销售窗口2 卖 出 了 7 号 票 !

销售窗口1 卖 出 了 6 号 票 !

销售窗口0 卖 出 了 5 号 票 !

销售窗口2 卖 出 了 4 号 票 !

销售窗口1 卖 出 了 3 号 票 !

销售窗口0 卖 出 了 2 号 票 !

销售窗口2 卖 出 了 1 号 票 !

销售窗口1 卖 出 了 0 号 票 !《-----

销售窗口0 卖 出 了 -1 号 票 !《------

可以看到我们的程序出来了问题,上面打红色箭头所示,竟然卖出了0号票和-1号票了。

让我们画个图来分析一下如下所示:

416ddc7cea738a82a34ceaa1b62ce79d.png

通过以上分析,不难得出,造成问题原因,是因为同步操作问题。

那我们如何确定哪些是同步操作(或者有同步问题)?

1.明确哪些代码是多线程成运行的代码(run方法中的代码)

2.明确那些是共享数据(ticket票数)

3.明确多线程运行代码中那些语句是操作共享数据(System.out.println(saleWindowName + " 卖 出 了 " + ticket-- + " 号 票 !");)

接下来我们就可以通过Java给我们提供的synchroized关键字使用同步锁来解决以上的问题

packagecom.day04;/***@authorAdministrator 问题描述:使用多线程的方式来模拟多个窗口买票*/

public class SaleWindow implementsRunnable {//初始化票数10

private int ticket = 10;//线程的锁

privateObject lock;

@Overridepublic voidrun() {//获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称

int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));

String saleWindowName= "销售窗口" +threadNum;//开始买票

while (true) {//加上synchronized,并加入对象锁,new一个任意对象即可,我们这里使用Object来解决同步问题,注意这里必须是公用同一个锁lock

synchronized(lock) {if (ticket > 0) {//这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)

try{

Thread.sleep(1000L);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(saleWindowName+ " 卖 出 了 " + ticket-- + " 号 票 !");

}else{break;

}

}

}

}public static voidmain(String[] args) {//创建了销售窗口对象

SaleWindow sw = newSaleWindow();//启动线程,让第一个窗口开始买票

newThread(sw).start();//启动线程,让第二个窗口开始买票

newThread(sw).start();//启动线程,让第三个窗口开始买票

newThread(sw).start();

}

}

运行结果如下所示:

销售窗口1 卖 出 了 10 号 票 !

销售窗口2 卖 出 了 9 号 票 !

销售窗口0 卖 出 了 8 号 票 !

销售窗口0 卖 出 了 7 号 票 !

销售窗口2 卖 出 了 6 号 票 !

销售窗口1 卖 出 了 5 号 票 !

销售窗口2 卖 出 了 4 号 票 !

销售窗口0 卖 出 了 3 号 票 !

销售窗口0 卖 出 了 2 号 票 !

销售窗口2 卖 出 了 1 号 票 !

这样就有效的解决了同步的问题。

同样我们也可将上面的操作共享数据的同步操作抽取出来,单独封装成一个同步方法,只需要在方法上面的返回值前面加上synchronized关键字即可,这样可以更方面理解和阅读,优化后代码如下。

packagecom.day04;/***

*@authorAdministrator 问题描述:使用多线程的方式来模拟多个窗口买票

**/

public class SaleWindow implementsRunnable {//初始化票数10

private int ticket = 10;

@Overridepublic voidrun() {//开始买票

while (true) {//当没有票了结束

if (!saleSuccess()) {break;

}

}

}public synchronized booleansaleSuccess() {//获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称

int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));

String saleWindowName= "销售窗口" +threadNum;if (ticket > 0) {//这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)

try{

Thread.sleep(1000L);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(saleWindowName+ " 卖 出 了 " + ticket-- + " 号 票 !");return true;

}else{return false;

}

}public static voidmain(String[] args) {//创建了销售窗口对象

SaleWindow sw = newSaleWindow();//启动线程,让第一个窗口开始买票

newThread(sw).start();//启动线程,让第二个窗口开始买票

newThread(sw).start();//启动线程,让第三个窗口开始买票

newThread(sw).start();

}

}

销售窗口1 卖 出 了 10 号 票 !销售窗口2 卖 出 了 9 号 票 !销售窗口0 卖 出 了 8 号 票 !销售窗口0 卖 出 了 7 号 票 !销售窗口2 卖 出 了 6 号 票 !销售窗口1 卖 出 了 5 号 票 !销售窗口2 卖 出 了 4 号 票 !销售窗口0 卖 出 了 3 号 票 !销售窗口0 卖 出 了 2 号 票 !销售窗口2 卖 出 了 1 号 票 !

现在又有一个问题出现了,public synchronized boolean saleSuccess()该同步函数用的是哪一个锁?

我们猜想可能用的是this这个对象锁,如果我们让线程一个执行带有sychronized的同步方法,一个执行带有this对象的sychronized同步代码块的方法,如果能够得到正确的结果,不出现同步问题,即论证正确,反之,如果还是出现同步问题即用的不是this这个对象锁。代码如下:

packagecom.day04;/***

*@authorAdministrator 问题描述:使用多线程的方式来模拟多个窗口买票

**/

public class SaleWindow implementsRunnable {//初始化票数10

private int ticket = 10;

@Overridepublic voidrun() {//获取当前线程的序号从0开始

int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));//偶数线程执行该方法

if ((threadNum + 1) % 2 == 0) {while (true) {synchronized (this) {

String saleWindowName= "奇数销售窗口" +threadNum;if (ticket > 0) {//这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)

try{

Thread.sleep(1000L);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(saleWindowName+ " 卖 出 了 " + ticket-- + " 号 票 !");

}else{break;

}

}

}

}else{//奇数线程执行该方法//开始买票

while (true) {//当没有票了结束

if (!saleSuccess()) {break;

}

}

}

}public synchronized booleansaleSuccess() {//获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称

int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));

String saleWindowName= "偶数销售窗口" +threadNum;if (ticket > 0) {//这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)

try{

Thread.sleep(1000L);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(saleWindowName+ " 卖 出 了 " + ticket-- + " 号 票 !");return true;

}else{return false;

}

}public static voidmain(String[] args) {//创建了销售窗口对象

SaleWindow sw = newSaleWindow();//启动线程,让第一个窗口开始买票

newThread(sw).start();//启动线程,让第二个窗口开始买票

newThread(sw).start();//启动线程,让第三个窗口开始买票

newThread(sw).start();

}

}

运行结果如下:

偶数销售窗口0 卖 出 了 10 号 票 !

偶数销售窗口0 卖 出 了 9 号 票 !

偶数销售窗口2 卖 出 了 8 号 票 !

奇数销售窗口1 卖 出 了 7 号 票 !

偶数销售窗口2 卖 出 了 6 号 票 !

偶数销售窗口2 卖 出 了 5 号 票 !

偶数销售窗口2 卖 出 了 4 号 票 !

偶数销售窗口2 卖 出 了 3 号 票 !

偶数销售窗口2 卖 出 了 2 号 票 !

偶数销售窗口0 卖 出 了 1 号 票 !

由上面的接口即可论证同步方法使用对的对象锁是this。

同样的加入我们的将我们的共享数据ticket改成静态的,并将同步方法也改成静态,它用的是那个对象锁?

我们猜想是本类的class对象这个锁即(SaleWindow.class)这个对象锁。同理如果我们让线程一个执行带有sychronized的静态同步方法,一个执行带有本类的class对象这个锁即(SaleWindow.class)的sychronized同步代码块的方法,如果能够得到正确的结果,不出现同步问题,即论证正确。反之,如果还是出现同步问题,即说明静态同步方法使用的锁不是本类的class对象这个锁即(SaleWindow.class)这个对象锁。代码如下:

packagecom.day04;/***

*@authorAdministrator 问题描述:使用多线程的方式来模拟多个窗口买票

**/

public class SaleWindow implementsRunnable {//初始化票数10

private static int ticket = 10;

@Overridepublic voidrun() {//获取当前线程的序号从0开始

int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));//偶数线程执行该方法

if ((threadNum + 1) % 2 == 0) {while (true) {synchronized (SaleWindow.class) {

String saleWindowName= "奇数销售窗口" +threadNum;if (ticket > 0) {//这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)

try{

Thread.sleep(1000L);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(saleWindowName+ " 卖 出 了 " + ticket-- + " 号 票 !");

}else{break;

}

}

}

}else{//奇数线程执行该方法//开始买票

while (true) {//当没有票了结束

if (!saleSuccess()) {break;

}

}

}

}public synchronized static booleansaleSuccess() {//获取线程的名称,比如Thread-0,并将它截掉Thread-取0这个数字标识,为了构造下面卖票窗口名称

int threadNum = Integer.parseInt(Thread.currentThread().getName().substring(7));

String saleWindowName= "偶数销售窗口" +threadNum;if (ticket > 0) {//这里为了演示出线程不同步的问题,让线程睡眠一段时间,延时)

try{

Thread.sleep(1000L);

}catch(InterruptedException e) {

e.printStackTrace();

}

System.out.println(saleWindowName+ " 卖 出 了 " + ticket-- + " 号 票 !");return true;

}else{return false;

}

}public static voidmain(String[] args) {//创建了销售窗口对象

SaleWindow sw = newSaleWindow();//启动线程,让第一个窗口开始买票

newThread(sw).start();//启动线程,让第二个窗口开始买票

newThread(sw).start();//启动线程,让第三个窗口开始买票

newThread(sw).start();

}

}

运行结果如下:

偶数销售窗口0 卖 出 了 10 号 票 !

偶数销售窗口0 卖 出 了 9 号 票 !

偶数销售窗口2 卖 出 了 8 号 票 !

奇数销售窗口1 卖 出 了 7 号 票 !

偶数销售窗口2 卖 出 了 6 号 票 !

偶数销售窗口2 卖 出 了 5 号 票 !

偶数销售窗口2 卖 出 了 4 号 票 !

偶数销售窗口2 卖 出 了 3 号 票 !

偶数销售窗口2 卖 出 了 2 号 票 !

偶数销售窗口0 卖 出 了 1 号 票 !

由上面的接口即可论证同步方法使用对的对象锁是本类的class对象这个锁即(SaleWindow.class)这个对象锁。

public synchronized booleansaleSuccess()

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值