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

  • 相关阅读:
    fullCalendar改造计划之带农历节气节假日的万年历(转)
    Linked List Cycle
    Remove Nth Node From End of List
    Binary Tree Inorder Traversal
    Unique Binary Search Trees
    Binary Tree Level Order Traversal
    Binary Tree Level Order Traversal II
    Plus One
    Remove Duplicates from Sorted List
    Merge Two Sorted Lists
  • 原文地址:https://www.cnblogs.com/yefengyu/p/4857319.html
Copyright © 2011-2022 走看看