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

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

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

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

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

准备工作

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

如何做…

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

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

private final Object controlCinema1, controlCinema2;

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

public class TicketOffice1 implements Runnable {

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

@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);
}

[/code]

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

[code lang=”java”]

public class TicketOffice2 implements Runnable {

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

@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);
}

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

Cinema cinema=new Cinema();

[/code]

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

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

19.启动这两个线程。

[code lang=”java”]

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

[/code]

20.等待线程执行完成。

[code lang=”java”]

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

[/code]

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

[code lang=”java”]

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

[/code]

它是如何工作的…

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

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

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

3

不止这些…

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

参见

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

原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 基本线程同步(三)在同步的类里安排独立属性

  • Trackback 关闭
  • 评论 (8)
    • yanbit
    • 2013/12/09 9:52上午

    当你使用synchronized关键字来保护代码块时,你必须通过一个对象的引用作为参数
    synchronized controlcinemal相当于一把锁,它到底锁住的是什么呢。不太明白为什么需要一个对象来作为锁,是因为这个对象中在jvm 堆内存中只有一份吗。这个解释起来是不是比较麻烦,有好的资料可以推荐一下吗
    synchronized (controlCinema1) {
    if (number<vacanciesCinema1) {
    vacanciesCinema1-=number;
    return true;
    } else {
    return false;
    }

    • 因为锁的信息是存放在对象的对象头里的。详见:http://ifeve.com/java-synchronized/

    • test
    • 2014/04/27 11:07上午

    我觉得这个例子有问题, synchronized关键字根本就没有起作用,起作用的join方法,他让thread2在等待。线程1和线程2不会同一时间去执行,我的理解有问题吗?请教各位.

      • 每天打老虎
      • 2014/06/09 3:30下午

      我也没大看懂,到底讲的东西有多少用处,两个object 就是两个monitor,两个monitor当然是两个临界区,不存在资源竞争问题。文章到底讲的什么意思,,没明白

      • 两个object分别用于两个资源的同步。文章主要意思是同步的对象不再使用this,可以对两个资源分开同步。

        • 每天打老虎
        • 2014/06/10 4:57下午

        明白,就是为了说明 不同minitor上的线程不存在同步关系。

    • 例子没有问题,两个线程先start了,主线程调用join,等待两个线程结束才结束。

    • kk
    • 2018/07/18 9:18下午

    第一种情况:如果有一个线程访问一个属性和另一个线程在同一时刻访问另一个属性,这是没有问题的
    public class TicketOffice2 implements Runnable {
    private Cinema cinema;
    public TicketOffice2 (Cinema cinema) {
    this.cinema=cinema;
    }

    @Override
    public void run() {
    cinema.sellTickets2(2);
    cinema.sellTickets2(4);
    }
    }

    public class TicketOffice1 implements Runnable {

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

    @Override
    public void run() {

    cinema.sellTickets1(5);
    cinema.sellTickets1(2);
    }
    }

    Main类不变,运行得到正确结果。

    第二种情况:就是文章中表述的。但是这里可以把如下代码注掉,加入Thread.sleep(1000);比如1000毫秒,这个毫秒可以足够长,太短不行。这样就能保证最后取出来的剩余座位数是正确的。
    try {
    /* thread1.join();
    thread2.join();*/
    Thread.sleep(1000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }

return top