zoukankan      html  css  js  c++  java
  • java线程详解(三)

    java线程间通信

    首先看一段代码

    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 = "male";
                }
                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);
            }
        }
    }
    
    class Test
    {
        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();
        }
    
    }
    image

    上面的代码主要是想实现一个人名和姓别的同时输入和输出,但是看结果却是错乱不堪的。这个原因是由于是线程安全问题。

    image

    下面就代码中加入同步代码块。关键要注意到Input和Output都要加入,并且synchronized的对象必须是同一个,这个选取Res r = new Res(); 由这条语句创建的对象r比较好。下面是修改后的代码及结果。

    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){
                    if(x==0){
                        r.name = "mike";
                        r.sex = "male";
                    }
                    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);
                }
            }
        }
    }
    
    class Test
    {
        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();
        }
    
    }

    image

    同步代码块保证线程安全运行,但是为什么会出现一直显示丽丽或者是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 = "male";
                    }
                    else{
                        r.name = "丽丽";
                        r.sex = "女";
                    }
                    x = (x+1) % 2;
                    //到这里说明该线程阻塞后其他线程已经取走数据,并且告知该线程,该线程又可存数据
                    r.flag = true; //存数据之后标记为改变
                        try{r.notify();}catch(Exception e){}
                }
            }
        }
    }
    
    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){}
                    System.out.println(r.name + "---" + r.sex);
                    r.flag = false;//取了数据就换标记位
                        try{r.notify();}catch(Exception e){}//然后通知其他线程
                }
            }
        }
    }
    
    class Test
    {
        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();
        }
    
    }

    image

    此时就会出现我们想要的结果,看看java api

    image

    可以看出wait(),notify(),等方法都用在同步中,因为要对持有锁的操作。所以这些方法要在同步中,只有同步才有锁!

    为什么这些方法还会定义在Object中呢?

         因为这些线程在操作同步线程时,都必须标识他们所操作线程的锁。只有同一个锁上被wait的线程才可以被同一个锁上的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; //存数据之后标记为改变
            try{this.notify();}catch(Exception e){}
        }
        public synchronized void out(){
            if(!flag)//这里如果没有数据就会等待,有数据就会去取数据
                try{this.wait();}catch(Exception e){}
            System.out.println(name + "---" + sex);
            flag = false;//取了数据就换标记位
             try{this.notify();}catch(Exception e){}//然后通知其他线程
            
        }
    }
    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","male");
                    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();
            }
        }
    }
    class Test
    {
        public static void main(String[] args)
        {
            Res r = new Res();
            new Thread(new Input(r)).start();
            new Thread(new Output(r)).start();
        }
    
    }

    这次再来看一个生产消费者的例子。

    class  ProducerConsumer
    {
        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();
        }
    }
    
    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 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);
            Thread t3 = new Thread(pro);
            Thread t4 = new Thread(con);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }

    这里就是有两个生产线程,有两个消费线程,那么会出现问题

    image

    这就是当出现多个线程的时候的问题,需要使用while()循环不断判断标记,而不能使用if进行单次判断。也就是要把set和out里面的if语句全部变为while。这个时候会发生全部等待的现象,这里需要使用notifyAll进行全部唤醒。上面的程序只需要修改一下的几个地方

    public synchronized void set(String name){ 
            while(flag)//(1)
                try{wait();}catch(Exception e){}
            this.name = name + "----" + count++; 
            System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
            flag = true;
            this.notifyAll();//(2)
        }
        public synchronized void out(){
            while(!flag)//(3)
                try{wait();}catch(Exception e){}
            System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name);
            flag = false;
            this.notifyAll();//(4)
        }
    记住大笑:

    当出现多个生产者线程和消费者线程时,必须使用while和notifyAll。

    image

    其实notifyAll唤醒了所有线程,这也不是我们的目的,我们只想唤醒对方线程,这该怎么做呢?

    其实这需要看jdk1.5的新特性

    image

    仔细看这方面的资料,重新修改代码

    import java.util.concurrent.locks.*;
    class  ProducerConsumer
    {
        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);
            Thread t3 = new Thread(pro);
            Thread t4 = new Thread(con);
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    
    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();// == try{wait();}catch(Exception e){}
                this.name = name + "----" + count++; 
                System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
                flag = true;
                condition.signalAll();// == this.notifyAll();如果使用signal则会出现等待现象
            } finally {
                lock.unlock();//这里显示解锁,必须执行,所以要放在这里
            }
    
        }
        public  void out()throws InterruptedException{
            lock.lock();//这里显示加锁
                try {
                    while(!flag)
                        condition.await();// == try{wait();}catch(Exception e){}
                System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name);
                flag = false;
                condition.signalAll();// == this.notifyAll();如果使用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){}        
            }
        }
    }

         上面程序只是对上上一个程序的替代,只不过是用到了比价现代的做法,但是本质还是没变,没有达到我们的目的,就是唤醒线程只唤醒对方线程,简而言之,生产者线程唤醒消费者线程,消费者线程唤醒生产者线程。虽然上面的程序没有实现这个目标,不过他具有比较多的特性,现在只需要简单修改便可达到目的。

    image

    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();// == try{wait();}catch(Exception e){}
                this.name = name + "----" + count++; 
                System.out.println(Thread.currentThread().getName()+"---生产者---"+this.name);
                flag = true;
                condition_con.signal();// == this.notifyAll();
            } finally {
                lock.unlock();
            }
    
        }
        public  void out()throws InterruptedException{
            lock.lock();
                try {
                    while(!flag)
                        condition_con.await();// == try{wait();}catch(Exception e){}
                System.out.println(Thread.currentThread().getName()+"---消费者--------"+this.name);
                flag = false;
                condition_pro.signal();// == this.notifyAll();
            } finally {
                lock.unlock();
            }
        }
    }

    这里主要看注释的几行代码。他们达到了目的:生产者线程唤醒消费者线程,消费者线程唤醒生产者线程。lock可以支持多个相关的 Condition 对象。

    image

  • 相关阅读:
    大数据方向招人难啊!!
    .netcore 急速接入第三方登录,不看后悔
    T-SQL——函数——时间操作函数
    T-SQL——关于XML类型
    机器学习方法
    可读性友好的JavaScript:两个专家的故事
    快速了解 JavaScript ES2019 的五个新增特性
    了解 Vue 的 Compsition API
    使用 JavaScript 操作浏览器历史记录 API
    JavaScript ES 模块:现代化前端编程必备技能
  • 原文地址:https://www.cnblogs.com/yefengyu/p/4857319.html
Copyright © 2011-2022 走看看