黑马程序员 关于线程的了解二

来源:互联网 发布:炫酷导航网站源码 编辑:程序博客网 时间:2024/05/29 14:18

----------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}}(第一种表达方式)

线程互斥的一个经典实例(模型:同一个人的不同动作)

题目:用多线程互斥的思想实现对某个银行账户的存取款操作的设计。

//创建账户class Account{private String name;//户主姓名private int balance;//余额,模拟ATM取款为整数public Account(String name,int balance) {this.name = name;this.balance = balance;}public String getName() {return name;}public int getBalance() {return balance;}public void saveMoney(int money){//存款this.balance += money;}public int withdrawMoney(int money){//取款if (money<=this.balance) {this.balance -= money;return money;}System.out.println("当前余额为:"+this.balance+"元,预取款:"+money+"元,余额不足!");return 0;}}

class SaveMoney extends Thread{//存款线程类private Account account;private int money;public SaveMoney(Account account, int money) {this.account = account;this.money = money;}public synchronized void run(){ int balance = this.account.getBalance();//获取存款前的余额this.account.saveMoney(money);System.out.println("账户:"+this.account.getName()+",余额:"+balance+"元,此次存入:"+money+"元,现余额:"+account.getBalance()+"元");}}

class WithdrawMoney extends Thread{//取款线程类private Account account;private int money;public WithdrawMoney(Account account, int money) {this.account = account;this.money = money;}public synchronized void  run(){int balance = this.account.getBalance();//获取取款前的余额System.out.println("账户:"+this.account.getName()+",余额:"+balance+"元,此次取款:"+this.account.withdrawMoney(money)+"元,现余额:"+this.account.getBalance()+"元");}}

public class SaveWithdrawMoney{public static void main(String[] args) {Account account = new Account("张三",0);//账户初始时余额为零new SaveMoney(account,300).start();new SaveMoney(account,600).start();new WithdrawMoney(account,200).start();new WithdrawMoney(account,15000).start();}}

说明:该例中,用户的存或取的操作先后是不确定的,但是该程序存取利用了同一把锁,保证了存取操作都是针对同一个用户进行的(从另一个角度说,就是这些操作都是该用户自己干的)。



线程同步

同步与异步的概念

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

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

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


同步实现

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()中,但临界区中有调用这个方法就可以了(我下面的那个生产者-消费者案例就是这样用的)。

线程同步的一个经典实例(模型:两组不同对象对同一共享变量操作,读写操作)。

说白了,该情形强调的是先后次序,也就是说必须等我这边忙完了,你那边才开始。

题目:用多线程同步思想实现对某个共享缓冲区的读写操作的设计,即一旦写入数据,马上读走。

public class ReaderAndWrite {public static void main(String[] args) {Buffer buffer = new Buffer();String[] datas = {"张三","李四","王五","赵六"};new writer(buffer,datas).start();new Reader(buffer,datas).start();}}

class Buffer{private String data;private boolean isNull = true;//读写线程通信的信号量,true表示缓冲区为空,可写public  void putData(String data){while (!isNull) {//等待isNull为true,即等待数据被读走try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}this.data = data;//此时isNull为true,即前一个数据已被取走isNull = false;//将信号量设为false,表示缓冲区不为空,用以通知读进程notify();//唤醒等待的读线程,以进行读取刚刚存入的数据}public  String getData(){while (isNull) {//此时若isNull为true,即无数据,则等待isNull为false,即等待写入数据try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}isNull = true;//将信号量设为true,表示缓冲区为空,用以通知写进程notify();//唤醒等待的写线程,以进行写操作return data;//有数据则返回当前数据}}

class writer extends Thread{private Buffer buffer;private String[] datas;public writer(Buffer buffer,String[] datas) {this.buffer = buffer;this.datas = datas;}public  void run(){//很明显,涉及到缓冲区,则可以将表示缓冲区的共享变量设为锁synchronized (buffer) {for(int i = 0;i<datas.length;i++){buffer.putData(datas[i]);System.out.println("写入:"+datas[i]);}}}}


class Reader extends Thread{private Buffer buffer;private String[] datas;public Reader(Buffer buffer,String[] datas) {this.buffer = buffer;this.datas = datas;}public void run(){//使用与写操作相同的缓冲区,则设置同一把锁synchronized (buffer) {for(int i = 0;i<datas.length;i++){System.out.println("读取:"+buffer.getData());}}}}


总结:主要的是理解同步机制下的互斥和同步。


 



 

----------android培训、java培训、java学习型技术博客、期待与您交流!----------

 

原创粉丝点击