Java-多线程3
线程间通信
- 共分成四块代码 如下:
代码一:
public class Student {
String name;
int age;
}
代码二:
public class SetThread implements Runnable {
private Student student;
private int i = 0;
// private Object obj = new Object();
SetThread(Student student) {
this.student = student;
}
@Override
public void run() {
while (true){
synchronized (student){
if(i%2==0){
student.name = "李毅";
student.age = 18;
}else {
student.name = "小虎";
student.age = 16;
}
i++;
}
}
}
}
代码三:
package com.shujia.wyh.day26;
public class GetThread implements Runnable {
private Student student;
// private Object obj = new Object();
GetThread(Student student) {
this.student = student;
}
@Override
public void run() {
// Student student = new Student();
while (true){
synchronized (student){
System.out.println(student.name + "---" + student.age);
}
}
}
}
代码四:
public class StudentDemo {
public static void main(String[] args) {
//定义一个共享的资源
Student student = new Student();
//创建Runnable对象
SetThread setThread = new SetThread(student);
GetThread getThread = new GetThread(student);
//借助Thread类创建线程对象
Thread t1 = new Thread(setThread, "生产者");
Thread t2 = new Thread(getThread, "消费者");
//启动线程
t1.start();
t2.start();
}
}
分析线程间通信的案例:
- 共有资源类:Student
- 生产者:SetThread 给学生对象进行赋值
- 消费者:GetThread 给学生对象进行取值
- 测试类:StudentDemo
问题一:
- 按照我们分析类思路来写程序,运行后发现每一次运行的结果都是null--0
这是必然,因为我们在每一个线程类中都单独new了一次学生对象
,而我们实际上赋值和取值的对象应该是同一个。
所以不应该在内部定义。
怎么解决呢?
应该在外部把学生对象定义出来,然后使用构造方法的形式进行传参。
问题二:
- 我们为了运行出来的数据好看一点,在程序加入了循环和判断,赋值的时候,赋不同的值。
但是呢,在运行的时候,又出现了新的问题。
1、姓名和年龄不匹配
2、同一个数据出现了多次(消费者消费了多次)
原因:
1)、姓名和年龄不匹配
这是由线程执行具有随机性导致的
2)、同一个数据出现了多次(消费者消费了多次)
由于CPU一个小小的时间片就足矣我们线程执行很多次
线程安全的问题:
1、是否有多线程环境,是 一个生产者,一个消费者
2、是否有共享数据,是 Student
3、是否有多条语句操作着共享数据 是
既然都满足,那么就说明有线程安全问题。
解决方案:
加锁。
注意:
1、不同种类的线程类中都要加锁
2、并且不同种类的线程类中的锁对象必须是同一个(指的是线程对象共同拥有一个锁对象-> (student对象))
问题三;等待唤醒机制
- 我们用有限的技术来解决问题2的时候,只解决了数据不一致的问题,但是没有解决重复出现的问题
经过分析后,发现这是生产者消费者的问题
使用新技术来实现:
- 如何添加等待唤醒机制呢?
Object类中有三个方法来解决生产者消费者问题的:
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法。
void notify() 唤醒正在等待对象监视器的单个线程。
void notifyAll() 唤醒正在等待对象监视器的所有线程。
为什么上面这些方法不定义在Thread类中呢?
这些方法要想被调用,就必须通过锁对象调用。
多个线程对象中调用等待唤醒的对象要一致,并且与锁对象一致。
并且通过测试后发现,锁对象的类型可以是任意类型的
所以这些方法会被定义在Object类中,因为java中所有的类都有一个父类叫做Object,他们创建出来的对象将来也有可能被作为锁对象。
同样是四块代码
代码一:
public class Student2 {
String name;
int age;
boolean flag; //布尔类型的成员变量系统赋予默认值false
}
代码二:
// package com.shujia.wyh.day26;
public class SetThread2 implements Runnable {
private Student2 student;
private int i = 0;
// private Object obj = new Object();
private Demo demo;
SetThread2(Student2 student) {
this.student = student;
}
SetThread2(Student2 student,Demo demo) {
this.student = student;
this.demo = demo;
}
@Override
public void run() {
while (true){
synchronized (demo){
//先看看学生对象有没有值
//如果这里flag值是false,说明学生对象的成员变量是没有值,或者说这里的值还没有被消费,就不等待消费者
//直接生产数据
//如果这里是true,说明学生对象有数据,生产者等待消费者消费数据
if(student.flag){
//调用wait方法等待
//共有的锁对象调用方法
try {
// student.wait();
demo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if(i%2==0){
student.name = "李毅";
student.age = 18;
}else {
student.name = "小虎";
student.age = 16;
}
i++;
//生产完数据后,通知消费者来消费数据
// student.notify();
demo.notify();
student.flag = true;
}
}
}
}
代码三:
package com.shujia.wyh.day26;
public class GetThread2 implements Runnable {
private Student2 student;
// private Object obj = new Object();
private Demo demo;
GetThread2(Student2 student) {
this.student = student;
}
GetThread2(Student2 student,Demo demo) {
this.student = student;
this.demo = demo;
}
@Override
public void run() {
// Student student = new Student();
while (true){
synchronized (demo){
//先看看学生对象有没有值
//当flag为false的时候,说明对象还没有生产数据,这时候消费者等待,等待生产者生产数据
//当flag为true的时候,说明对象数据生产完毕,作为消费者就应该消费数据
if(!student.flag){
try {
// student.wait();
demo.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//这一步相当于消费数据
System.out.println(student.name + "---" + student.age);
//通知生产者生产数据
// student.notify();
demo.notify();
student.flag = false;
}
}
}
}
代码四:
public class StudentDemo2 {
public static void main(String[] args) {
//定义一个共享的资源
Student2 student = new Student2();
//随便定义一个对象来进行唤醒和等待
Demo demo = new Demo();
//创建Runnable对象
SetThread2 setThread = new SetThread2(student,demo);
GetThread2 getThread = new GetThread2(student,demo);
//借助Thread类创建线程对象
Thread t1 = new Thread(setThread, "生产者");
Thread t2 = new Thread(getThread, "消费者");
//启动线程
t1.start();
t2.start();
}
}
线程组的概念:
- ThreadGroup:
线程组代表一组线程。
此外,线程组还可以包括其他线程组。
理解:把多个线程组合到一起,可以对这一组线程进行管理
public class ThreadGroupDemo {
public static void main(String[] args) {
//创建Runnable对象
MyRunnable myRunnable = new MyRunnable();
//借助Thread类创建线程对象
// Thread t1 = new Thread(myRunnable, "李毅");
// Thread t2 = new Thread(myRunnable, "许文客");
//
// //想办法获取线程所在线程组
// //ThreadGroup getThreadGroup()
// //返回此线程所属的线程组。
// ThreadGroup group1 = t1.getThreadGroup();
// ThreadGroup group2 = t2.getThreadGroup();
//
// //默认情况下,如果不对线程进行分组,会有一个默认的组
// //String getName() 返回此线程组的名称。
// //线程默认所属线程组叫做main,也就是都属于主线程组中的
// String g1 = group1.getName();
// String g2 = group2.getName();
// System.out.println(g1);
// System.out.println(g2);
//创建一个组
//ThreadGroup(String name)
//构造一个新的线程组。 创建线程组的同时给线程组起名字
ThreadGroup group = new ThreadGroup("帅哥组");
//没有办法做到分完组后,再进行修改组
//Thread(ThreadGroup group, Runnable target, String name)
//创建线程对象,并分配组
Thread t1 = new Thread(group, myRunnable, "李毅");
Thread t2 = new Thread(group, myRunnable, "许文客");
//打印线程所属组的名字
String s1 = t1.getThreadGroup().getName();
String s2 = t2.getThreadGroup().getName();
System.out.println(s1);
System.out.println(s2);
ThreadGroup group2 = new ThreadGroup("美女组");
//创建线程并且进行分组
Thread t3 = new Thread(group2, myRunnable, "姚欣");
Thread t4 = new Thread(group2, myRunnable, "张傲");
//打印线程所属组的名字
String s3 = t3.getThreadGroup().getName();
String s4 = t4.getThreadGroup().getName();
System.out.println(s3);
System.out.println(s4);
//需求:将帅哥组设置成守护线程
//直接通过线程组名来进行设置
group.setDaemon(true);
// t1.setDaemon(true);
// t2.setDaemon(true);
//启动线程
t3.start();
t4.start();
t1.start();
t2.start();
}
}