黑马程序员 关于线程的了解二,黑马程序员
黑马程序员 关于线程的了解二,黑马程序员
----------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学习型技术博客、期待与您交流!----------
相关文章
- 暂无相关文章
用户点评