当前位置: 代码迷 >> 编程 >> 黑马软件工程师 java 线程互斥与线程同步
  详细解决方案

黑马软件工程师 java 线程互斥与线程同步

热度:8247   发布时间:2013-02-26 00:00:00.0
黑马程序员 java 线程互斥与线程同步
----------android培训、java培训、java学习型技术博客、期待与您交流! ----------

互斥与同步

引子

由于多线程共享同一资源(临界资源),使得多线程程序结果会有不确定性。

怎么解决不确定性呢?以下两种方式可以部分控制不确定性:

线程互斥

线程同步

在熟悉一下两个概念:

临界区:用synchronized标记的代码段

临界资源:被临界区竞争的访问的资源

线程互斥

锁机制

线程互斥是使用锁机制来实现的,来看看锁机制:

  1. 标记访问共享资源的代码段Java就是用synchronized来标记代码段的,synchronized是个关键字),指明这段代码将作为一个整体以原子方式来访问共享资源;

  2. 给被访问的资源关联上一把锁;

  3. 当标记的的代码段(临界区)访问共享资源(临界资源)前,首先必须获得对象关联的锁;获得锁后将锁锁闭(lock),并开始实施访问;在标记的代码段访问结束后,释放锁;然后别的代码段就可以访问这个资源了。

  4. 只有对象才有锁基本数据类型没有锁。

  5. 没有使用synchronized标记的代码段,锁机制不起作用。

  6. 无论是synchronized正常结束还是异常退出,都会释放锁。

使用格式

Synchronized标记方式有两种:

  • synchronized(obj)area ; //obj是临界资源【一个对象】,area是临界区【一段代码】。

  • synchronized方法声明

    //比如:publicsynchronized void function(){……}

    //等价于publicvoid function(){synchronized(this){area}}(第一种表达方式)


谈到线程互斥问题有个很经典的案例就是银行存钱取钱问题,来实现一下:

/** * 存钱取钱问题 * 分析: * 账户类Account   取钱线程类Saver   存钱线程类Fetcher * 临界资源->账户类的实例   临界区->存取钱动作 * @author jin * */public class TakeSavingMoney {	/**	 * @param args	 */	public static void main(String[] args) {		// TODO Auto-generated method stub		Account account=new Account("晋瑜", 5000000);		// 存入1000000		(new Saver(account, 1000000)).start();		// 取出30000		(new Fetcher(account, 30000)).start();		// 取出450000		(new Fetcher(account, 450000)).start();		// 存入50000		(new Saver(account, 50000)).start();	}}class Account{	// 账户类	private String name;	// 账户名	private double money;	// 账户余额		public Account(String name, double money) {		// TODO Auto-generated constructor stub		this.name=name;		this.money=money;	}	public String getName(){		return this.name;	}	public double getMoney(){		return this.money;	}	public void put(double money){	// 存钱		this.money+=money;	}	public void get(double money){	// 取钱		this.money-=money;	}}class Saver extends Thread{	//存钱类	private Account account;	// 存钱人拥有一个账户	private double money;		// 将要存钱的数	public Saver(Account account, double money) {		// TODO Auto-generated constructor stub		this.account=account;		this.money=money;	}	@Override	public void run() {		// TODO Auto-generated method stub		synchronized (account) {	// account是临界资源			//临界区			System.out.println(account.getName()+"账户\n"+"现有:"+account.getMoney()+"元\n"+"存入:"+this.money+"元.");			account.put(this.money);			System.out.println("现有余额:"+account.getMoney());		}	}}class Fetcher extends Thread{	//取钱类	private Account account;	// 取款人持有一个账户	private double money;		// 将要取钱的数	public Fetcher(Account account, double money) {		// TODO Auto-generated constructor stub		this.account=account;		this.money=money;	}	@Override	public void run() {		// TODO Auto-generated method stub		synchronized (account) {				//临界区			System.out.println(account.getName()+"账户\n"+"现有:"+account.getMoney()+"元\n"+"取出:"+this.money+"元.");			account.get(this.money);			System.out.println("现有余额:"+account.getMoney());		}	}}运行结果:晋瑜账户现有:5000000.0元存入:1000000.0元.现有余额:6000000.0晋瑜账户现有:6000000.0元存入:50000.0元.现有余额:6050000.0晋瑜账户现有:6050000.0元取出:450000.0元.现有余额:5600000.0晋瑜账户现有:5600000.0元取出:30000.0元.现有余额:5570000.0

注意假如Saver中用synchronized了,而Fetcher中没有用锁,则在Saver线程占用临界资源时,Fetcher是可以访问的。

线程同步

同步与异步的概念

在学习线程同步前,我们也要理解下面两个概念:

异步:多个线程的运行互相独立,彼此间无依赖性;

同步:多个线程的运行满足特定的节奏。


同步实现

synchronized虽然有”同步”的意思,但它实现的首先是互斥机制,讲究的是消除对临界资源访问的竞争,而不关心访问线程之间的步调。而要实现同步:不仅要消除对临界资源访问的竞争,还要关心访问线程之间的步调。

所以,用以下公式来表达同步机制的实现再合适不过了:

Java的同步机制=存取共享资源的互斥机制+线程间的通信机制

存取共享资源的互斥机制我们已经知道了用synchronized来实现了,那线程间的通信怎么实现呢?

线程间的通信

线程间的通信通过Object类中的方法:wait()notify()notifyAll()来实现。

wait():暂停当前线程的执行,并释放所持有的锁,进入等待状态。

notify():唤醒一个等待线程。

notifyAll():唤醒所有等待的线程。

这三个方法都是Object类的final方法,所以不能被重写

这三个方法必须要与synchronized一起使用,只能直接或间接地用于临界区中。

注意:我在网上就看到了有位博友写的一篇文章(http://blog.csdn.net/zyplus/article/details/6672775),他说”从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){...}语句块内“,这是直接用于临界区,其实也可以:比如说用在临界资源的一个方法put()中,但临界区中有调用这个方法就可以了(我下面的那个生产者-消费者案例就是这样用的)。

下面再来看看一个有关线程同步的经典案例:生产者-消费者问题

/** * 生产者与消费者问题 * 分析 * 仓库类Warehouse   生产者类Producer   消费者类Consumer * 临界资源->仓库类Warehouse  临界区->存取产品的动作(代码) * @author jin * */public class ProducerConsumer {	/**	 * @param args	 */	public static void main(String[] args) {		// TODO Auto-generated method stub		WareHouse buffer=new WareHouse();		(new Producer(buffer)).start();		(new Consumer(buffer)).start();	}}class WareHouse{	private int product;	// 仓库中的产品	private boolean isEmpty=true;	// 标记仓库是否为空	public void put(int product_put){	//向仓库放入产品		// 若仓库不为空,则进入等待状态;		while(!isEmpty){			try {				wait();			} catch (InterruptedException e) {				// TODO Auto-generated catch block				e.printStackTrace();			}		}		// 若为空,则向仓库放入产品(放入了产品当能就要标记仓库不为空咯)并发出通知		this.product=product_put;		this.isEmpty=false;		notify();	}	public int get(){	//从仓库取出产品		// 若仓库为空,取产品这个动作的线程自然要进入等待状态了		while(isEmpty){			try {				wait();			} catch (InterruptedException e) {				// TODO Auto-generated catch block				e.printStackTrace();			}		}		// 若仓库不为空,返回产品(取出了产品仓库就空了,就要标记仓库为空)并发出通知		isEmpty=true;		notify();		return this.product;	}}class Producer extends Thread{	private WareHouse buffer;	// 将要访问的临界资源	public Producer(WareHouse buffer) {		// TODO Auto-generated constructor stub		this.buffer=buffer;	}		@Override	public void run() {		// TODO Auto-generated method stub		synchronized (buffer) {			for(int i=1; i<=5; i++){	// 假设生产5个产品:1、2、3、4、5				buffer.put(i);				System.out.println("存入产品:"+i);			}		}	}}class Consumer extends Thread{	private WareHouse buffer;	// 将要访问的临界资源	public Consumer(WareHouse buffer) {		// TODO Auto-generated constructor stub		this.buffer=buffer;	}		@Override	public void run() {		// TODO Auto-generated method stub		synchronized (buffer) {			for(int i=1; i<=5; i++){	// 生产了5个产品,我们也就5次去拿产品				System.out.println("取出产品:"+buffer.get());			}		}	}}

是吧,也可以间接使用在临界区



  相关解决方案