基本线程同步1,2,3

基本线程同步(一)引言

在并发编程中发生的最常见的一种情况是超过一个执行线程使用共享资源。在并发应用程序中,多个线程读或写相同的数据或访问同一文件或数据库连接这是正常的。这些共享资源会引发错误或数据不一致的情况,我们必须通过一些机制来避免这些错误。

解决这些问题从临界区的概念开始。临界区是访问一个共享资源在同一时间不能被超过一个线程执行的代码块。

Java(和 几乎所有的编程语言)提供同步机制,帮助程序员实现临界区。当一个线程想要访问一个临界区,它使用其中的一个同步机制来找出是否有任何其他线程执行临界 区。如果没有,这个线程就进入临界区。否则,这个线程通过同步机制暂停直到另一个线程执行完临界区。当多个线程正在等待一个线程完成执行的一个临界 区,JVM选择其中一个线程执行,其余的线程会等待直到轮到它们。

本章展示了一些的指南,指导如何使用Java语言提供的两种基本的同步机制:

  • 关键字synchronized
  • Lock接口及其实现

基本线程同步(二)同步方法

在这个指南中,我们将学习在Java中如何使用一个最基本的同步方法,即使用 synchronized关键字来控制并发访问方法。只有一个执行线程将会访问一个对象中被synchronized关键字声明的方法。如果另一个线程试图访问同一个对象中任何被synchronized关键字声明的方法,它将被暂停,直到第一个线程结束方法的执行。

换句话说,每个方法声明为synchronized关键字是一个临界区,Java只允许一个对象执行其中的一个临界区。

静态方法有不同的行为。只有一个执行线程访问被synchronized关键字声明的静态方法,但另一个线程可以访问该类的一个对象中的其他非静态的方法。 你必须非常小心这一点,因为两个线程可以访问两个不同的同步方法,如果其中一个是静态的而另一个不是。

如果这两种方为了学习这个概念,我们将实现一个有两个线程访问共同对象的示例。我们将有一个银行帐户和两个线程:其中一个线程将钱转移到帐户而另一个线程将从账户中扣款。在没有同步方法,我们可能得到不正确的结果。同步机制保证了账户的正确。法改变相同的数据,你将会有数据不一致 的错误。

1.创建一个Account类来模拟我们的银行账户。它只有一个double类型的属性,名为balance。

2.实现setBalance()和getBalance()方法来写和读balance属性的值。

3..实现一个addAmount()方法,用来根据传入的参数增加balance的值。由于应该只有一个线程能改变balance的值,所以使用synchronized关键字将这个方法转换成临界区。

4.实现一个subtractAmount()方法,用来根据传入的参数减少balance的值。由于应该只有一个线程能改变balance的值,所以使用synchronized关键字将这个方法转换成临界区。

5.实现一个类来模拟ATM,它调用subtractAmount()方法来减少账户上的余额(balance值)。这个类必须实现Runnable接口,作为一个线程执行。

6.在这个类中,添加一个Account对象。实现构造器用来初始化account的值。

7.实现run()方法。它将调用100次account对象上的subtractAmount()方法,用来减少余额(balance值)

8.实现一个类来模拟公司,它调用addAmount()方法来增加账户上的余额(balance值)。这个类必须实现Runnable接口,作为一个线程执行。

9.在这个类中,添加一个Account对象。实现构造器用来初始化account的值。

10.实现run()方法。它将调用100次account对象上的addAmount()方法,用来增加余额(balance值)。

11.通过创建一个类,类名为main,包含main()方法来实现应用程序的主类。

12.创建一个Account对象,并且初始化balance值为1000。

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

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

15.在控制台打印balance初始值。

启动这些线程。

16.等待两个使用join()方法结束的线程,并且在控制台打印账户的最终余额(balance值)。

package concurrency.study.chap02;

public class Account {
	private double balance;

	public double getBalance() {
		return balance;
	}

	public void setBalance(double balance) {
		this.balance = balance;
	}

	public synchronized void addAmount(double amount) {
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		tmp += amount;
		balance = tmp;
	}

	public synchronized void subtractAmount(double amount) {
		double tmp = balance;
		try {
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		tmp -= amount;
		balance = tmp;
	}
}
package concurrency.study.chap02;

public class Bank implements Runnable {
	private Account account;

	public Bank(Account account) {
		this.account = account;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			account.subtractAmount(1000);
		}
	}
}
package concurrency.study.chap02;

public class Company implements Runnable {

	private Account account;

	public Company(Account account) {
		this.account = account;
	}

	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			account.addAmount(1000);
		}
	}
}
package concurrency.study.chap02;

public class AccountMain {
	public static void main(String[] args) {
		Account account = new Account();
		account.setBalance(1000);
		Company company = new Company(account);
		Thread companyThread = new Thread(company);

		Bank bank = new Bank(account);
		Thread bankThread = new Thread(bank);

		System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
		companyThread.start();
		bankThread.start();

		try {
			companyThread.join();
			bankThread.join();
			System.out.printf("Account : Final Balance: %f\n", account.getBalance());
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

在 这个指南中,你已经开发了一个增加和减少模拟银行账户的类的余额的应用程序。在这个程序中,每次都调用100次addAmount()方法来增加1000 的余额和调用100次subtractAmount()方法来减少1000的余额。你应该期望最终的余额和初始的余额是相等的。你试图促使一个错误情况使 用tmp变量来存储账户余额,所以你读取帐户余额,你增加临时变量的值,然后你再次设置账户的余额值。另外,你通过使用Thread类的sleep()方 法引入一个小延迟,让执行该方法的线程睡眠10毫秒,所以,如果另一个线程执行该方法,它可以修改账户的余额来引发一个错误。这是 synchronized关键字机制,避免这些错误。

如果你想看到并发访问共享数据的问题,那么就删除addAmount()和 subtractAmount()方法的synchronized关键字,然后运行该程序。在没有synchronized关键字的情况下,当一个线程在 睡眠后再读取账户的余额,另一个方法将读取该账户的余额。所以这两个方法将修改相同的余额并且其中一个操作不会反映在最终的结果。

用synchronized关键字,在并发应用程序中,我们保证了正确地访问共享数据。

如我们在介绍中提到的这个指南,只有一个线程能访问一个对象的声明为synchronized关键字的方法。如果一个线程A正在执行一个 synchronized方法,而线程B想要执行同个实例对象的synchronized方法,它将阻塞,直到线程A执行完。但是如果线程B访问相同类的不同实例对象,它们都不会被阻塞。

不止这些…

synchronized关键字不利于应用程序的性能,所以你必须仅在修改共享数据的并发环境下的方法上使用它。如果你有多个线程正在调用一个synchronized方法,在同一时刻只有一个线程执行它,而其他的线程将会等 待。如果这个操作没有使用synchronized关键字,所有线程可以在同一时刻执行这个操作,减少总的执行时间。如果你知道一个方法将不会被多个线程 调用,请不要使用synchronized关键字。

你可以使用递归调用synchronized方法。当线程访问一个对象的synchronized方法,你可以调用该对象的其他synchronized方法,包括正在执行的方法。它将不会再次访问synchronized方法。

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

19.启动这两个线程。

20.等待线程执行完成。

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

package concurrency.study.chap02;

public class Cinema {
	long vacanciesCinema1, vacanciesCinema2;

	private final Object controlCinema1, controlCinema2;

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

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

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

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

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

	public long getVacanciesCinema1() {
		return vacanciesCinema1;
	}

	public long getVacanciesCinema2() {
		return vacanciesCinema2;
	}

}
package concurrency.study.chap02;

public class TicketOffice1 implements Runnable {

	private Cinema cinema;

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

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

}
package concurrency.study.chap02;

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);
		cinema.sellTickets1(2);
		cinema.sellTickets1(1);
		cinema.returnTickets2(2);
		cinema.sellTickets1(3);
		cinema.sellTickets2(2);
		cinema.sellTickets1(2);
	}
}
package concurrency.study.chap02;

public class CinemaMain {
	public static void main(String[] args) {
		Cinema cinema = new Cinema();
		TicketOffice1 ticketOffice1 = new TicketOffice1(cinema);
		Thread thread1 = new Thread(ticketOffice1, "TicketOffice1");
		TicketOffice2 ticketOffice2 = new TicketOffice2(cinema);
		Thread thread2 = new Thread(ticketOffice2, "TicketOffice2");
		thread1.start();
		thread2.start();
		try {
			thread1.join();
			thread2.join();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		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属性。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值