day20Java-常用对象Thread-多线程中生产者和消费者
程序员文章站
2022-05-09 13:25:18
...
多线程生产者和消费者
前面已经写过电影票程序,不是特别的符合实际情况。
多线程生产者和消费者:
就是不同种类的线程对同一个资源的操作。
自己对Java等待唤醒机制总结:
之前买电影票的线程只有一个卖票线程,创建多个线程对象去执行它。出现了线程安全问题可以使用同步来解决。
但是现在是不同种类的线程对同一个资源的操作。也可以使用同步来解决线程安全问题,线程安全问题解决了,但是打印的结果不是很符合实际情况,如果让不同种类的线程更符合实际情况,可以使用Java的等待唤醒机制。
线程通信问题:
不同种类的线程间针对同一个资源的操作。
多线程生产者消费者代码版本1
代码演示
生产者消费者测试
public class StudnetThreadDemo {
public static void main(String[] args) {
//创建一个生产者消费者共享的对象
Student s = new Student();
//创建生产者线程对象
SetThread st = new SetThread(s);
//创建消费者线程对象
GetThread gt = new GetThread(s);
//启动线程
st.start();
gt.start();
}
}
生产者
public class SetThread extends Thread {
private Student s;
public SetThread(Student s) {
this.s = s;
}
@Override
public void run() {
int count = 0;
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count % 2 == 0) {
//Student s = new Student();
s.name = "格雷福斯";
s.age = 60;
} else {
s.name = "至高之拳";
s.age = 30;
}
count++;
}
}
}
消费者
public class GetThread extends Thread {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
//这里不能这样创建对象,那样就是各使用各的。
//Student s = new Student();
while (true) {
/*try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println(s.name + "-" + s.age);
}
}
}
实体类
public class Student {
public String name;
public int age;
}
结果:我只列出来了有问题的
斯-60
格雷福斯-3
至高之拳-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
多线程生产者消费者代码版本2-同步解决问题
分析:
资源类:Student
设置学生数据:SetThread(生产者)
获取学生数据:GetThread(消费者)
测试类:StudnetThreadDemo
问题1:按照思路写代码,发现数据每次都是:null---0
原因:我们在每个线程中都创建了新的资源,而我们要求的时候设置和获取线程的资源应该是同一个
如何实现呢?
在外界把这个数据创建出来,通过构造方法传递给其他的类。
问题2:为了效果更好一些,我加入的循环和判断,给出不同的值,但是出现了新的问题。
A:相同的数据出现了多次
原因:CPU一点时间片执行权,足够线程执行很多次了。
B:姓名和年龄不匹配
原因:因为多线程运行的随机性导致的
线程安全问题?
A:是否是多线程环境? 是
B:是否有共享数据? 是 Student
C:是否有多条语句操作共享数据? 是
解决方案:
加锁。
注意:
A:不同种类的线程都要加锁。
如果只有生产者一种类型线程加锁,还是会出现上面一样的问题。
B:不同种类的线程加的锁必须是同一把。
如果不同类型线程锁不同,各自锁各自的,也会出现上面一样的问题,那毫无意义。
加锁后:
实现生产者在生产的时候,消费者不能消费不能消费(生产者那边已经把锁锁住了),消费者在消费的时候,生产者那边不能生产(消费者那边已经把锁锁住了)
加锁后也就是把年龄和姓名不匹配的问题解决了,但是没有解决同一个数据出现多次。
数据安全了。
代码演示
生产者消费者测试
public class StudnetThreadDemo {
public static void main(String[] args) {
//创建一个生产者消费者共享的对象
Student s = new Student();
//创建生产者线程对象
SetThread st = new SetThread(s);
//创建消费者线程对象
GetThread gt = new GetThread(s);
//启动线程
st.start();
gt.start();
}
}
生产者
public class SetThread extends Thread {
private Student s;
public SetThread(Student s) {
this.s = s;
}
int count = 0;
@Override
public void run() {
while (true) {
synchronized (s) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count % 2 == 0) {
//Student s = new Student();
s.name = "格雷福斯"; //不加同步刚执行到这里,就被其他线程抢到执行权了,上面一次内存中的值可能是name=至高之
//拳,age=30,如果这时候输出值也就name="格雷福斯",age=30。
s.age = 60;
} else {
s.name = "至高之拳";//不加同步刚执行到这里,就被其他线程抢到执行权了,上一次内存中的值可能是name=格雷福斯,age=60
//如果这时候输出值也就是name=至高之拳,age=60;
s.age = 30;
}
count++;
}
}
}
}
消费者
public class GetThread extends Thread {
private Student s;
public GetThread(Student s) {
this.s = s;
}
@Override
public void run() {
//这里不能这样创建对象,那样就是各使用各的。
//Student s = new Student();
while (true) {
synchronized (s) {
/*try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}*/
System.out.println(s.name + "-" + s.age);
}
}
}
}
实体类
public class Student {
public String name;
public int age;
}
结果:
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
格雷福斯-60
至高之拳-30
至高之拳-30
至高之拳-30
至高之拳-30
至高之拳-30
至高之拳-30
多线程生产者消费者代码版本3-等待唤醒机制解决问题
上面其实已经解决数据安全问题了,但是呢?数据输出相同的数据都是打印一大片。觉得不是很好看,想依次打印出来。
同步能解决线程安全问题。但是解决不了那种很理性的输出数据。
如何实现呢?
为了满足这样需求Java就提供了等待唤醒机制解决。
等待和唤醒:
Object中提供了3个方法:
wait():等待。
notify:唤醒单个线程。
notify:唤醒所有线程。
为什么这些方法不定义在Thread类中呢?
这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象是任意锁对象。
所以,这些方法必须定义在Object类中。
等待唤醒机制思路图解
代码演示
生产者消费者测试
public class StudnetThreadDemo {
public static void main(String[] args) {
//创建一个生产者消费者共享的对象
Student s = new Student();
//创建生产者线程对象
SetThread st = new SetThread(s);
//创建消费者线程对象
GetThread gt = new GetThread(s);
//启动线程
st.start();
gt.start();
}
}
生产者
public class SetThread extends Thread {
private Student s;
public SetThread(Student s) {
this.s = s;
}
int count = 0;
@Override
@SuppressWarnings("all")
//生产者
public void run() {
while (true) {
synchronized (s) {
//如果有数据就等待,没有数据就创建
if(s.flag){
try {
s.wait(); //满足判断条件st等待,立即释放锁。st等待的情况下,只能gt抢到执行权。
//注意:醒来是从这里醒来的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (count % 2 == 0) {
//Student s = new Student();
s.name = "格雷福斯";
s.age = 60;
} else {
s.name = "至高之拳";
s.age = 30;
}
count++;
//到这里说明数据已经生产好了,修改标记
s.flag =true;
//唤醒线程
s.notify(); //唤醒线程,唤醒线程后不会立马执行。在抢CPU执行权。
//st抢到或者gt抢到
//情况1:如果是st抢到cpu执行权,刚才已经生产好数据了,那就要等待了。
//情况2:gt抢到cpu执行权那就从消费者的wait()中醒来,继续向下执行。
}
}
}
}
消费者
public class GetThread extends Thread {
private Student s;
public GetThread(Student s) {
this.s = s;
}
//消费者
@Override
public void run() {
//这里不能这样创建对象,那样就是各使用各的。
//Student s = new Student();
while (true) {
synchronized (s) {
//如果有数据,就消费,没有就等待。
if(!s.flag){
try {
s.wait();//满足判断条件gt等待,并立即释放锁。gt等待的情况下,只能st抢到执行权。
//注意:醒来是从这里醒来的。
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(s.name + "-" + s.age);
//到这里说明已经消费完,修改标记
s.flag = false;
//唤醒线程
s.notify();//唤醒线程,唤醒线程后不会立马执行。在抢CPU执行权。
//st抢到或者gt抢到
//情况1:如果gt抢到了cpu的执行权,数据已经消费完了,那就要等待了。
//情况2:st抢到了cpu执行权那就从生产者wait()中醒来,继续向下执行。
}
}
}
}
实体类
public class Student {
public String name;
public int age;
public boolean flag; //定义一个标记,是否有学生,默认是false.没有数据
}
结果:
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
线程状态转换图
多线程生产者消费者代码版本4-代码优化
代码演示
生产者消费者测试
public class StudentDemo {
public static void main(String[] args) {
//创建学生对象
Student s = new Student();
//创建线程对象
SetThread st = new SetThread(s);
GetThread gt= new GetThread(s);
//启动线程
st.start();
gt.start();
}
}
生产者
public class SetThread extends Thread {
private Student s;
public SetThread(Student s) {
this.s = s;
}
int count = 0;
@Override
public void run() {
while (true) {
if (count % 2 == 0) {
s.setNameAge("格雷福斯", 60);
} else {
s.setNameAge("至高之拳", 30);
}
count++;
}
}
}
消费者
public class GetThread extends Thread {
private Student s;
public GetThread(Student s){
this.s =s;
}
@Override
public void run() {
while (true) {
s.getNameAge();
}
}
}
实体类
public class Student {
//实际开发中成员变量肯定是不能被外界访问的
private String name;
private int age;
private boolean flag;
public synchronized void setNameAge(String name, int age) {
//如果数据存在就等待
if (this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.name = name;
this.age = age;
//修改标记
this.flag = true;
//唤醒线程
this.notify();
}
public synchronized void getNameAge() {
//如果没有数据就等待
if (!this.flag) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(this.name + "-" + this.age);
//修改标记
this.flag = false;
//唤醒线程
this.notify();
}
}
结果:
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
至高之拳-30
格雷福斯-60
上一篇: Django框架路由系统