用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();
}
}
}
总结:分别为生产者和消费者创建两个监视器,在唤醒时只唤醒对方一个线程,极大提高了效率,合理利用了资源。