首先我们说一下什么是线程安全问题
场景:现在银行账户有5000元、有两个线程t1和t2、由于这两个线程是异步执行的取款操作、有可能t1线程取款1000、还没等更新账户、t2就已经也取款1000、这样导致t1线程取款后、余额还剩4000、t2线程取款后、余额还剩4000、这样就导致了t1和t2两个线程不安全问题。简单来说、需要同步线程的时候却出现了异步、这就导致线程不安全。
案例(生产者消费者模式)
1、生产者和消费者应该操作共享资源
2、使用一个或多个线程来表示生产者
3、使用一个或多个线程来表示消费者
为什么生产者不直接把数据给消费者、而是先把数据存放到共享资源中、然后消费者再从共享资源取出数据。
从面相对象的角度来说:我们要实现低耦合的代码
——————————————————————————————————————
我们创建一个生产者
我们创建一个消费者
创建一个公共资源区
部分打印结果如下
下面我们来分析一下打印结果:
当生产者生产一个春哥-男时、这时候消费者还没有来得及取出来、然后生产者又往共享资源区存入了凤姐结果把春哥覆盖掉了、这时候消费者刚好取出凤姐-男。
问题:出现了性别紊乱的问题
解决方案:只要保证生产者在生产姓名和性别的过程中保持同步、中间消费者不能来取走。
等待唤醒机制:
wait():执行该方法的线程对象释放同步锁、,jvm把线程放到等待池中、等待其他线程来唤醒该线程。
notify():执行该方法的线程随机唤醒一个在等待池中的线程(等待池中的线程并不能执行代码)、把线程放到锁池中。
上述方法只能被同步监听锁对象来调用、否则报异常illegalMonitorStateException
假设a线程和b线程共同操作一个x对象,a、b线程可以通过x对象的wait()方法和notify()方法进行通信:
1、当a线程执行x对象的同步方法是、a线程室友x的对象锁,b线程没有执行机会、b线程在x对象的锁池中等待
2、a线程在同步方法中执行x.wait()方法是、a线程释放x对象锁、a线程进入等待池
3、在x对象的锁池中等待b线程获取x的对象锁、执行x对象的另一个同步方法
4、b线程在同步方法中执行x.notify()时、jvm把a线程从x对象的等待池移到锁池中、随时等待获取锁
5、b线程执行完同步方法释放锁、a线程获得锁、执行同步方法
/**
* 创建公共资源
*/
public class ShareResource {
private String name;//性名
private String gender;//性别
private boolean isEmpty = true;//判断资源对象是否为空
//向生产者提供一个生产的方法
public synchronized void push(String name, String gender) {
try {
//共享资源区初始值为空、if条件不成立
while (!isEmpty) {
//isEmpty为false、即共享资源区为部位空、不在生产
// 等待消费者来获取
//将线程Processor放入等待池、只能被其他线程唤醒
this.wait();
}
//生产开始
this.name = name;
Thread.sleep(500);
this.gender = gender;
//生产结束
//共享资源区不为空
isEmpty = false;
this.notify();//唤醒消费者消费
} catch (Exception e) {
e.printStackTrace();
}
}
//向消费者提供一个消费的方法
public synchronized void pull() {
try {
//isEmpty为空、进入等待池、等待生产者生产
while (isEmpty) {
this.wait();
}
//消费开始
Thread.sleep(500);
System.out.println(this.name + "-" + this.gender);
//消费结束
isEmpty = true;
this.notify();
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 创建消费者
*/
public class Consumer implements Runnable {
private ShareResource resource = null;
public Consumer(ShareResource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
resource.pull();
}
}
}
/**
* 创建生产者
*/
public class Producer implements Runnable {
private ShareResource resource = null;
public Producer(ShareResource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 50; i++) {
//先生产春哥-男、在生产凤姐-女
if (i % 2 == 0) {
resource.push("春哥", "男");
} else {
resource.push("凤姐", "女");
}
}
}
}
public class App {
public static void main(String[] args) {
//创建一个公共资源对象
ShareResource resource = new ShareResource();
//启动生产者线程
new Thread(new Producer(resource)).start();
//启动消费者线程
new Thread(new Consumer(resource)).start();
}
}
我们来分析一下等待唤醒机制
1、首先生产者消费者启动线程、异步进入临界区(公用资源区)。
2、消费者判断临界区为空、进入等待池等待。生产者判断临界区为空、开始生产。
3、生产者生产完毕后、临界区资源isEmpty = false、生产者唤醒消费者线程进入锁池、随时拿住对象锁来消费临界区资源。然后自己又要生产、开始判断临界区、进入等待池。
4、消费者消费资源、然后isEmpty = true、消费者唤醒生产者。