以生产者和消费者为案例:生产者生产一件商品,消费者购买一件商品。
public class Product {
private String brand;
private String name;
// setter,getter,toString
}
public class ProducerThread extends Thread {
// 共享商品
private Product p;
public ProducerThread(Product product) {
this.p = product;
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
p.setBrand("费列罗");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("巧克力");
} else {
p.setBrand("哈尔滨");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("啤酒");
}
System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
}
}
}
public class Cuntomer extends Thread {
private Product p;
public Cuntomer(Product p) {
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <= 10 ; i++) {
System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
}
}
}
public static void main(String[] args) {
Product p = new Product();
ProducerThread pt = new ProducerThread(p);
Cuntomer c = new Cuntomer(p);
pt.start();
c.start();
}
造成原因:当生产者生产“哈尔滨啤酒”的时候,先打印“哈尔滨”,线程sleep(100)期间,消费者把线程资源抢走,并打印出哈尔滨。但因为“啤酒”是要睡完100ms后才能打印出来,所以在产品名称处才会打印出null。当消费者执行完后,线程资源才重新落回生产者这里, 所以才能循环打印出哈尔滨啤酒和费列罗巧克力
改进:仍存在生产者和消费者无法交叉进行交易的问题
同步代码块
public class ProducerThread extends Thread {
// 共享商品
private Product p;
public ProducerThread(Product product) {
this.p = product;
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
synchronized (p){
if (i % 2 == 0) {
p.setBrand("费列罗");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("巧克力");
} else {
p.setBrand("哈尔滨");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
p.setName("啤酒");
}
System.out.println("生产者生产了:" + p.getBrand() + "---" + p.getName());
}
}
}
}
public class Cuntomer extends Thread {
private Product p;
public Cuntomer(Product p) {
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <= 10 ; i++) {
synchronized (p){
System.out.println("消费者消费了:" + p.getBrand() + "---" + p.getName());
}
}
}
}
同步方法:
package com.yzc.threads.thread04;
public class Product {
private String brand;
private String name;
// setter,getter,toString
// 设置生产者生产商品的方法
public synchronized void setProduct(String brand,String name){
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
}
// 设置消费者获取商品的方法
public synchronized void getProduct(){
System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
}
}
public class ProducerThread extends Thread {
// 共享商品
private Product p;
public ProducerThread(Product product) {
this.p = product;
}
@Override
public void run() {
for (int i = 1; i <= 10; i++) {
if (i % 2 == 0) {
p.setProduct("哈尔滨", "啤酒");
} else {
p.setProduct("费列罗", "巧克力");
}
}
}
}
public class Cuntomer extends Thread {
private Product p;
public Cuntomer(Product p) {
this.p = p;
}
@Override
public void run() {
for (int i = 1; i <= 10 ; i++) {
p.getProduct();
}
}
}
再次改进:解决生产者和交易者无法进行交叉交易的问题(使用wait()和notify())
public class Product {
private String brand;
private String name;
boolean isExist = false;// ture:表示有商品;false:表示没有商品
// setter,getter,toString
public synchronized void setProduct(String brand, String name) {
if(isExist == true){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
isExist = true;
notify();
}
public synchronized void getProduct() {
if(isExist == false){
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
isExist = false;
notify();
}
}
ProducerThread和Cuntomer两个类代码不变,解决
其中wait()和notify():
注意:
这两个方法必须放在同步方法或者同步代码块中才生效,因为在同步的基础上进行线程通信才是有效的
wait和sleep的区别,sleep进入阻塞装填后没有释放锁,wait进入阻塞状态的同时会释放锁
更进一步:
如果生产者和消费者是多个的话,那么很有可能当生产者释放了锁后,另一个生产者抢到了资源,所以为了避免这个问题,可以将生产者和消费者放到两个不同的等待队列中
使用Lock和Condition类中的await(),signal(),signalAll
public class Product {
private String brand;
private String name;
boolean isExist = false;// ture:表示有商品;false:表示没有商品
Lock lock = new ReentrantLock();
// 生产者等待队列
Condition productCondition = lock.newCondition();
// 消费者等待队列
Condition constomerCondition = lock.newCondition();
// setter,getter,toString
public void setProduct(String brand, String name) {
lock.lock();
try{
if (isExist == true) {
try {
productCondition.await();// 生产者阻塞,生产者进入等地队列中去
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setBrand(brand);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setName(name);
System.out.println("生产者生产了:" + this.getBrand() + "---" + this.getName());
isExist = true;
// 通知消费者消费
constomerCondition.signal();
}finally {
lock.unlock();
}
}
public void getProduct() {
lock.lock();
try{
if (isExist == false) {
try {
constomerCondition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("消费者消费了:" + this.getBrand() + "---" + this.getName());
isExist = false;
// 通知生产者生产
productCondition.signal();
}finally {
lock.unlock();
}
}
}
其余代码不变