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

简介:

声明:本文是《 Java 7 Concurrency Cookbook 》的第二章,作者: Javier Fernández González     译者:许巧辉  校对:方腾飞

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

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

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

准备工作

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

如何做…

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

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

public class Cinema {
private long vacanciesCinema1;
private long vacanciesCinema2;

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

private final Object controlCinema1, controlCinema2;

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

public Cinema(){
controlCinema1=new Object();
controlCinema2=new Object();
vacanciesCinema1=20;
vacanciesCinema2=20;
}

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

public boolean sellTickets1 (int number) {
synchronized (controlCinema1) {
if (number<vacanciesCinema1) {
vacanciesCinema1-=number;
return true;
} else {
return false;
}
}
}

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

public boolean sellTickets2 (int number) {
synchronized (controlCinema2) {
if (number<vacanciesCinema2) {
vacanciesCinema2-=number;
return true;
} else {
return false;
}
}
}

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

public boolean returnTickets1 (int number) {
synchronized (controlCinema1) {
vacanciesCinema1+=number;
return true;
}
}

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

public boolean returnTickets2 (int number) {
synchronized (controlCinema2) {
vacanciesCinema2+=number;
return true;
}
}

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

public long getVacanciesCinema1() {
return vacanciesCinema1;
}
public long getVacanciesCinema2() {
return vacanciesCinema2;
}

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

public class TicketOffice1 implements Runnable {

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

private Cinema cinema;
public TicketOffice1 (Cinema cinema) {
this.cinema=cinema;
}

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

@Override
public void run() {
cinema.sellTickets1(3);
cinema.sellTickets1(2);
cinema.sellTickets2(2);
cinema.returnTickets1(3);
cinema.sellTickets1(5);
cinema.sellTickets2(2);
cinema.sellTickets2(2);
cinema.sellTickets2(2);
}

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

public class TicketOffice2 implements Runnable {

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

private Cinema cinema;
public TicketOffice2 (Cinema cinema) {
this.cinema=cinema;
}

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

@Override
public void run() {
cinema.sellTickets2(2);
cinema.sellTickets2(4);
cinema.sellTickets1(2);
cinema.sellTickets1(1);
cinema.returnTickets2(2);
cinema.sellTickets1(3);
cinema.sellTickets2(2);
cinema.sellTickets1(2);
}

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

public class Main {
public static void main(String[] args) {

16.声明和创建一个Cinema对象。

Cinema cinema=new Cinema();

17.创建一个TicketOffice1对象,并且用线程来运行它。

TicketOffice1 ticketOffice1=new TicketOffice1(cinema);
Thread thread1=new Thread(ticketOffice1,"TicketOffice1");

18.创建一个TicketOffice2对象,并且用线程来运行它。

TicketOffice2 ticketOffice2=new TicketOffice2(cinema);
Thread thread2=new Thread(ticketOffice2,"TicketOffice2");

19.启动这两个线程。

thread1.start();
thread2.start();

20.等待线程执行完成。

try {
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}

21.两个电影院的空缺数写入控制台。

System.out.printf("Room 1 Vacancies: %d\n",cinema.getVacanciesCinema1());
System.out.printf("Room 2 Vacancies: %d\n",cinema.getVacanciesCinema2());

它是如何工作的…

当你使用synchronized关键字来保护代码块,你使用一个对象作为参数。JVM可以保证只有一个线程可以访问那个对象保护所有的代码块(请注意,我们总是谈论的对象,而不是类)。

注释:在这个示例中,我们用一个对象来控制vacanciesCinema1属性的访问。所以,在任意时刻,只有一个线程能修改该属性。用另一个对象来控制 vacanciesCinema2属性的访问。所以,在任意时刻,只有一个线程能修改这个属性。但是可能有两个线程同时运行,一个修改 vacancesCinema1属性而另一个修改vacanciesCinema2属性。

当你运行这个示例,你可以看到每个电影院的空缺数量的最后的结果总是预期的。在以下截图中,你可以看到应用程序的执行结果:

3

不止这些…

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

参见

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

文章转自 并发编程网-ifeve.com

目录
相关文章
|
10月前
|
安全 算法 Java
Java 多线程:线程安全与同步控制的深度解析
本文介绍了 Java 多线程开发的关键技术,涵盖线程的创建与启动、线程安全问题及其解决方案,包括 synchronized 关键字、原子类和线程间通信机制。通过示例代码讲解了多线程编程中的常见问题与优化方法,帮助开发者提升程序性能与稳定性。
424 0
|
7月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
396 1
|
7月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
368 1
|
安全 Java
Java多线程集合类
本文介绍了Java中线程安全的问题及解决方案。通过示例代码展示了使用`CopyOnWriteArrayList`、`CopyOnWriteArraySet`和`ConcurrentHashMap`来解决多线程环境下集合操作的线程安全问题。这些类通过不同的机制确保了线程安全,提高了并发性能。
266 1
【JavaEE】——多线程常用类
Callable的call方法,FutureTask类,ReentrantLock可重入锁和对比,Semaphore信号量(PV操作)CountDownLatch锁存器,
|
Java 程序员 调度
【JavaEE】线程创建和终止,Thread类方法,变量捕获(7000字长文)
创建线程的五种方式,Thread常见方法(守护进程.setDaemon() ,isAlive),start和run方法的区别,如何提前终止一个线程,标志位,isinterrupted,变量捕获
|
安全 Java API
【JavaEE】多线程编程引入——认识Thread类
Thread类,Thread中的run方法,在编程中怎么调度多线程
|
10月前
|
Java API 微服务
为什么虚拟线程将改变Java并发编程?
为什么虚拟线程将改变Java并发编程?
448 83
|
7月前
|
Java
如何在Java中进行多线程编程
Java多线程编程常用方式包括:继承Thread类、实现Runnable接口、Callable接口(可返回结果)及使用线程池。推荐线程池以提升性能,避免频繁创建线程。结合同步与通信机制,可有效管理并发任务。
297 6
|
机器学习/深度学习 消息中间件 存储
【高薪程序员必看】万字长文拆解Java并发编程!(9-2):并发工具-线程池
🌟 ​大家好,我是摘星!​ 🌟今天为大家带来的是并发编程中的强力并发工具-线程池,废话不多说让我们直接开始。
428 0

热门文章

最新文章