基本线程同步(三)在同步的类里安排独立属性

简介:

在同步的类里安排独立属性

当你使用synchronized关键字来保护代码块时,你必须通过一个对象的引用作为参数。通常,你将会使用this关键字来引用执行该方法的对象,但是你也可以使用其他对象引用。通常情况下,这些对象被创建只有这个目的。比如,你在一个类中有被多个线程共享的两个独立属性。你必须同步访问每个变量,如果有一个线程访问一个属性和另一个线程在同一时刻访问另一个属性,这是没有问题的。

在这个指南中,你将学习如何解决这种情况的一个例子,编程模拟一家电影院有两个屏幕和两个售票处。当一个售票处出售门票,它们用于两个电影院的其中一个,但不能用于两个,所以在每个电影院的免费席位的数量是独立的属性。

准备工作

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.创建一个Cinema类,添加两个long类型的属性,命名为vacanciesCinema1和vacanciesCinema2。


1 public class Cinema {
2 private long vacanciesCinema1;
3 private long vacanciesCinema2;

2.给Cinema类添加两个额外的Object属性,命名为controlCinema1和controlCinema2。


1 private final Object controlCinema1, controlCinema2;

3.实现Cinema类的构造方法,初始化所有属性。


1 public Cinema(){
2 controlCinema1=new Object();
3 controlCinema2=new Object();
4 vacanciesCinema1=20;
5 vacanciesCinema2=20;
6 }

4.实现sellTickets1()方法,当第一个电影院出售一些门票将调用它。使用controlCinema1对象来控制访问synchronized的代码块。


01 public boolean sellTickets1 (int number) {
02 synchronized (controlCinema1) {
03 if (number<vacanciesCinema1) {
04 vacanciesCinema1-=number;
05 return true;
06 } else {
07 return false;
08 }
09 }
10 }

5.实现sellTickets2()方法,当第二个电影院出售一些门票将调用它。使用controlCinema2对象来控制访问synchronized的代码块。


01 public boolean sellTickets2 (int number) {
02 synchronized (controlCinema2) {
03 if (number<vacanciesCinema2) {
04 vacanciesCinema2-=number;
05 return true;
06 } else {
07 return false;
08 }
09 }
10 }

6.实现returnTickets1()方法,当第一个电影院被退回一些票时将调用它。使用controlCinema1对象来控制访问synchronized的代码块。


1 public boolean returnTickets1 (int number) {
2 synchronized (controlCinema1) {
3 vacanciesCinema1+=number;
4 return true;
5 }
6 }

7.实现returnTickets2()方法,当第二个电影院被退回一些票时将调用它。使用controlCinema2对象来控制访问synchronized的代码块。


1 public boolean returnTickets2 (int number) {
2 synchronized (controlCinema2) {
3 vacanciesCinema2+=number;
4 return true;
5 }
6 }

8.实现其他两个方法,用来返回每个电影院空缺位置的数量。


1 public long getVacanciesCinema1() {
2 return vacanciesCinema1;
3 }
4 public long getVacanciesCinema2() {
5 return vacanciesCinema2;
6 }

9.实现TicketOffice1类,并指定它实现Runnable接口。


1 public class TicketOffice1 implements Runnable {

10.声明一个Cinema对象,并实现该类(类TicketOffice1)的构造器用来初始化这个对象。


1 private Cinema cinema;
2 public TicketOffice1 (Cinema cinema) {
3 this.cinema=cinema;
4 }

11.实现run()方法,用来模拟在两个电影院的一些操作。


01 @Override
02 public void 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);
11 }

12.实现TicketOffice2类,并指定它实现Runnable接口。


1 public class TicketOffice2 implements Runnable {

13.声明一个Cinema对象,并实现该类(类TicketOffice2)的构造器用来初始化这个对象。


1 private Cinema cinema;
2 public TicketOffice2 (Cinema cinema) {
3 this.cinema=cinema;
4 }

14.实现run()方法,用来模拟在两个电影院的一些操作。


01 @Override
02 public void 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);
11 }

15.通过创建类名为Main,且包括main()方法来实现这个示例的主类。


1 public class 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.启动这两个线程。


1 thread1.start();
2 thread2.start();

20.等待线程执行完成。


1 try {
2 thread1.join();
3 thread2.join();
4 } catch (InterruptedException e) {
5 e.printStackTrace();
6 }

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

不止这些…

synchronize关键字还有其他重要用法,请见其他指南中解释这个关键字使用的参见部分。

参见

在第2章,基本线程同步中在同步代码中使用条件的指南。 

目录
相关文章
|
2月前
|
Java
【JavaEE】——多线程常用类
Callable的call方法,FutureTask类,ReentrantLock可重入锁和对比,Semaphore信号量(PV操作)CountDownLatch锁存器,
|
2月前
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
2月前
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
3月前
|
Java 调度
Java 线程同步的四种方式,最全详解,建议收藏!
本文详细解析了Java线程同步的四种方式:synchronized关键字、ReentrantLock、原子变量和ThreadLocal,通过实例代码和对比分析,帮助你深入理解线程同步机制。关注【mikechen的互联网架构】,10年+BAT架构经验倾囊相授。
Java 线程同步的四种方式,最全详解,建议收藏!
|
3月前
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
|
4月前
|
Java
在Java多线程编程中,实现Runnable接口通常优于继承Thread类
【10月更文挑战第20天】在Java多线程编程中,实现Runnable接口通常优于继承Thread类。原因包括:1) Java只支持单继承,实现接口不受此限制;2) Runnable接口便于代码复用和线程池管理;3) 分离任务与线程,提高灵活性。因此,实现Runnable接口是更佳选择。
92 2
|
4月前
|
Java
Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口
【10月更文挑战第20天】《JAVA多线程深度解析:线程的创建之路》介绍了Java中多线程编程的基本概念和创建线程的两种主要方式:继承Thread类和实现Runnable接口。文章详细讲解了每种方式的实现方法、优缺点及适用场景,帮助读者更好地理解和掌握多线程编程技术,为复杂任务的高效处理奠定基础。
68 2
|
4天前
|
Python
python3多线程中使用线程睡眠
本文详细介绍了Python3多线程编程中使用线程睡眠的基本方法和应用场景。通过 `time.sleep()`函数,可以使线程暂停执行一段指定的时间,从而控制线程的执行节奏。通过实际示例演示了如何在多线程中使用线程睡眠来实现计数器和下载器功能。希望本文能帮助您更好地理解和应用Python多线程编程,提高程序的并发能力和执行效率。
33 20
|
10天前
|
安全 Java C#
Unity多线程使用(线程池)
在C#中使用线程池需引用`System.Threading`。创建单个线程时,务必在Unity程序停止前关闭线程(如使用`Thread.Abort()`),否则可能导致崩溃。示例代码展示了如何创建和管理线程,确保在线程中执行任务并在主线程中处理结果。完整代码包括线程池队列、主线程检查及线程安全的操作队列管理,确保多线程操作的稳定性和安全性。
|
2月前
|
NoSQL Redis
单线程传奇Redis,为何引入多线程?
Redis 4.0 引入多线程支持,主要用于后台对象删除、处理阻塞命令和网络 I/O 等操作,以提高并发性和性能。尽管如此,Redis 仍保留单线程执行模型处理客户端请求,确保高效性和简单性。多线程仅用于优化后台任务,如异步删除过期对象和分担读写操作,从而提升整体性能。
79 1

热门文章

最新文章