1.相关知识点
格式:
synchronized 返回值类型 方法名称(形参列表0) {//对当前对象(this)加锁
//代码(原子操作)
}
注意:只有调用同步方法或包含同步代码块的方法时,才需要对象的锁标记,其他的普通方法则不用
2.实例
以“丈夫”、“妻子”两条线程取钱为例【此例中运用到了代理模式】,何时加锁?–>写操作需要,读则不用
/**
*银行账户类
*/
public class Account {
//账号、密码、取钱数
private String cardnum;
private String password;
private Double money;
public Account(String cardnum, String password, Double money) {
this.cardnum = cardnum;
this.password = password;
this.money = money;
}
//取款(原子操作,从插卡验证,到取款成功的一系列步骤,不可缺少或打乱)
public synchronized void getmoney(String cardnum,String password,Double money) throws InterruptedException {
//模拟读卡
Thread.sleep(2000);
System.out.println("欢迎您,"+Thread.currentThread().getName()+",正在读卡中...");
//验证账户
if(this.cardnum.equals(cardnum)&&this.password.equals(password)){
//模拟读卡
Thread.sleep(1000);
System.out.println("验证成功!正在读取余额,请稍后...");
this.readmoney(this.money);
if(this.money>=money){
Thread.sleep(1000);
System.out.println("正在取钱,请留意出钞口!");
//余额减少
this.money-=money;
System.out.println(Thread.currentThread().getName()+"取款成功,账户余额为"+this.money+"元!");
}else{
System.out.println(Thread.currentThread().getName()+":抱歉,您的帐户余额不足!");
}
}else{
System.out.println("验证失败,请重新登录!");
}
}
// 读取存款,不是原子操作,无需加锁
public void readmoney(Double money) throws InterruptedException {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+":余额为"+money+"元!");
}
}
//丈夫线程
public class Husband implements Runnable {
Account account;
public Husband(Account account) {
this.account = account;
}
@Override
public void run() {
// 代理模式应用,该线程不知道取钱逻辑,于是委托atm线程去取钱
try {
//模拟中进来必须要休眠一秒,否则谁先启动谁就能抢到cpu,看不出两个线程的竞争
Thread.sleep(1000);
this.account.getmoney("12345","ac132",1000.00);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//妻子线程
package com.basic.multithreading.ATM;
/**
*妻子取钱线程
*/
public class Wife implements Runnable {
Account account;
public Wife(Account account) {
this.account = account;
}
@Override
public void run() {
// 代理模式应用,该线程不知道取钱逻辑,于是委托atm线程去取钱
try {
Thread.sleep(1000);
this.account.getmoney("12345","ac132",1000.00);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//test
public class ATMTest {
public static void main(String[] args) {
//临界资源:同一张银行卡
//临界资源对象只有一把锁!
Account account = new Account("12345","ac132",1900.00);
//Create threads
Thread husband = new Thread(new Husband(account),"丈夫");
Thread wife = new Thread(new Wife(account),"妻子");
// System.out.println(husband); //Thread[丈夫,5,main]
//Start threads
husband.start();
wife.start();
}
}
3.总结
上述实例中:
- 取款操作是原子操作,故对此方法做了同步处理(加锁);如果是读取存款,则不需要做同步处理,即不需要用锁标记
- 代码解析:
- 新建一个银行账户,这个银行账户作为临界资源,所以对它的写操作(getmoney)需要加锁,而对它的读操作(readmoney)则无需加锁
- 以唯一的account为参创建“丈夫”和“妻子”两个线程并启动两个线程
- 线程启动后自动调用run方法准备取钱,但是发现线程中并无取款的相关逻辑,于是委托account对象调用getmoney方法完成取款业务【代理模式】
- account对象调用getmoney方法,发现这个方法作了同步处理,于是竞争到的线程先进去执行业务,后抢到cpu的线程则等待getmoney“开锁”