在同步的类里安排独立属性
当你使用synchronized关键字来保护代码块时,你必须通过一个对象的引用作为参数。通常,你将会使用this关键字来引用执行该方法的对象,但是你也可以使用其他对象引用。通常情况下,这些对象被创建只有这个目的。比如,你在一个类中有被多个线程共享的两个独立属性。你必须同步访问每个变量,如果有一个线程访问一个属性和另一个线程在同一时刻访问另一个属性,这是没有问题的。
在这个指南中,你将学习如何解决这种情况的一个例子,编程模拟一家电影院有两个屏幕和两个售票处。当一个售票处出售门票,它们用于两个电影院的其中一个,但不能用于两个,所以在每个电影院的免费席位的数量是独立的属性。
准备工作
这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。
如何做…
按以下步骤来实现的这个例子:
1.创建一个Cinema类,添加两个long类型的属性,命名为vacanciesCinema1和vacanciesCinema2。
2 |
private long vacanciesCinema1; |
3 |
private long vacanciesCinema2; |
2.给Cinema类添加两个额外的Object属性,命名为controlCinema1和controlCinema2。
1 |
private final Object controlCinema1, controlCinema2; |
3.实现Cinema类的构造方法,初始化所有属性。
2 |
controlCinema1= new Object(); |
3 |
controlCinema2= new Object(); |
4.实现sellTickets1()方法,当第一个电影院出售一些门票将调用它。使用controlCinema1对象来控制访问synchronized的代码块。
01 |
public boolean sellTickets1 ( int number) { |
02 |
synchronized (controlCinema1) { |
03 |
if (number<vacanciesCinema1) { |
04 |
vacanciesCinema1-=number; |
5.实现sellTickets2()方法,当第二个电影院出售一些门票将调用它。使用controlCinema2对象来控制访问synchronized的代码块。
01 |
public boolean sellTickets2 ( int number) { |
02 |
synchronized (controlCinema2) { |
03 |
if (number<vacanciesCinema2) { |
04 |
vacanciesCinema2-=number; |
6.实现returnTickets1()方法,当第一个电影院被退回一些票时将调用它。使用controlCinema1对象来控制访问synchronized的代码块。
1 |
public boolean returnTickets1 ( int number) { |
2 |
synchronized (controlCinema1) { |
3 |
vacanciesCinema1+=number; |
7.实现returnTickets2()方法,当第二个电影院被退回一些票时将调用它。使用controlCinema2对象来控制访问synchronized的代码块。
1 |
public boolean returnTickets2 ( int number) { |
2 |
synchronized (controlCinema2) { |
3 |
vacanciesCinema2+=number; |
8.实现其他两个方法,用来返回每个电影院空缺位置的数量。
1 |
public long getVacanciesCinema1() { |
2 |
return vacanciesCinema1; |
4 |
public long getVacanciesCinema2() { |
5 |
return vacanciesCinema2; |
9.实现TicketOffice1类,并指定它实现Runnable接口。
1 |
public class TicketOffice1 implements Runnable { |
10.声明一个Cinema对象,并实现该类(类TicketOffice1)的构造器用来初始化这个对象。
2 |
public TicketOffice1 (Cinema cinema) { |
11.实现run()方法,用来模拟在两个电影院的一些操作。
03 |
cinema.sellTickets1( 3 ); |
04 |
cinema.sellTickets1( 2 ); |
05 |
cinema.sellTickets2( 2 ); |
06 |
cinema.returnTickets1( 3 ); |
07 |
cinema.sellTickets1( 5 ); |
08 |
cinema.sellTickets2( 2 ); |
09 |
cinema.sellTickets2( 2 ); |
10 |
cinema.sellTickets2( 2 ); |
12.实现TicketOffice2类,并指定它实现Runnable接口。
1 |
public class TicketOffice2 implements Runnable { |
13.声明一个Cinema对象,并实现该类(类TicketOffice2)的构造器用来初始化这个对象。
2 |
public TicketOffice2 (Cinema cinema) { |
14.实现run()方法,用来模拟在两个电影院的一些操作。
03 |
cinema.sellTickets2( 2 ); |
04 |
cinema.sellTickets2( 4 ); |
05 |
cinema.sellTickets1( 2 ); |
06 |
cinema.sellTickets1( 1 ); |
07 |
cinema.returnTickets2( 2 ); |
08 |
cinema.sellTickets1( 3 ); |
09 |
cinema.sellTickets2( 2 ); |
10 |
cinema.sellTickets1( 2 ); |
15.通过创建类名为Main,且包括main()方法来实现这个示例的主类。
2 |
public static void main(String[] args) { |
16.声明和创建一个Cinema对象。
1 |
Cinema cinema= new Cinema(); |
17.创建一个TicketOffice1对象,并且用线程来运行它。
1 |
TicketOffice1 ticketOffice1= new TicketOffice1(cinema); |
2 |
Thread thread1= new Thread(ticketOffice1, "TicketOffice1" ); |
18.创建一个TicketOffice2对象,并且用线程来运行它。
1 |
TicketOffice2 ticketOffice2= new TicketOffice2(cinema); |
2 |
Thread thread2= new Thread(ticketOffice2, "TicketOffice2" ); |
19.启动这两个线程。
20.等待线程执行完成。
4 |
} catch (InterruptedException e) { |
21.两个电影院的空缺数写入控制台。
1 |
System.out.printf( "Room 1 Vacancies: %d\n" ,cinema.getVacanciesCinema1()); |
2 |
System.out.printf( "Room 2 Vacancies: %d\n" ,cinema.getVacanciesCinema2()); |
它是如何工作的…
当你使用synchronized关键字来保护代码块,你使用一个对象作为参数。JVM可以保证只有一个线程可以访问那个对象保护所有的代码块(请注意,我们总是谈论的对象,而不是类)。
注释:在这个示例中,我们用一个对象来控制vacanciesCinema1属性的访问。所以,在任意时刻,只有一个线程能修改该属性。用另一个对象来控制 vacanciesCinema2属性的访问。所以,在任意时刻,只有一个线程能修改这个属性。但是可能有两个线程同时运行,一个修改 vacancesCinema1属性而另一个修改vacanciesCinema2属性。
当你运行这个示例,你可以看到每个电影院的空缺数量的最后的结果总是预期的。在以下截图中,你可以看到应用程序的执行结果:
![3](https://ucc.alicdn.com/dtyz3662dkx3a/developer-article25679/20241009/271a7a7062864d039565ab3519f0905f.jpeg?x-oss-process=image/resize,w_1400/format,webp)
不止这些…
synchronize关键字还有其他重要用法,请见其他指南中解释这个关键字使用的参见部分。
参见
在第2章,基本线程同步中在同步代码中使用条件的指南。