-
线程间通信
线程间通信其实就是多个线程在操作同一个资源,但是操作的动作不同,下面需要实现一个需求:有一个输入操作和一个输出操作,一边向缓冲区中存数据,一遍从缓冲区中取数据:
class Res{
String name;
String sex;
}
class Input implements Runnable{
private Res r;
Input(Res r){
this.r = r;
}
public void run() {
int x = 0;
while(true) {
if(x==0) {
r.name = "mike";
r.sex = "man";
}else {
r.name = "丽萨";
r.sex = "女";
}
x = (x+1)%2;
}
}
}
class Output implements Runnable{
private Res r;
Output(Res r){
this.r = r;
}
public void run() {
while(true) {
System.out.println(r.name+"..."+r.sex);
}
}
}
public class Main{
public static void main(String[] args) {
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
在上面的代码中出现了取出来的数据有“mike...女”这样的错误数据,这是因为存数据的同时也能取数据,造成对同一共享数据的操作不同步导致的,下面需要解决该安全问题,解决线程同步安全问题,可以用锁:
class Res{
String name;
String sex;
}
class Input implements Runnable{
private Res r;
Input(Res r){
this.r = r;
}
public void run() {
int x = 0;
while(true) {
synchronized(r) { //要和Output中的同步使用同一个锁,要不然还是没有实现同步,还可以使用Input.class等作为锁
if(x==0) {
r.name = "mike";
r.sex = "man";
}else {
r.name = "丽萨";
r.sex = "女";
}
x = (x+1)%2;
}
}
}
}
class Output implements Runnable{
private Res r;
Output(Res r){
this.r = r;
}
public void run() {
while(true) {
synchronized(r) {
System.out.println(r.name+"..."+r.sex);
}
}
}
}
public class Main{
public static void main(String[] args) {
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
上面的代码解决了线程通信的安全问题,但是它每次会打印很多个“mike...男”或“丽萨...女”,没有实现存一个取一个的效果,下面实现这一个效果,并介绍等待唤醒机制:
class Res{
String name;
String sex;
boolean flag = false;
}
class Input implements Runnable{
private Res r;
Input(Res r){
this.r = r;
}
public void run() {
int x = 0;
while(true) {
synchronized(r) {
if(r.flag)
try{r.wait();}catch(Exception e) {}
if(x==0) {
r.name = "mike";
r.sex = "man";
}else {
r.name = "丽萨";
r.sex = "女";
}
x = (x+1)%2;
r.flag = true;
r.notify();
}
}
}
}
class Output implements Runnable{
private Res r;
Output(Res r){
this.r = r;
}
public void run() {
while(true) {
synchronized(r) {
if(!r.flag)
try{r.wait();}catch(Exception e) {} //调用锁r的wait方法来等待
System.out.println(r.name+"..."+r.sex);
r.flag = false;
r.notify(); //调用锁r的notify方法来唤醒其他线程
}
}
}
}
public class Main{
public static void main(String[] args) {
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
wait()、notify()、notifyAll()都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定义在Object类中?
因为这些方法在操作线程同步时,都必须要标识它们所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁上的notify唤醒,不可以对不同锁中的线程进行唤醒。也就是说等待和唤醒必须是同一个锁,而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。下面对代码进行一下优化:
class Res{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(String name ,String sex) {
if(flag)
try{this.wait();}catch(Exception e) {}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag)
try{this.wait();}catch(Exception e) {}
System.out.println(name+"..."+sex);
flag = false;
this.notify();
}
}
class Input implements Runnable{
private Res r;
Input(Res r){
this.r = r;
}
public void run() {
int x = 0;
while(true) {
if(x==0) {
r.set("mike","man");
}else {
r.set("丽萨","女");
}
x = (x+1)%2;
}
}
}
class Output implements Runnable{
private Res r;
Output(Res r){
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
public class Main{
public static void main(String[] args) {
Res r = new Res();
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
-
生产者消费者问题
下面展示一个生产者和一个消费者的案例代码,原理和上面的案例一致:
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
if(flag)
try {wait();}catch(Exception e) {}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag)
try {wait();}catch(Exception e) {}
System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
flag = false;
this.notify();
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run() {
while(true) {
res.set("+商品+");
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run() {
while(true) {
res.out();
}
}
}
public class Main{
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
但是现实生活中都是多生产者多消费者问题,将上述代码进行修改,变成两个生产者和两个消费者,如下面的代码,则会出现有一个生产者生产的商品,被两个消费者取走的情况,以及其他的错误情况,先进行分析一下,假设t1开始运行获得了CPU执行权,开始生产第一个商品,生产完了将flag设为true,并唤醒等待线程,当前无等待线程,在进入判断flag为true,进入等待状态,此时t3进入out方法准备取走商品,将flag设为false,唤醒t1并进入等待状态,此时t4进入out判断!flag为true,进入等待状态,等于此时有生产者进程t1处于唤醒状态,消费者进程t3和t4处于等待状态,如果现在t1生产了一个商品,并将flag设为true,唤醒t3并进入等待状态,t3取走商品后,将线程池中等待的第一个进程t4唤醒,此时虽然flag为false,但是t4无需判断又可以取走和t3同样的商品,这样就造成错误了。
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void set(String name) {
if(flag)
try {wait();}catch(Exception e) {}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag)
try {wait();}catch(Exception e) {}
System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
flag = false;
this.notify();
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run() {
while(true) {
res.set("+商品+");
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run() {
while(true) {
res.out();
}
}
}
public class Main{
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
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();
}
}
出现了上面的错误,可以通过在wait方法结束继续判断,即改为while判断:
public synchronized void set(String name) {
while(flag)
try {wait();}catch(Exception e) {}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notify();
}
public synchronized void out() {
while(!flag)
try {wait();}catch(Exception e) {}
System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
flag = false;
this.notify();
}
但是上面又会出现死锁的情况,这是因为唤醒往往是唤醒线程池的第一个等待线程,而有可能唤醒的是己方线程,从而造成死锁,所以使用notifyAll的方法,唤醒所有线程。
public synchronized void set(String name) {
while(flag)
try {wait();}catch(Exception e) {}
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
this.notifyAll();
}
public synchronized void out() {
while(!flag)
try {wait();}catch(Exception e) {}
System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
flag = false;
this.notifyAll();
}
-
生产者消费者问题使用JDK1.5新版本
JDK1.5新版本中提供了 Lock接口来代替synchronized来进行上锁操作,改变后的版本如下:
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void set(String name)throws InterruptedException {
lock.lock();
try {
while(flag)
condition.await();
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
condition.signalAll();
}
finally {
lock.unlock();
}
}
public synchronized void out()throws InterruptedException {
lock.lock();
try {
while(!flag)
condition.await();
System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
flag = false;
condition.signalAll();
}
finally {
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run() {
while(true) {
try {
res.set("+商品+");
}catch(InterruptedException e) {
}
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run() {
while(true) {
try {
res.out();
}catch(InterruptedException e) {
}
}
}
}
public class Main{
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
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();
}
}
其中最重要的是接下来的部分,因为上面只是把一些格式给换了,还是唤醒线程池所有等待的线程,但是该Lock可以让一个锁上支持多个相关的Condition对象,所以可以设置两个对象分别为生产者对象和消费者对象,两个对象持有的锁是同一个锁,但是唤醒的时候,是只能唤醒持有对应对象锁的线程:
import java.util.concurrent.locks.*;
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(String name)throws InterruptedException {
lock.lock();
try {
while(flag)
condition_pro.await();
this.name = name+"--"+count++;
System.out.println(Thread.currentThread().getName()+"...生产者..."+this.name);
flag = true;
condition_con.signal();
}
finally {
lock.unlock(); //释放锁的动作一定要执行
}
}
public synchronized void out()throws InterruptedException {
lock.lock();
try {
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者......."+this.name);
flag = false;
condition_pro.signal();
}
finally {
lock.unlock();
}
}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run() {
while(true) {
try {
res.set("+商品+");
}catch(InterruptedException e) {
}
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run() {
while(true) {
try {
res.out();
}catch(InterruptedException e) {
}
}
}
}
public class Main{
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
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();
}
}
JDK1.5中提供了多线程升级解决方案,将同步synchronized替换成显式的Lock操作,将Object中的wait、notify、notifyAll替换成了Condition对象,该对象可以获取Lock锁,在上面的示例中实现了本方只唤醒对方的操作。
本文详细解析了线程间通信的基本概念及其实现方式,通过具体示例展示了如何利用同步机制解决线程安全问题。进一步讨论了生产者消费者问题,包括其在多线程环境下的挑战和解决方案,以及JDK1.5中提供的新工具和技术,如Lock接口和Condition对象,用于更高效地管理和协调多线程任务。
1442

被折叠的 条评论
为什么被折叠?



