多线程学习笔记四 - 线程间通信问题
线程间通信问题:
多个线程在处理同一资源,但是任务却不同;
java中将资源共享的方法(思路):
1、方法或者变量静态化---->静态化后,在类加载的时候,会将其加载到内存的方法区进行共享
2、单例设计模式---->保证只对一个实例进行操作。
3、将资源作为操作该资源的类的构造函数的参数,这样可以保证此类的多个对象在使用该资源的时候使用该资源的同一个实例。
现在我们要用第三种方法来进行线程间的通信。
情景:两个线程 ,一个负责输入,一个负责输出;共同处理一个资源。
public class ThreadCommunication {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
if(x == 0){
r.setName("小明");
r.setSex("女");
}else{
r.setName("snoopy");
r.setSex("man");
}
x = (x+1)%2;
}
}
}
class Output implements Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
System.out.println(r.getName()+": "+r.getSex());
}
}
}
class Resource{
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
结果出现了下列情况:
snoopy: man
小明: man
小明: man
snoopy: 女
小明: man
小明: man
snoopy: 女
小明: man
这说明了线程发生了安全问题。
将操作共享资源的代码加上锁:
public class ThreadCommunication {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized(r){
if(x == 0){
r.setName("小明");
r.setSex("女");
}else{
r.setName("snoopy");
r.setSex("man");
}
x = (x+1)%2;
}
}
}
}
class Output implements Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized(r){
System.out.println(r.getName()+": "+r.getSex());
}
}
}
}
class Resource{
private String name;
private String sex;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
解决了线程安全问题。
线程间通信----等待唤醒机制
上述情况是没有顺序的,我们的目的是输入一个,输出一个,那么我们在进行输入的时候就要进行处理:
1、当资源有值但是未输出的情况下就不能再输入。 ----等待
2、当资源已经输出的时候就要继续输入。 ----唤醒
等待唤醒涉及方法:
wait():让线程处于冻结状态,被wait的线程都会被存储到线程池内。
在其他线程调用此对象的 notify() 方法或 notifyAll() 方法前,导致当前线程等待。换句话说,此方法的行为就好像它仅执行 wait(0) 调用一样。
当前线程必须拥有此对象监视器。
该线程发布对此监视器的所有权并等待,直到其他线程通过调用 notify 方法,或 notifyAll 方法通知在此对象的监视器上等待的线程醒来。
然后该线程将等到重新获得对监视器的所有权后才能继续执行。
notify():唤醒线程池中的任意一个线程。
唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。
选择是任意性的,并在对实现做出决定时发生。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
notifyAll():唤醒线程池中所有线程。
唤醒在此对象监视器上等待的所有线程。线程通过调用其中一个 wait 方法,在对象的监视器上等待。
这些方法都必须定义在同步中,因为这些方法都是用于操作线程状态的方法。必须要明确到底操作的是哪个锁上面的线程。
重点:
在使用上述方法的时候,一定要明确该方法所属的锁。
换句话说:到底wait的是哪个锁上的线程,如果是wait的A锁上面线程,那么就必须用A锁上的notify唤醒A锁上门的线程。
为什么操作线程的方法定义在Object类中??
因为这些方法是监视器的方法,而监视器其实就是锁。而锁可以是任意对象。任意对象都可以调用的方法一定在Object中。
理解同步块和对象锁:
1、我们可以把共享资源想象成为一个房间,synchronized是房间门上面的锁,而Object作为监视器(内含有监视器方法可理解为钥匙)。
2、线程可以看作要进房间的人,不同的线程可以看作不同的人,如果一个人(线程)拿着钥匙(对象)要进房间访问共享资源,首先要判断里面是否有人。
3、如果有人则进不去,这个人出于阻塞状态,如果没人则进入并独占该共享资源。
4、当发生了线程等待的时候,针对于当前钥匙的主人进入线程池等待。直到也有该类钥匙的另外的人唤醒线程池池排队等候的有同类钥匙的人的时候,线程池会随机弄醒一位。
5、如果是不同锁对象(监视器)进行的操作,是没有同步作用的。
实例:
public class ThreadCommunication {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
@Override
public void run() {
int x = 0;
while(true){
synchronized(r){
if(r.isFlag() == false){
if(x == 0){
r.setName("小红");
r.setSex("女");
}else{
r.setName("snoopy");
r.setSex("man");
}
x = (x+1)%2;
r.setFlag(true);
r.notify();
}else{
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Output implements Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized(r){
if(r.isFlag() == true){
System.out.println(r.getName()+": "+r.getSex());
r.setFlag(false);
r.notify();
}else{
try {
r.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Resource{
private String name;
private String sex;
private boolean flag = false;
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getSex() {
return sex;
}
public void setSex(String sex) {
this.sex = sex;
}
}
代码优化:
public class ThreadCommunication {
public static void main(String[] args) {
Resource r = new Resource();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.start();
}
}
class Input implements Runnable{
Resource r;
Input(Resource r){
this.r = r;
}
@Override
public void run() {
int x = 0;
while(true){
synchronized(r){
if(x == 0){
r.in("小红", "女");
}else{
r.in("snoopy","man");
}
x = (x+1)%2;
}
}
}
}
class Output implements Runnable{
Resource r;
Output(Resource r){
this.r = r;
}
@Override
public void run() {
while(true){
synchronized(r){
r.out();
}
}
}
}
class Resource{
private String name;
private String sex;
private boolean flag = false;
public void in(String name,String sex){
if(flag == false){
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}else{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void out(){
if(flag == true){
System.out.println(this.name+": "+this.sex);
flag = false;
this.notify();
}else{
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}