这一节主要是继上次提到的线程同步三大方法:同步代码块、同步方法、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(); } }
- 结果