理论教育 Java程序设计:线程同步

Java程序设计:线程同步

时间:2023-10-18 理论教育 版权反馈
【摘要】:在项目chapter12的src文件夹下新建一个名为cn.pzhu.syn的包,并在这个包下创建一个名为Example01的类,在这个类中实现售票操作,关键代码如下:在Example01.java中将售票的操作放在同步代码块中,同一时刻只允许一个线程访问同步代码块,只有当一个线程将判断售票数、售票、改变售票数全部执行完毕,才允许其他线程售票。图12.19程序运行结果从图12.19的运行结果可以看出,这种方式也可以保证售票的正确执行,和同步代码块的效果一样。

Java程序设计:线程同步

引起线程安全问题的原因其实是因为多个线程同时处理公共资源,因此要解决多线程的安全问题,只需要确保同一时刻只允许一个线程访问公共资源即可。

如上述示例中多窗口售票问题,就应该保证以下代码在同一时刻只能有一个线程(售票窗口)访问。

while (true) {

为了实现同一时刻只能有一个线程访问公共资源,那么就需要给这个公共资源加锁,就像一个人进入公用电话亭之后将门锁上,其他人就不能再进入电话亭,直到这个人出来之后将锁打开,其他人才可以进入公用电话亭。Java为多线程同步提供了两种方法,一种是将代码放在synchronized修饰的代码块中,另外一种是用synchronized关键字来修饰访问公共资源的方法。

1.同步代码块

同步代码块就是用关键字synchronized修饰的一段代码,在这段代码块中可以将处理公共资源的代码放在其中,这样就可以保证同一时刻只有一个线程能够操作公共资源。同步代码块的语法格式如下:

将可能造成线程安全问题的代码(操作公共资源的代码)放在synchronized同步代码块中,这样所有的线程在访问这段代码块时就需要先获得obj对象锁,只有获得锁的线程才能够执行,而其他线程会因为无法获得锁而进入BLOCKED状态,等到当前线程执行完同步代码块且释放对象锁之后,所有线程开始抢夺锁的拥有权,获得锁的线程再进入同步代码块开始执行,循环这个过程直到任务执行完毕为止。上述代码中的obj是一个锁对象,这个锁对象可以是任意一个对象,但多个线程共享的锁对象必须是唯一的。每个对象都存在一个标志位,该标志位表示这个对象是否被锁定,可以用0和1来表示,0表示对象已经被锁定,1表示对象没有被锁定。当线程运行到同步代码块时,会检查obj对象的锁状态,如果为0则表示已经有其他线程锁定了这个对象,即有其他线程在执行同步代码块,那么当前线程就会进入阻塞状态;当其他线程执行完同步代码块之后,obj会被解锁,状态置为1,这时其他线程才能执行同步代码块。

【例12.10】修改窗口售票的代码,将售票的代码放在synchronized关键字修饰的代码块中。

在项目chapter12的src文件夹下新建一个名为cn.pzhu.syn的包,并在这个包下创建一个名为Example01的类,在这个类中实现售票操作,关键代码如下:

在Example01.java中将售票的操作(判断票的余量、售票、线程休眠等操作)放在同步代码块中,同一时刻只允许一个线程(一个窗口)访问同步代码块,只有当一个线程将判断售票数、售票、改变售票数全部执行完毕,才允许其他线程售票。

说明:

代码中使用Object obj = new Object()方法创建了一个锁对象,用来保证线程安全。需要注意的是,锁对象的创建不能放在run()方法中,否则每个线程在运行run()方法的时候都会创建一个新的锁对象,无法实现多线程公用一个锁,这样就无法实现多个线程之间的同步效果。

运行结果如图12.18所示。

图12.18 程序运行结果

从图12.18的运行结果可以看出,售出的票到第10张就停止了,没有再出现多售或者是重复销售同一张票的情况。

2.同步方法

同步方法就是用关键字synchronized修饰的一个方法,可以将处理公共资源的所有操作都封装在这个方法中,其效果和同步代码块类似,只是锁定的范围大小不同而已,同步方法的语法格式如下:(www.daowen.com)

方法修饰符 synchronized 方法返回值类型 方法名(参数列表){

//可能造成线程安全问题的代码

}

被synchronized关键字修饰的方法在同一时刻只允许一个线程方法,其他线程如果访问该方法会进入阻塞状态,直到当前线程将该方法执行完毕之后,其他线程才有机会执行该方法。

说明:

synchronized关键字可以修饰对象、实例方法、静态方法,修饰的内容不同,其同步的范围也不同:

修饰对象,即作用于代码块,这需要给定一个唯一的任意对象,线程进入同步代码前要获得指定对象的锁;

修饰实例方法,那么调用这个方法的类的实例会被加锁,进入同步代码前要获得这个对象实例的锁;

修饰静态方法,那么该方法所在类的class对象会被加锁,进入同步代码前要获得当前类的class对象的锁。

【例12.11】修改窗口售票的代码,将售票的方法sell()用synchronized关键字进行修饰。

在cn.pzhu.syn包下创建一个名为Example02的类,在这个类中通过同步方法来实现线程安全的售票操作,关键代码如下:

上述代码,将所有售票的操作封装为了一个名为sell()的方法,在该方法中先判断已售票数是否小于总票数,如果小于则开始售票,并将已售票数加1;如果已售票数大于或等于总票数,则使用System.exit(0)结束线程的执行。

提示:

在使用同步方法时,要注意应该将操作公共资源的方法进行抽取,然后用synchronized关键字修饰这个抽取的方法,而不要直接对run()方法进行修饰,否则会因为run()方法被锁定而造成其他线程无法执行。

程序运行结果如图12.19所示。

图12.19 程序运行结果

从图12.19的运行结果可以看出,这种方式也可以保证售票的正确执行,和同步代码块的效果一样。

说明:

用同步代码块或同步方法来解决线程安全问题,虽然能够保证同一时间只有一个线程能够访问公共数据。但是所有线程在执行同步代码块或同步方法前都需要判断对应锁的状态,这会降低执行效率,且会消耗一定系统资源。

免责声明:以上内容源自网络,版权归原作者所有,如有侵犯您的原创版权请告知,我们将尽快删除相关内容。

我要反馈