黑马程序员 关于线程的了解二
来源:互联网 发布:炫酷导航网站源码 编辑:程序博客网 时间:2024/05/29 14:18
----------android培训、java培训、java学习型技术博客、期待与您交流!----------
互斥与同步
引子
由于多线程共享同一资源(临界资源),使得多线程程序结果会有不确定性。
怎么解决不确定性呢?以下两种方式可以部分控制不确定性:
线程互斥
线程同步
在熟悉一下两个概念:
临界区:用synchronized标记的代码段
临界资源:被临界区竞争的访问的资源
线程互斥
锁机制
线程互斥是使用锁机制来实现的,来看看锁机制:
标记出访问共享资源的代码段(Java就是用synchronized来标记代码段的,synchronized是个关键字),指明这段代码将作为一个整体以原子方式来访问共享资源;
给被访问的资源关联上一把锁;
当标记的的代码段(临界区)访问共享资源(临界资源)前,首先必须获得对象关联的锁;获得锁后将锁锁闭(lock),并开始实施访问;在标记的代码段访问结束后,释放锁;然后别的代码段就可以访问这个资源了。
只有对象才有锁,基本数据类型没有锁。
没有使用synchronized标记的代码段,锁机制不起作用。
无论是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学习型技术博客、期待与您交流!----------
- 黑马程序员 关于线程的了解二
- 黑马程序员 关于线程的了解一
- 【黑马程序员】关于线程的学习
- <黑马程序员>关于线程锁的使用
- 黑马程序员——JavaSE之多线程中关于锁的理解 二
- 黑马程序员之多线程二
- 黑马程序员-(多线程)了解线程与如何解决线程同步到来的安全问题!(面试)
- 黑马程序员 日记二:线程的互斥的学习
- 黑马程序员—创建线程的二种方式
- 黑马程序员——线程的总结(二)
- 黑马程序员--关于线程的一些笔记总结
- 黑马程序员——关于线程的总结
- 黑马程序员-关于线程继承Thread和Runnable的对比
- 黑马程序员-关于线程同步、死锁的小总结
- 黑马程序员-关于线程学习的若干总结
- 黑马程序员关于进程和线程的认知
- 黑马程序员 线程分析(二)
- 黑马程序员日记二线程 synchronized
- linux目录与文件的权限意义
- JSP运行环境的配置安装【原创】
- Linux 文件操作——系统调用和标准I/O库
- 多校九 hdu 4690 EBCDIC
- hdu2087 剪花布条
- 黑马程序员 关于线程的了解二
- Java 中正确使用 hashCode 和 equals 方法
- sleep和wait的区别
- POJ 1061
- win7 + ubuntu12.04 双系统 开机出现grub>界面
- 解决 通过继承QAbstractTableModel并实现headerData方法给QTableView添加标题时,标题不显示 的问题
- 正则表达式
- [日志]什么是好的选择?
- 今天和manager的谈话