Java多线程安全问题(举例:生产者和消费者)

       用Java也有很长一段时间了,但是在工作中一直没机会用到线程,由于最近换了一家公司,面试了一些线程问题,虽然勉强靠着模糊的记忆回答上来了,但是始终觉着理解的并不深刻,这两天整理了一下这方面的知识,写一遍关于Java的多线程安全问题。一方面增强自己的理解和记忆,另一方面也帮助一下新手朋友更好的理解多线程安全问题。使用多线程代码模拟生产者和消费者,如有不正之处,欢迎指出。

 一、   单个生产者和单个消费者(应用场景比较少,不着重介绍)

public class TestThread1 {

	public static void main(String[] args) {
		Res res = new Res();
		Producer pro = new Producer(res);
		Consumer con = new Consumer(res);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(con);
		t1.start();
		t2.start();
	}
}

class Res {
	private String name;
	private int count = 0;
	private boolean flag;

	public synchronized void set(String name) {
		if (flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name = name + "---" + count;
		count++;
		System.out.println(Thread.currentThread().getName() + "...生产者..."
				+ this.name);
		flag = true;
		notify();
	}

	public synchronized void get() {
		if (!flag) {
			try {
				wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName() + "______消费者______"
				+ this.name);
		flag = false;
		notify();
	}

}

// 生产者
class Producer implements Runnable {
	Res r;

	public Producer(Res r) {
		this.r = r;
	}

	@Override
	public void run() {
		while(true){
			r.set("面包");
		}
	}

}

// 消费者
class Consumer implements Runnable {
	Res r;

	public Consumer(Res r) {
		this.r = r;
	}

	@Override
	public void run() {
		while(true){
			r.get();
		}
	}
}


二、多个生产者和多个消费者(实际开发经常遇到,着重介绍)

1、第一步:

public class TestThread2 {
	public static void main(String[] args) {
		Res res = new Res();
		Producer pro = new Producer(res);
		Consumer con = new Consumer(res);
		Thread t1 = new Thread(pro);
		Thread t2 = new Thread(pro);
		Thread t3 = new Thread(con);
		Thread t4 = new Thread(con);
		t1.start();
		t2.start();
		t3.start();
		t4.start();
	}
}

class Res {
	private String name;
	private int count = 0;
	private boolean flag;

	public synchronized void set(String name) {
		if (flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name = name + "---" + count;
		count++;
		System.out.println(Thread.currentThread().getName() + "...生产者..."
				+ this.name);
		flag = true;
		notify();
	}

	public synchronized void get() {
		if (!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName() + "______消费者______"
				+ this.name);
		flag = false;
		notify();
	}

}

// 生产者
class Producer implements Runnable {
	Res r;

	public Producer(Res r) {
		this.r = r;
	}

	@Override
	public void run() {
		while(true){
			r.set("面包");
		}
	}

}

// 消费者
class Consumer implements Runnable {
	Res r;

	public Consumer(Res r) {
		this.r = r;
	}

	@Override
	public void run() {
		while(true){
			r.get();
		}
	}
}

总结:这种写法不是线程安全的,会出现以下这种情况:


分析原因:在同步方法中,用if判断后,由于notify()的随机唤醒特性,有可能唤醒本方的线程,造成重复生产或重复消费的情况。

2、第二步(以下忽略例子中重复的代码):

class Res {
	private String name;
	private int count = 0;
	private boolean flag;

	public synchronized void set(String name) {
		while (flag) {//此处换成while循环判断,每次被唤醒需要重新判断标识符
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		this.name = name + "---" + count;
		count++;
		System.out.println(Thread.currentThread().getName() + "...生产者..."
				+ this.name);
		flag = true;
		notifyAll();//此处替换成notifyAll,否则将会造成死锁
	}

	public synchronized void get() {
		while (!flag) {
			try {
				this.wait();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(Thread.currentThread().getName() + "______消费者______"
				+ this.name);
		flag = false;
		notifyAll();
	}
}

总结:这种方法可以解决多生产者和多消费者的线程安全问题,但是效率比较低,因为每次都唤醒所有线程,需要对每个线程进行判断。

3、第三步:

class Res {
	private String name;
	private int count = 0;
	private boolean flag;
	private final ReentrantLock lock = new ReentrantLock();
	private Condition con = lock.newCondition();

	public void set(String name) {
		try{
			lock.lock();
			while (flag) {
				try {
					con.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			this.name = name + "---" + count;
			count++;
			System.out.println(Thread.currentThread().getName() + "...生产者..."
					+ this.name);
			flag = true;
			con.signalAll();
		}finally{
			lock.unlock();
		}
	}

	public void get() {
		try{
			lock.lock();
			while (!flag) {
				try {
					con.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + "______消费者______"
					+ this.name);
			flag = false;
			con.signalAll();
		}finally{
			lock.unlock();
		}
	}
}

总结:这种写法虽然可读性变强了点,效率或许也有所提高,但仍然解决不了性能低的问题,因为唤醒的是所有线程。

4、第四步:

class Res {
	private String name;
	private int count = 0;
	private boolean flag;
	private final ReentrantLock lock = new ReentrantLock();
	private Condition producer_con = lock.newCondition();
	private Condition consumer_con = lock.newCondition();

	public void set(String name) {
		try{
			lock.lock();
			while (flag) {
				try {
					producer_con.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			this.name = name + "---" + count;
			count++;
			System.out.println(Thread.currentThread().getName() + "...生产者..."
					+ this.name);
			flag = true;
			consumer_con.signal();
		}finally{
			lock.unlock();
		}
	}

	public void get() {
		try{
			lock.lock();
			while (!flag) {
				try {
					consumer_con.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			System.out.println(Thread.currentThread().getName() + "______消费者______"
					+ this.name);
			flag = false;
			producer_con.signal();
		}finally{
			lock.unlock();
		}
	}
}
总结:分别为生产者和消费者创建两个监视器,在唤醒时只唤醒对方一个线程,极大提高了效率,合理利用了资源。
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值