这一节主要是继上次提到的线程同步三大方法:同步代码块、同步方法、Lock锁。
同步代码块,把出现线程安全问题的核心代码给上锁。
还是继上次的例子,对代码块加上synchronized ("getMoney") {}之后就不会出现线程安全问题了:
public void getMoney(double money) {
String names = Thread.currentThread().getName();
System.out.println(names+"进来取钱了!");
//会出现问题的代码块
synchronized ("getMoney") {//同步锁对象getMoney,
if (this.money >= money) {
System.out.println(names + "取出" + money);
this.money -= money;
System.out.println(ID + "剩余" + this.money);
} else
System.out.println(names + "取钱" + money + " 失败," + "剩余" + this.money);
}
}
锁对象唯一(字符串"getMoney")不太好,会锁住其他全部非共享线程。因此建议使用共享资源作为锁对象,对于实例方法建议使用this作为锁对象。 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
同步方法,出现线程安全问题的核心方法给上锁。
只需要在方法加上synchronized 修饰,这种方法就使得一整个方法加上了锁。
public synchronized void getMoney(double money){
该方法在底层是有隐式锁对象的,对于两个共享资源是不冲突的。
Lock锁,一个新的锁对象Lock。
在共享资源类里面加上private final Lock lock=new ReentrantLock(); —final了,唯一不可替换的锁。所以例子中,在账户里面加上这句,同一个账户就是同一把锁。然后再取钱方法中加上lock()上锁,使用完用unlock()解锁。
public void getMoney(double money){
String name=Thread.currentThread().getName();
//
lock.lock();
if(this.money>=money){
System.out.println(name+"取出"+money);
this.money-=money;
System.out.println(ID+"剩余"+this.money);
}
else
System.out.println(name+"取钱"+money+" 失败"+"剩余"+this.money);
lock.unlock();
}
为了防止加锁后出现异常,导致解锁问题,通常使用try...catch...finally;
lock.lock();
try {
if (this.money >= money) {
System.out.println(name + "取出" + money);
this.money -= money;
System.out.println(ID + "剩余" + this.money);
} else
System.out.println(name + "取钱" + money + " 失败" + "剩余" + this.money);
} finally {
lock.unlock();
}
线程通信
线程通信就是线程间相互发送数据,线程间共享一个资源即可实现线程通信,(保证线程安全的前提)。例如在生产者与消费者模型中,一个有限的空间仓库中,生产者线程生产完内容后唤醒消费者,然后停下来等待生产,消费者消费完该产品后唤醒生产者,停止消费。
线程通信的三个常见方法(Object类,当前同步锁对象进行调用):
void wait() :让当前线程等待并释放所占锁,直到另一个线程调用notify()方法或 notifyAll()方法;
void notify():唤醒正在等待的单个线程;
void notifyAll():唤醒正在等待的所有线程。
示例,简单的生产者与消费者模型案例:有一个亲情账户,最大金额是10,000,孩子可以取钱,父母存钱,还是假设每次都只能存取10000元为例。
创建一个账户类,有存、取方法;父母进行存操作方法后,存钱成功(当账户余0)就会唤醒其他进程(让消费者进行消费)并进入等待,存钱失败(账户余10,000)就自己等待;孩子进行取操作方法后,取钱成功(当账户有钱)就会唤醒其他进程(让生产者进行生产)并进入等待,取钱失败(账户余0)就自己等待;
class account{
private String ID;
private double money;
public account(){}
public account(String id,double money){
this.ID=id;
this.money=money;
}
//取
public synchronized void getMoney(double money){
String name=Thread.currentThread().getName();
System.out.println(name + " 取锁 *****");
try {
if (this.money >= money) {
System.out.print(name + "取出" + money+" - ");
this.money -= money;
System.out.println(ID + "剩余" + this.money);
this.notifyAll();//取完之后唤醒其他线程
this.wait();//自己等待,会释放锁
} else {
System.out.println(name + "取出失败!");
this.wait();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
//存
public synchronized void inMoney(double money) {
String name=Thread.currentThread().getName();
System.out.println(name + " 取锁 *****");
//
try {
if (this.money == 0) {
System.out.print(name + "存钱 " + money+" - ");
this.money += money;
System.out.println(ID + "剩余" + this.money);
this.notifyAll();
this.wait();
}
else {
System.out.println(name + "存入失败!");
this.wait();
}
}catch (InterruptedException e){
e.printStackTrace();
}
}
}
两个线程类的实现继承Thread,在里面无限次存取操作;
class getMoneyThread extends Thread{
private account a;
public getMoneyThread(account a,String name){
super(name);
this.a=a;
}
@Override
public void run() {
while (true) {
a.getMoney(10000);
try{
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class inMoneyThread extends Thread{
private account a;
public inMoneyThread(account a,String name){
super(name);
this.a=a;
}
@Override
public void run() {
while (true) {
a.inMoney(10000);
try{
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
创建账户对象和生产者消费者对象;
/**
* 多线程通信
*/
public class Demo_lock {
public static void main(String[] args) {
account acc=new account("happy",10000);
//消费者
new getMoneyThread(acc,"熊大").start();
new getMoneyThread(acc,"熊二").start();
//生产者
new inMoneyThread(acc,"父亲").start();
new inMoneyThread(acc,"母亲").start();
}
}
结果