美文网首页
Java多线程 ----(3)线程间通讯

Java多线程 ----(3)线程间通讯

作者: 艾剪疏 | 来源:发表于2018-08-24 11:36 被阅读24次

1 同步生效的条件
2 wait();notify();notifyAll();三个方法
3 多线程之间的交互通信易存在的问题(难点)
4 线程停止
5 守护线程
6 join方法
7 线程优先级

1 同步生效的条件

先看一段铺垫代码,这个代码很简单,但有2个地方需要注意。

  • 多线程意味着输入和输出都需要被同步(存在多个线程);
  • 注意多线程要使用相同的锁,这个锁是输入程序和输出程序共同使用的Resource对象;
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 Resource{
    String name;
    String sex;
    boolean flag = false;
}

public class InputResource implements Runnable {

    private Resource r;

    InputResource(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        int x = 0;
        while (true) {
            synchronized (r) {
                if (x == 0) {
                    r.name = "james";
                    r.sex = "man";
                } else {
                    r.name = "丽丽";
                    r.sex = "女";
                }
                x = (x + 1) % 2;
            }
        }
    }
}

public class OutPutResource implements Runnable{

    private Resource r;
    OutPutResource(Resource r){
        this.r = r;
    }
    public void run() {
        while(true){
            synchronized(r){
                System.out.println(r.name+"::::::::::::::"+r.sex);
            }
        }
    }
}

注意这两点之后,程序正确,输出正常。


image.png

2 wait();notify();notifyAll();三个方法

上面虽然执行正常,但并不是希望的效果,因为应该是一次输入对应一次输出。所以这时就需要使用wait和
notify来对线程进行睡眠和唤醒操作。

代码如下:

public class InputResource_1 implements Runnable {

    private Resource r;

    InputResource_1(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        int x = 0;
        while (true) {
            synchronized (r) {
                if (r.flag) {
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                //首先执行false,赋值操作
                if (x == 0) {
                    r.name = "james";
                    r.sex = "man";
                } else {
                    r.name = "丽丽";
                    r.sex = "女";
                }
                x = (x + 1) % 2;
                r.flag = true;//flag置为true之后,output被notify,input被wait
                r.notify();
            }
        }
    }
}

public class OutPutResource_1 implements Runnable {

    private Resource r;

    OutPutResource_1(Resource r) {
        this.r = r;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (r) {
                if (!r.flag) {
                    try {
                        r.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println(r.name + "::::::::::::::" + r.sex);
                r.flag = false;
                r.notify();
            }
        }
    }
}
image.png

这里的只wait和notify等待和唤醒必须是同一个锁。

3 多线程之间的交互通信易存在的问题(难点)

例子的要求是:生产者生产一个,消费者就消费一个,生产和消费一一对等。
代码如下:

public class ProducerAndConsumer {

    public static void main(String[] args){
        ResourcesGS r = new ResourcesGS();
        Producer p = new Producer(r);
        Consumer c = new Consumer(r);
        Thread t1 = new Thread(c);
        Thread t2 = new Thread(c);
        Thread t3 = new Thread(p);
        Thread t4 = new Thread(p);
        t1.start();
        t2.start();
        t3.start();
        t4.start();
    }
}
public class ResourcesGS {

    private String name;
    private int count = 1;
    private boolean flag = false;

    public synchronized void set(String name){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.name = name +"---"+ count++;
        System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
        flag = true;
        this.notify();
    }

    public synchronized void out(){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
        flag = false;
        this.notify();
    }
}
public class Producer implements Runnable{

    private ResourcesGS r;
    Producer(ResourcesGS r){
        this.r = r;
    }
    @Override
    public void run() {
        while(true){
            r.set("producer");
        }
    }
}
public class Consumer implements Runnable{

    private ResourcesGS r;
    Consumer(ResourcesGS r){
        this.r = r;
    }
    @Override
    public void run() {
        while(true){
            r.out();
        }
    }
}

大部分代码都是和谐状态,但是的确存在错误现象:生产一次,消费多次,生产多次,消费一次。


image.png

首先从代码来分析,主要是ResourcesGS中的代码

public class ResourcesGS {

    private String name;
    private int count = 1;
    private boolean flag = false;

    public synchronized void set(String name){
        if(flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        this.name = name +"---"+ count++;
        System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
        flag = true;
        this.notify();
    }

    public synchronized void out(){
        if(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
        flag = false;
        this.notify();
    }
}

t1,t2是生产者,t3,t4是消费者。
假设:

  • t1抢到执行权,首先“生产1”,然后置标记为false,notify剩下的三个线程t2,t3,t4全部被唤醒,接着wait()等待;
  • 如果这时t2抢到执行权,标记为true,wait()等待。那么就意味这等待队列中现在是t1,t2的排序。
  • t3抢到执行权,执行代码"消费1",然后置标记为true,notify了t1,因为t4本来没睡,所以t4抢到执行权,标记为true,直接wait。第一次的生产消费是和谐状态。
  • 这时t1抢到执行权,“生产2”,然后置标记为false,t1执行notify,这时根据队列中等待线程的排列顺序,t2被唤醒(出错点),这时t2不用判断标记,直接继续向下执行,“生产3”。这里就连续生产了2次。

这时就会导致生产两次,消费一次的现象。

那么怎么解决呢?
问题的核心是:t2没有判断标记,直接继续执行了"生产"的代码。只需要让t2一直判断标记即可。就是将if判断改为while判断。这时,即使t2醒了也需要在重新判断标记,所以可以修复改bug。
但是修改后运行结果如下:


image.png

只走了前三个线程,程序就停止了。

假设t2这时醒了,然后进行while语句的判断,这时标记为true,然后将会被wait(),直接使用object类中的notifyAll()方法唤醒所有的线程即可。这时,即使t2也睡了,但是t3和t4全部被唤醒,可以继续向下执行,程序就会继续执行了。
代码如下:

class ResourcesGS{
    private String name;
    private int count = 1;
    private boolean flag = false;

    public synchronized void set(String name){
        while(flag){
            try {
                this.wait();//t1 t2
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name +"---"+ count++;
        System.out.println(Thread.currentThread().getName()+"..生产者.."+this.name);
        flag = true;
        this.notifyAll();
    }

    public synchronized void out(){
        while(!flag){
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName()+"........消费者......."+this.name);
        flag = false;
        this.notifyAll();
    }
}

最后的结果如下,一切都正常了!


image.png

4 线程停止

stop方法已经过时,现在常见的停止线程的方法如下:

(1)让运行的线程结束

代码如下:通过控制标记,结束run方法。

public class StopThread implements Runnable{

    private boolean flag = true;

    @Override
    public void run() {
        while (flag){
            System.out.println(Thread.currentThread().getName()+"...run");
        }
    }

    public void changeFlag(){
        flag = false;
    }

    public static void main(String[] args) {
        StopThread stopThread = new StopThread();

        Thread t1 = new Thread(stopThread);
        Thread t2 = new Thread(stopThread);

        t1.start();
        t2.start();

        int num = 0;
        while(true){
            if(num++ == 60){
                stopThread.changeFlag();
                break;
            }
            System.out.println(Thread.currentThread().getName()+"..."+num);
        }
    }
}

(2)interrupt方法

该方法不是停止线程,而是中断线程。主要是会将 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int)等方法的状态清除。强制让线程恢复到运行状态中,这样就可以继续操作标记让线程结束。

public class StopThread implements Runnable{

    private boolean flag = true;

    @Override
    public synchronized void run() {
        while (flag){
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
                System.out.println(Thread.currentThread().getName()+"...run");
                flag = false;
            }
        }
    }

    public void changeFlag(){
        flag = false;
    }

    public static void main(String[] args) {
        StopThread stopThread = new StopThread();

        Thread t1 = new Thread(stopThread);
        Thread t2 = new Thread(stopThread);

        t1.start();
        t2.start();

        int num = 0;
        while(true){
            if(num++ == 100){
                t1.interrupt();
                t2.interrupt();
                break;
            }
            System.out.println(Thread.currentThread().getName()+"..."+num);
        }
    }
}

运行结果如下:抛出异常,通过置标记,停止线程。


image.png

5 守护线程

Java中提供守护线程,特点是:守护线程属于后台线程,而主线程是前台线程,守护线程会在主线程运行结束之后自动停止。

将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。 
该方法必须在启动线程前调用。 
public final void setDaemon(boolean on)

刚才的代码,如果将t1和t2都设置为守护线程,那么就不需要人为去设置停止,t1,t2会在主线程运行结束后自动结束。

 t1.setDaemon(true);
 t2.setDaemon(true);

6 join方法

当A线程执行到了B线程的join方法时,A就会等待,等B线程都执行完毕,A才会继续执行。一般join用来临时加入线程执行。

例如,若这样执行,main、Thread0、Thread1会随机无序的交替执行。

public class StopThread implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 280; i++) {
            System.out.println(Thread.currentThread().getName()+"...run");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        StopThread stopThread = new StopThread();

        Thread t1 = new Thread(stopThread);
        Thread t2 = new Thread(stopThread);

        t1.start();
        t2.start();

        for (int i = 0; i < 280; i++) {
            System.out.println(Thread.currentThread().getName()+"...main");
        }
    }
}

image.png

若在 t1.start()后面加上 t1.join()。

t1.start()
t1.join()
t2.start()

执行结果如下:


image.png

如上所述,main、Thread1(t2)会等Thread0(t1)执行完毕后在执行。

7 线程优先级

1 toString()

返回该线程的字符串表示形式,包括线程名称、优先级和线程组。


image.png

2 更改线程优先级

如上图,所有线程的默认优先级是5,但是可以根据需要,设置线程的优先级,优先级高的cpu在执行时就会优先考虑,执行的机会就更大。

提供1,5,10三个档次的优先级。

image.png
getPriority() 
setPriority(int newPriority) 

多线程的基础知识总结完毕。
END

相关文章

网友评论

      本文标题:Java多线程 ----(3)线程间通讯

      本文链接:https://www.haomeiwen.com/subject/vtomiftx.html