博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
线程同步基础 -- synchronized篇
阅读量:6875 次
发布时间:2019-06-26

本文共 10492 字,大约阅读时间需要 34 分钟。

  hot3.png

 多个执行线程共享一个资源的情形是最常见的并发编程情景之一。在并发应用中常常遇到这样的情景:多个线程读或者写相同的数据,或者访问相同的文件或者数据库连接。为了防止这些共享资源可能出现错误或者数据不一致,人们引入了临界区(critical section)概念。临界区是一个用以访问共享资源的代码块,这个代码块中同一时间只允许一个线程执行。

    为了实现这个临界区,Java提供了同步机制。当一个线程试图访问一个临界区时,它将使用一种同步机制来查看是不是已经有其他线程进入临界区。如果没有,它就进入临界区,如果有,他就被同步机制挂起,直到进入的线程离开临界区。如果在等待进入临界区的线程不止一个,JVM会选择其中的一个,其余将继续等待。

    一、使用synchronized实现同步方法。如果一个对象已用synchronized关键字来生命,那么只有一个执行线程被允许访问它。如果其他线程试图访问这个对象的其他方法,它将被挂起,知道第一个线程执行完正在运行的方法。

    也就是说,每一个用synchronized关键字声明的方法都是临界区。静态方法则有不同的行为。用synchronized关键字声明的静态方法,同时只能够被一个线程访问,但是其他线程可以访问这个对象的其他非静态方法。必须谨慎这一点。

package org.concurrency.synchronization;/** * @author Administrator * 银行账户模型 */public class Account {	private double balance;//余额	public double getBalance() {		return balance;	}	public void setBalance(double balance) {		this.balance = balance;	}	/**	 * 转账,使余额增加	 * */	public synchronized void addAccount(double amount){		double tmp = balance;		System.out.printf("before_add_Account : Current Balance: %f\n",tmp);		try {			Thread.sleep(10);		} catch (InterruptedException e) {			// TODO Auto-generated catch block			e.printStackTrace();		}		tmp += amount;		this.balance = tmp;	}	/**	 * 转出,使余额减少	 * */	public synchronized void subtractAmount(double amount){		double tmp = balance;		System.out.printf("before_subtract_Account : Current Balance: %f\n",tmp);		try {			Thread.sleep(10);		} catch (InterruptedException e) {			// TODO Auto-generated catch block			e.printStackTrace();		}		tmp -= amount;		this.balance = tmp;		System.out.printf("subtract_Account : Current Balance: %f\n",this.getBalance());	}}package org.concurrency.synchronization;/** * @author Administrator * ATM模拟类 */public class Bank implements Runnable {	private Account account;		public Bank(Account account) {		super();		this.account = account;	}	@Override	public void run() {		// TODO Auto-generated method stub		for(int i = 0;i<10;i++){			account.subtractAmount(1000);		}	}}package org.concurrency.synchronization;/** * @author Administrator * 公司模拟类 */public class Company implements Runnable {	private Account account;		public Company(Account account) {		super();		this.account = account;	}	@Override	public void run() {		// TODO Auto-generated method stub		for(int i = 0;i<10;i++){			account.addAccount(1000);		}	}}package org.concurrency.synchronization;/** * @author Administrator * 线程启动类 */public class Main {	public static void main(String[] args) {		// TODO Auto-generated method stub		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);						companyThread.start();		bankThread.start();				try {			companyThread.join();			bankThread.join();						System.out.printf("Account : Final Balance: %f\n",account.getBalance());		} catch (InterruptedException e) {			// TODO Auto-generated catch block			e.printStackTrace();		}	}}运行结果:before_add_Account : Current Balance: 1000.000000before_add_Account : Current Balance: 2000.000000before_add_Account : Current Balance: 3000.000000before_subtract_Account : Current Balance: 4000.000000subtract_Account : Current Balance: 3000.000000before_subtract_Account : Current Balance: 3000.000000subtract_Account : Current Balance: 2000.000000before_subtract_Account : Current Balance: 2000.000000subtract_Account : Current Balance: 1000.000000before_subtract_Account : Current Balance: 1000.000000subtract_Account : Current Balance: 0.000000before_subtract_Account : Current Balance: 0.000000subtract_Account : Current Balance: -1000.000000before_subtract_Account : Current Balance: -1000.000000subtract_Account : Current Balance: -2000.000000before_subtract_Account : Current Balance: -2000.000000subtract_Account : Current Balance: -3000.000000before_subtract_Account : Current Balance: -3000.000000subtract_Account : Current Balance: -4000.000000before_subtract_Account : Current Balance: -4000.000000subtract_Account : Current Balance: -5000.000000before_subtract_Account : Current Balance: -5000.000000subtract_Account : Current Balance: -6000.000000before_add_Account : Current Balance: -6000.000000before_add_Account : Current Balance: -5000.000000before_add_Account : Current Balance: -4000.000000before_add_Account : Current Balance: -3000.000000before_add_Account : Current Balance: -2000.000000before_add_Account : Current Balance: -1000.000000before_add_Account : Current Balance: 0.000000Account : Final Balance: 1000.000000

    在示例中使用了临时变量,故意制造了一个错误场景:tmp先获取账户余额,然后进行数额累加,最后把最终结果更新为账户余额。此外,通过Thread.sleep()方法增加了延时,使得正在执行的线程休眠,而此时其他线程也可能执行这个方法,因此可能会变更余额,引发错误。而synchronized关键字避免了这类错误的发生。

    在没有synchronized关键字的情况下,一个线程读取了账户余额然后进入休眠,这个时候其他线程读取这个账户余额,最终这两个方法都将修改同一个余额,这将产生数据不一致,去除synchronized关键字,运行结果,最初和最后的金额不一致,并且多次运行,将获取多个不同的结果

before_subtract_Account : Current Balance: 1000.000000before_add_Account : Current Balance: 1000.000000before_add_Account : Current Balance: 0.000000subtract_Account : Current Balance: 0.000000before_subtract_Account : Current Balance: 0.000000subtract_Account : Current Balance: -1000.000000before_subtract_Account : Current Balance: -1000.000000before_add_Account : Current Balance: -1000.000000before_add_Account : Current Balance: -2000.000000subtract_Account : Current Balance: -2000.000000before_subtract_Account : Current Balance: -2000.000000before_add_Account : Current Balance: -1000.000000subtract_Account : Current Balance: -1000.000000before_subtract_Account : Current Balance: -1000.000000before_add_Account : Current Balance: 0.000000subtract_Account : Current Balance: -2000.000000before_subtract_Account : Current Balance: -2000.000000subtract_Account : Current Balance: 1000.000000before_subtract_Account : Current Balance: 1000.000000before_add_Account : Current Balance: 1000.000000subtract_Account : Current Balance: 0.000000before_subtract_Account : Current Balance: 0.000000before_add_Account : Current Balance: 2000.000000subtract_Account : Current Balance: -1000.000000before_subtract_Account : Current Balance: -1000.000000before_add_Account : Current Balance: 3000.000000subtract_Account : Current Balance: -2000.000000before_subtract_Account : Current Balance: -2000.000000before_add_Account : Current Balance: 4000.000000subtract_Account : Current Balance: -3000.000000Account : Final Balance: 5000.000000

 synchronized关键字会降低应用程序的性能,因此只能在并发情况中需要修改共享数据的方法上使用它。如果多个线程访问同一个synchronized方法,则只有一个线程可以访问,其他线程将等待。

可以递归调用被synchronized声明的方法。当线程访问一个对象的同步方法时,它还可调用这个对象的其他同步方法。

我们通过synchronized关键字来保护代码块的访问。应该这样利用synchronized关键字:方法的其余部分保持在synchronized代码块之外,以获取更好的性能。临界区(同一时间只能被一个线程访问的代码块)的访问应该尽可能的短。

二、使用非依赖属性实现同步。当使用synchronized关键字来保护代码块时,必须把对象引用作为传入参数。通常情况下,使用this关键字来引用执行方法所属对象。在类中有多个非依赖来属性,他们被多个线程共享,则必须同步每一个变量的访问,同一时刻只允许一个线程访问一个属性变量,其他线程访问令一个属性变量。下面实例将演示一个电影院售piao的场景。

package org.concurrency.synchronization;/** * @author Administrator * 电影院类 */public class Cinema {	private long vacanciesCinema1;	private long vacanciesCinema2;		private final Object controlCinema1;	private final Object controlCinema2;	public Cinema() {		controlCinema1 = new Object();		controlCinema2 = new Object();		 vacanciesCinema1 = 20;		 vacanciesCinema2 = 20;	}		/**	 * 卖出piao	 * */	public boolean selTickets1(int number){		synchronized (controlCinema1) {			if(number 

 工作原理

用synchronized关键字保护代码块时,我们使用对象作为它的传入参数。JVM保证同一时间只有一个线程能够访问这个对象的代码保护块。这个例子使用了一个对象来控制对vacanciesCinema1属性的访问,所以同一时间只有一个线程能够修改这个属性;使用了另一个对象来控制vacanciesCinema2属性的访问,所以同一时间只有一个线程能够修改这个属性。但是,这个离职允许同事运行两个线程:一个修改vacanciesCinema1属性,另一个修改vacanciesCinema2属性。

运行结果总是如下:

vacanciesCinema1:卖出3张vacanciesCinema2:卖出2张vacanciesCinema2:卖出4张vacanciesCinema1:卖出2张vacanciesCinema1:卖出2张vacanciesCinema2:卖出2张vacanciesCinema1:卖出1张vacanciesCinema2:退回2张vacanciesCinema1:退回3张vacanciesCinema1:卖出5张vacanciesCinema2:卖出2张vacanciesCinema1:卖出3张vacanciesCinema2:卖出2张vacanciesCinema2:卖出2张vacanciesCinema1:卖出2张vacanciesCinema2:卖出2张Room 1 Vacancies: 5Room 2 Vacancies: 6

 三、在同步代码中使用条件

    在并发编程中一个典型的问题就是生产者-消费者(Producer - Consumer)问题。我们有一个数据缓冲区,一个或多个数据生产者将把数据存入这个缓冲区,一个或多个数据消费者将数据从缓冲区取走。

    这个缓冲区是一个共享数据结构,必须使用同步机制控制对它的访问,例如使用synchronized关键字,但是会受到更多的限制。如果缓冲区是满的,生产者就不能再放入数据,如果数据是空的,消费者就不能读取数据。

    对这些场景,Java在Object类中提供了wait()、notify()和notifyAll()方法。线程可以在同步代码块中使用wait()方法。不能再同步代码块中调用wait()方法。当一个线程调用wait()方法时,JVM将这个线程置入休眠,并且释放控制这个同步代码块的对象,同时允许其他线程执行这个对象控制的其他同步代码块。为了唤醒这个线程,必须在这个对象控制的某个同步代码块儿中调用notify()或者notifyAll()方法。

package org.concurrency.synchronization.producer;import java.util.Date;import java.util.LinkedList;import java.util.List;public class EventStorage {	private int maxSize;	private List
storage; public EventStorage() { maxSize = 0; storage = new LinkedList
(); } public synchronized void set(){ while(storage.size() == maxSize){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } storage.add(new Date()); System.out.printf("Set : %d",storage.size()); notifyAll(); } public synchronized void get(){ while(storage.size() == 0){ try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } System.out.printf("Get: %d: %s",storage.size(),((LinkedList
)storage).poll()); notifyAll(); }}package org.concurrency.synchronization.producer;public class Producer implements Runnable { private EventStorage storage; public Producer(EventStorage storage) { super(); this.storage = storage; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i<10;i++){ storage.set(); } }}package org.concurrency.synchronization.producer;public class Producer implements Runnable { private EventStorage storage; public Producer(EventStorage storage) { super(); this.storage = storage; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i<10;i++){ storage.set(); } }}package org.concurrency.synchronization.producer;public class Consumer implements Runnable { private EventStorage storage; public Consumer(EventStorage storage) { super(); this.storage = storage; } @Override public void run() { // TODO Auto-generated method stub for(int i = 0;i<10;i++){ storage.get(); } }}package org.concurrency.synchronization.producer;public class Main_Producer { public static void main(String[] args) { // TODO Auto-generated method stub EventStorage storage = new EventStorage(); Thread thread1 = new Thread(new Producer(storage)); Thread thread2 = new Thread(new Consumer(storage)); thread1.start(); thread2.start(); }}

    


 

转载于:https://my.oschina.net/mrku/blog/686736

你可能感兴趣的文章
linux lsattr命令: 显示文件属性
查看>>
linux as4 dns设置
查看>>
Tor介绍
查看>>
windows2003终端服务器超出了最大允许连接数
查看>>
linux 开启独立iptables日志
查看>>
shell之while循环
查看>>
FFmpeg avcodec_parameters_to_context函数剖析
查看>>
初探Cocos Creator
查看>>
常见的监控软件原理
查看>>
svn 合并分支到主干
查看>>
PHP学习笔记(<a href='?out=login'>)
查看>>
PLSQL子程序即PLSQL块
查看>>
exportfs+NFS客户端问题
查看>>
检测主机是否存活的脚本
查看>>
Linux LVM之快照
查看>>
flask学习总结
查看>>
apr_socket_recv: Connection reset by peer (104)
查看>>
JAVA环境搭建
查看>>
Linux运维 第二阶段 (三)软件安装
查看>>
Exchange 2016/2013 - 禁用OWA密码更改
查看>>