精华内容
下载资源
问答
  • Java线程间通信

    千次阅读 2020-05-23 00:18:32
    什么是线程间通信 线程间通信其实就是多个线程在操作同一个资源,但是操作的动作不同 Demo前奏 两个线程操作同一对象,一个线程负责给对象切换赋值一次,另一个线程负责打印一次对象值 package com.star.test; ...

    什么是线程间通信

    线程间通信其实就是多个线程在操作同一个资源,但是操作的动作不同

    Demo前奏

    两个线程操作同一对象,一个线程负责给对象切换赋值一次,另一个线程负责打印一次对象值

    package com.star.test;
    
    class Person{
        //名字
        private String name;
        //性别
        private String sex;
    
        /**
         * 对象赋值
         * @param name
         * @param sex
         */
        public void setValue(String name, String sex) {
            this.name = name;
            this.sex = sex;
        }
    
        /**
         * 打印对象值
         */
        public void printValue() {
            System.out.println(name + "-----------" + sex);
        }
    }
    class InputValue implements Runnable {
        private Person person;
    
        InputValue(Person p) {
            this.person = p;
        }
    
        @Override
        public void run() {
            int i = 0;
            //循环给对象赋值
            while (true) {
                if (i == 0) {
                    person.setValue("喜羊羊","男");
                } else {
                    person.setValue("grey wolf","man");
                }
                i = (i + 1) % 2;
            }
        }
    }
    class OutputValue implements Runnable {
        private Person person;
    
        OutputValue(Person p) {
            this.person = p;
        }
    
        @Override
        public void run() {
            //循环打印对象值
            while (true) {
                person.printValue();
            }
        }
    }
    public class StarTest {
        public static void main(String[] args) {
            Person person = new Person();
            new Thread(new InputValue(person)).start();
            new Thread(new OutputValue(person)).start();
        }
    }
    

    输出结果如下:
    在这里插入图片描述
      可以发现,打印结果出现对不上的情况,也就是英文 name 对应中文 sex、中文 name 对应英文 sex,此时就是线程不安全引发的,我们将代码改造为线程安全的:

    改造代码

    /**
     * 对象赋值
     * @param name
     * @param sex
     */
    public void setValue(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
    
    /**
     * 打印对象值
     */
    public void printValue() {
        System.out.println(name + "-----------" + sex);
    }
    

    改为

    /**
     * 对象赋值
     * @param name
     * @param sex
     */
    public synchronized void setValue(String name, String sex) {
        this.name = name;
        this.sex = sex;
    }
    
    /**
     * 打印对象值
     */
    public synchronized void printValue() {
        System.out.println(name + "-----------" + sex);
    }
    

    运行结果:
    在这里插入图片描述
    此时我们发现:这种结果不是我们想要的,应该是赋值一次、打印一次,此时就用到了线程通信

    线程通信------等待唤醒机制

    package com.star.test;
    
    class Person{
        //名字
        private String name;
        //性别
        private String sex;
        //是否赋值标识
        boolean flag = false;
    
        /**
         * 对象赋值
         * @param name
         * @param sex
         */
        public synchronized void setValue(String name, String sex) {
            if (flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name;
            this.sex = sex;
            //赋值改变标识
            flag = true;
            //唤醒等待线程
            notify();
        }
    
        /**
         * 打印对象值
         */
        public synchronized void printValue() {
            if (!flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(name + "-----------" + sex);
            //改变标识
            flag = false;
            //唤醒等待线程
            notify();
        }
    }
    class InputValue implements Runnable {
        private Person person;
    
        InputValue(Person p) {
            this.person = p;
        }
    
        @Override
        public void run() {
            int i = 0;
            //循环给对象赋值
            while (true) {
                if (i == 0) {
                    person.setValue("喜羊羊","男");
                } else {
                    person.setValue("grey wolf","man");
                }
                i = (i + 1) % 2;
            }
        }
    }
    class OutputValue implements Runnable {
        private Person person;
    
        OutputValue(Person p) {
            this.person = p;
        }
    
        @Override
        public void run() {
            //循环打印对象值
            while (true) {
                person.printValue();
            }
        }
    }
    public class StarTest {
        public static void main(String[] args) {
            Person person = new Person();
            new Thread(new InputValue(person)).start();
            new Thread(new OutputValue(person)).start();
        }
    }
    

    运行结果:
    在这里插入图片描述

    原理图:

    ![在这里插入图片描述](https://img-blog.csdnimg.cn/20200521232231960.png?x-oss-process=image/watermark,type_ZmFuZ3poZW5naGVpdGk,shadow_10,text_aHR0cHM6Ly9ibG9nLmNzZG4ubmV0L3dlaXhpbl80MTY2MTQ2Ng==,size_16,color_FFFFFF,t_70
    如图所示
      赋值线程运行时,判断 flag 为 false 时赋值,赋值后改变 flag 为 true,并且叫醒等待线程,flag 为 true 时线程状态转换为等待;
      打印线程运行时,判断 flag 为 false 时进入等待状态,为 true 时则打印,之后改变 flag 为 false,并叫醒等待线程
    注意: notify() 方法是随机叫醒等待中的一个线程,上面的例子中,等待中的线程只有一个

    生产消费者

    1、单个生产、消费者

    package com.star.test;
    
    class Resource {
        //资源名称
        private String name;
        //数量
        private int count = 0;
        //标识
        private boolean flag = false;
    
        //生产
        public synchronized void set(String name) {
            if (flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.name = name;
            System.out.println(Thread.currentThread().getName() + "---生产者生产---" + this.name + ",剩余数量:" + (++count));
            flag = true;
            notify();
        }
    
        //消费
        public synchronized void get() {
            if (!flag) {
                try {
                    wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "******消费者消费******" + this.name + ",剩余数量:" + (--count));
            flag = false;
            notify();
        }
    }
    
    /**
     * 生产者
     */
    class Producer implements Runnable {
        private Resource resource;
    
        Producer(Resource res) {
            this.resource = res;
        }
    
        @Override
        public void run() {
            while (true) {
                resource.set("衣服");
            }
        }
    }
    
    /**
     * 消费者
     */
    class Consumer implements Runnable {
        private Resource resource;
    
        Consumer(Resource res) {
            this.resource = res;
        }
    
        @Override
        public void run() {
            while (true) {
                resource.get();
            }
        }
    }
    
    public class StarTest {
        public static void main(String[] args) {
            Resource resource = new Resource();
            new Thread(new Producer(resource), "线程1").start();
            new Thread(new Consumer(resource), "线程2").start();
        }
    }
    

    运行结果:
    在这里插入图片描述
    此时运行状态正常

    2、多个生产、消费者

    生产者、消费者各加一个,改动 main() 函数:

    public class StarTest {
        public static void main(String[] args) {
            Resource resource = new Resource();
            new Thread(new Producer(resource), "线程1").start();
            new Thread(new Consumer(resource), "线程2").start();
            new Thread(new Producer(resource), "线程3").start();
            new Thread(new Consumer(resource), "线程4").start();
        }
    }
    

    运行结果:
    在这里插入图片描述在这里插入图片描述
    我们可以看到,此时线程之间通信出现了问题,甚至出现了负数,如图:
    在这里插入图片描述
    要想解决现在的问题,就需要线程每次被唤醒之后都要去判断标识flag,并且避免唤醒己方线程

    改造代码:

    //生产
    public synchronized void set(String name) {
        if (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name;
        System.out.println(Thread.currentThread().getName() + "---生产者生产---" + this.name + ",剩余数量:" + (++count));
        flag = true;
        notify();
    }
    
    //消费
    public synchronized void get() {
        if (!flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "******消费者消费******" + this.name + ",剩余数量:" + (--count));
        flag = false;
        notify();
    }
    

    改为

    //生产
    public synchronized void set(String name) {
        while (flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        this.name = name;
        System.out.println(Thread.currentThread().getName() + "---生产者生产---" + this.name + ",剩余数量:" + (++count));
        flag = true;
        notifyAll();
    }
    
    //消费
    public synchronized void get() {
        while (!flag) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "******消费者消费******" + this.name + ",剩余数量:" + (--count));
        flag = false;
        notifyAll();
    }
    

    运行结果:
    在这里插入图片描述
    此时结果正常

    JDK5实现生产消费者

    JDK5是java的一个里程碑,提供了很多解决方案,比如多线程中加入了Lock锁,我们可以用Lock锁来替换 synchronized ,API中给我们提供了使用方法:
    在这里插入图片描述

    改造代码为lock锁:

    package com.star.test;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    class Resource {
        //资源名称
        private String name;
        //数量
        private int count = 0;
        //标识
        private boolean flag = false;
        //创建lock
        private Lock lock = new ReentrantLock();
        //获取Condition
        private Condition producer_condition = lock.newCondition();
        private Condition consumer_condition = lock.newCondition();
    
        //生产
        public void set(String name) throws InterruptedException {
            //加锁
            lock.lock();
            try {
                while (flag) {
                    //生产者等待
                    producer_condition.await();
                }
                this.name = name;
                System.out.println(Thread.currentThread().getName() + "---生产者生产---" + this.name + ",剩余数量:" + (++count));
                flag = true;
                //唤醒消费者
                consumer_condition.signalAll();
            } finally {
                //释放锁
                lock.unlock();
            }
        }
    
        //消费
        public void get() throws InterruptedException {
            lock.lock();
            try {
                while (!flag) {
                    //消费者等待
                    consumer_condition.await();
                }
                System.out.println(Thread.currentThread().getName() + "******消费者消费******" + this.name + ",剩余数量:" + (--count));
                flag = false;
                //唤醒生产者
                producer_condition.signalAll();
            } finally {
                lock.unlock();
            }
        }
    }
    
    /**
     * 生产者
     */
    class Producer implements Runnable {
        private Resource resource;
    
        Producer(Resource res) {
            this.resource = res;
        }
    
        @Override
        public void run() {
            while (true) {
                try {
                    resource.set("衣服");
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        }
    }
    
    /**
     * 消费者
     */
    class Consumer implements Runnable {
        private Resource resource;
    
        Consumer(Resource res) {
            this.resource = res;
        }
    
        @Override
        public void run() {
            while (true) {
                try {
                    resource.get();
                } catch (InterruptedException ie) {
                    ie.printStackTrace();
                }
            }
        }
    }
    
    public class StarTest {
        public static void main(String[] args) {
            Resource resource = new Resource();
            new Thread(new Producer(resource), "线程1").start();
            new Thread(new Consumer(resource), "线程2").start();
            new Thread(new Producer(resource), "线程3").start();
            new Thread(new Consumer(resource), "线程4").start();
        }
    }
    

    运行结果:
    在这里插入图片描述

    线程通信方法

    1、wait()、notify()、notifyAll()

    wait()、notify()、notifyAll() 这些方法都使用在同步中,因为要对持有监视器(锁)的线程操作,所以要使用在同步中,因为只有同步才具有锁。但是这些方法都定义在Object类中,因为这些方法在操作同步中线程时,都必须要标识所操作线程持有的锁,只有同一个锁上的被等待线程可以被同一个锁的 notify 唤醒,不可以对不同锁中的线程唤醒。也就是说:等待和唤醒必须是同一个锁

    锁可以是任意对象,可以被任意对象调用的方法定义在 Object 类中

    JDK5中引入了Lock锁,使用 Condition 对象中的 await()、signal()、signalAll() 将 Object 中的 wait()、notify()、notifyAll() ,该对象可以使用 lock 锁进行获取

    2、stop()、interrupt()

    在这里插入图片描述
    stop()方法是不安全的,所以已被弃用,那么如何来停止一个线程?其实我们都知道:run()方法结束就代表线程结束,多线程运行代码通常都是循环结构,只要控制住循环,就可以让 run() 结束,也就是线程结束。但是当线程处于冻结状态时,线程不会结束,如果没有指定方式让冻结线程恢复到运行状态,就需要对冻结线程进行清除,强制让线程恢复到运行状态,此时可以操作标识让线程结束,Thread 类中提供了 interrupt():
    在这里插入图片描述

    3、join()

    当A线程执行到了B线程的 join() 方法时,A线程就放弃运行资格,处于冻结等待状态,等B线程执行完,A才恢复运行资格;如果B线程执行过程中挂掉,那么需要用 interrupt() 方法来清理A线程的冻结状态;join()可以用来临时加入线程执行。

    4、yield()

    暂时释放执行资格,稍微减缓线程切换的频率,让多个线程得到运行资格的机会均等一些

    知识点

    1、wait与sleep区别

    1、sleep()方法属于Thread类的,而wait()方法是Object类中的
    2、sleep()方法使程序暂停执行指定的时间,让出cpu给其他线程,但是他的监控状态依然保持着,当指定的时间到了又会自动恢复运行状态,在调用sleep()方法时,线程不会释放对象锁。而当调用wait()方法的时候,线程会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify() 方法后本线程才进入对象锁定池准备获取对象锁进入运行状态

    2、Lock接口与synchronized关键字的区别

    1、Lock 接口可以尝试非阻塞地获取锁 当前线程尝试获取锁。如果这一时刻锁没有被其他线程获取到,则成功获取并持有锁。
    2、Lock 接口能被中断地获取锁, 与 synchronized 不同,获取到锁的线程能够响应中断,当获取到锁的线程被中断时,中断异常将会被抛出,同时锁会被释放。
    3、Lock 接口在指定的截止时间之前获取锁,如果截止时间到了依旧无法获取锁,则返回

    展开全文
  • java线程间通信

    万次阅读 2019-04-10 13:39:44
    在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界...

    在前面我们将了很多关于同步的问题,然而在现实中,需要线程之间的协作。比如说最经典的生产者-消费者模型:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此,一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作

    Java中线程通信协作的最常见的两种方式:

      一.syncrhoized加锁的线程的Object类的wait()/notify()/notifyAll()

      二.ReentrantLock类加锁的线程的Condition类的await()/signal()/signalAll()

    本篇先说第一种方式。

    wait()、notify()和notifyAll()是Object类中的方法:

          1)wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写。

      2)调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁)

      3)调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程

      4)调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程

           wait与notify实现线程间的通信实例:

    面试题:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序:

    package ThreadDemo;
    public class ThreadTest {
    	private static boolean bShouldMain = false;//这里相当于定义了控制该谁执行的一个信号灯
    	public static void main(String[] args) {
    		//因为用到了匿名内部类,内部类访问的局部变量需要用final修饰
    		final Business business = new Business();
    		new Thread(
    				new Runnable(){
    					public void run() {
    						for(int i=1;i<=50;i++){
    							//这里使用类的字节码对象作为锁进行同步,但是当需要对同步进行分组时就不科学了
    							business.sub(i);
    						}
    					}
    				}
    		).start();
     
    		//main方法本身是一个线程,这里是主线程的运行代码
    		for(int i=1;i<=50;i++){
    			business.main(i);
    		}
    	}
    }
     
    class Business {
    	private boolean bShouldSub = true;//最开始该子线程走
    	public synchronized void sub(int i){
    		while(!bShouldSub){//用while而不是if线程醒来还会再次进行判断,防止代码被伪唤醒,代码更健壮。还可以防止生产者消费者问题
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		for(int j=1;j<=10;j++){
    			System.out.println("sub thread sequence of "+j+", loop of "+i);
    		}
    		bShouldSub = false;
    		this.notify();
    	}
    	public synchronized void main(int i){
    		while(bShouldSub){
    			try {
    				this.wait();//这里的锁是this
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		for(int j=1;j<=100;j++){
    			System.out.println("main thread sequence of "+j+", loop of "+i);
    		}
    		bShouldSub = true;
    		this.notify();
    	}
    }

    经验:

    1. 要用到共同数据(包括同步锁)或共同算法的若干方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性
    2. 锁是上在要操作的资源的类的内部方法中,而不是线程代码中!好处是以后该类交给任何线程自然就同步了,而不需要考虑互斥同步的问题。
    3. Eclipse中将运行结果保存至文件的操作:Run as-->Run Configuration-->Common-->File处打钩然后选择一个文件

     

    展开全文
  • 主要介绍了Java线程间通信不同步问题,结合实例形式分析了java线程间通信不同步问题的原理并模拟实现了线程间通信不同步情况下的异常输出,需要的朋友可以参考下
  • 主要介绍了java线程间通信的通俗解释,介绍了线程通信中的几个相关概念,然后分享了线程通信的实现方式及代码示例,具有一定参考价值 ,需要的朋友可以了解下。
  • 下面小编就为大家带来一篇浅谈Java线程间通信之wait/notify。小编觉得挺不错的,现在就分享给大家,也给大家做个参考。一起跟随小编过来看看吧
  • java线程间通信的方式

    千次阅读 2020-12-12 20:46:07
    java线程间通信的方式1. 共享变量2. 等待/通知3. 管道流 1. 共享变量 volatile修饰的变量,线程间可见,可使用这种变量作为线程间传递消息的媒介; 延伸出来的,还有redis中的值,数据库中的值,都可以作为线程间...

    java线程间通信的方式

    1. 共享变量

    • volatile修饰的变量,线程间可见,可使用这种变量作为线程间传递消息的媒介;
    • 延伸出来的,还有redis中的值,数据库中的值,都可以作为线程间共同访问的变量;

    2. 等待/通知

    • 同步代码中利用锁对象的wait和notify方法来进行通信;
    • 经典案例如thread.join()方法,里面就是用等待通知机制实现的;

    3. 管道流

    • 管道输入/输出流和普通的文件输入/输出流或者网络输入/输出流不同之处在于,它主要用于线程之间的数据传输,而传输的媒介为内存。
    • 管道输入/输出流主要包括了如下4种具体实现:PipedOutputStream、PipedInputStream、PipedReader和PipedWriter,前两种面向字节,而后两种面向字符。
    package com.demo.other;
    
    import java.io.PipedReader;
    import java.io.PipedWriter;
    
    public class Piped {
        public static void main(String[] args) throws Exception {
            PipedWriter out = new PipedWriter();
            PipedReader in = new PipedReader();
    // 将输出流和输入流进行连接,否则在使用时会抛出IOException
            out.connect(in);
            Thread printThread = new Thread(new Print(in), "PrintThread");
            printThread.start();
            int receive = 0;
            try {
                while ((receive = System.in.read()) != -1) {
                    out.write(receive);
                }
            } finally {
                out.close();
            }
        }
    
    
        static class Print implements Runnable {
            private PipedReader in;
            public Print(PipedReader in) {
                this.in = in;
            }
    
            @Override
            public void run() {
                int receive = 0;
                try {
                    while ((receive = in.read()) != -1) {
                        System.out.print((char) receive);
                    }
                } catch (Exception ex) {
                }
            }
        }
    }
    

    打印:

    hello Kitty
    hello Kitty
    

    通过管道流可以将数据从一个线程写入,而另一个线程读出;

    展开全文
  • java线程间通信、线程池

    千次阅读 2019-08-22 19:38:34
    文章目录第四章:线程状态4.1 线程状态概述4.2 Waiting (无限等待)和线程间通信4.3 线程间通信代码实战第五章:线程池5.1 线程池5.2 线程池的使用 第四章:线程状态 4.1 线程状态概述 当线程被创建并启动以后,它既...

    第四章:线程状态

    4.1 线程状态概述

    当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期中,有几种状态呢?在APIjava.lang.Thread.State这个枚举中给出了六种线程状态

    • 状态描述

      • NEW:至今尚未启动的线程
      • RUNNABLE:正在 java 虚拟机中执行的线程
      • BLOCKER:受阻塞并等待某个监视器锁的线程
      • TIMED_WAITING:在指定的等待时间内都是处于休眠的状态
      • WAITING:无限期地休眠
      • TERMINATED:已退出的线程
    • 状态图

      在这里插入图片描述

    • 阻塞和休眠的区别

      阻塞状态:具有CPU的执行资格,等待CPU空闲时执行

      休眠状态:放弃CPU的执行资格,CPU空闲,也不执行

    4.2 Waiting (无限等待)和线程间通信

    Waiting状态在API中的介绍为:一个正在无限期等待另一个线程执行唤醒动作的线程。这里其实涉及了关于线程间通信的知识——等待唤醒机制。

    • 用一个顾客买包子的案例比喻等待唤醒机制:

    在这里插入图片描述

    • 案例分析

      • 创建一个顾客线程(消费者):告知老板要的包子的种类和数量,调用wait()方法,放弃使用cpu的执行,进入到WAITING状态(无限等待)

      • 创建一个老板线程(生产者):花了5秒做包子,做好包子之后,调用notify()方法,唤醒顾客吃包子

      • 注意:

        • 顾客和老板线程必须使用同步代码块包裹起来,保证等待和唤醒只能有一个在执行
        • 同步使用的锁对象必须保证唯一
        • 只有锁对象才能调用wait和notify方法
      • Object类中的方法

        • void wait()

          在其它线程调用 此对象的notify()方法或notifyAll()方法前,导致当前线程等待

        • void wait(long miles)

          有参数的话,就是计时等待,时间一到,不用唤醒也能自动醒来,跳到RUNNABLE或BLOCKED状态

        • void notify()

          唤醒在此对象监视器上等待的单个线程,如果有多个线程,会随机唤醒一个

        • void notifyAll()

          唤醒在此对象监视器上等待的所有线程

    • 代码演示

      public class WaitAndNotify {
      
      	public static void main(String[] args) {
      		//创建锁对象,保证唯一
      		Object obj = new Object();
      		//创建一个顾客线程
      		new Thread() {
      			public void run() {
      				//保证等待和唤醒的线程只能有一个执行,需要使用同步技术
      				synchronized(obj) {
      					System.out.println("顾客:告知老板要的包子的种类和数量");
      					//调用wait方法,放弃CPU的执行,进入到WAITING无限等待状态
      					try {
      						//编译期异常,但是不能用throws声明,因为父类的run方法没有抛异常声明,子类也不能
      						obj.wait(); 
      					} catch (InterruptedException e) {
      						e.printStackTrace();
      					}
      					//被唤醒之后的代码
      					System.out.println("顾客:开吃");
      				}
      			}
      		}.start();
      		
      		//创建一个老板线程(生产者)
      		new Thread() {
      			public void run() {
      				//老板花了5秒钟做包子
      				try {
      					Thread.sleep(5000);
      				} catch (InterruptedException e) {
      					e.printStackTrace();
      				}
      				synchronized(obj) {
      					//包子已经做好,唤醒顾客吃包子
      					System.out.println("老板:包子已经做好了");
      					//调用notify()方法,唤醒顾客线程
      					obj.notify();
      				}
      			}
      		}.start();
      	}
      }
      
      //补充:刚开始老板线程一直在睡,那肯定是顾客线程先进入到同步代码块,顾客线程先开始执行
      
      /*输出:
          顾客:告知老板要的包子的种类和数量
          老板:包子已经做好了
          顾客:开吃
       */
      

    4.3 线程间通信代码实战

    • 我们继续用4.2中的买包子作为实例,进行细致分析

      在这里插入图片描述

    • 首先包子这个资源是生产者和消费者共享的资源,通过判断包子的状态来决定是哪一个线程去运行

      • 刚开始包子的状态为:没有 (false)—>吃货线程唤醒包子铺线程---->吃货线程等待---->包子铺线程开始做包子---->做好包子---->修改包子的状态为:有 (true)
      • 有包子---->包子铺线程唤醒吃货线程---->包子铺线程等待---->吃货吃包子---->吃完包子---->修改包子的状态为:没有 (false)
      • 没有包子---->吃货线程唤醒包子铺线程---->吃货线程等待---->包子铺线程做包子---->做好包子---->修改包子的状态为:有 (true)
    • 代码分析:需要哪些类

      • 资源类(包子类)。它的属性有

        • 包子的状态:有(true),没有(false);

          public class BaoZi {
          	//皮
          	String pi;
          	
          	//馅
          	String xian;
          	
          	//包子的状态,初始状态为没有包子
          	boolean flag = false;
          }
          
      • 生产者类(包子铺类):是一个线程类,可以继承Thread

        • 设置线程任务(run):生产包子

        • 对包子的状态进行判断

          true:有包子

          ​ 包子铺调用wait()方法进入等待状态,等吃货吃完包子

          false:没有包子

          ​ 包子铺生产包子

          ​ 增加一些趣味:交替生产包子,有两种包子(i%2==0),

          ​ 包子铺生产好了包子,修改包子的状态为true有,唤醒吃货线程起来吃包子

        /*
         * 注意:
         * 	包子铺线程和吃货线程关系--->通信(互斥)
         * 	必须使用同步技术保证两个线程只能有一个在执行
         *  锁对象必须保证唯一,可以使用包子对象作为锁对象
         *  包子铺类和吃货类就需要把包子对象作为参数传进去
         *    1. 需要在成员位置创建一个包子变量
         *    2. 使用带参数构造方法,为这个包子变量赋值
         */
        public class BaoZiPu extends Thread {
        	//1. 需要在成员位置创建一个包子变量,作为锁对象
        	private BaoZi bz;
        	
        	//2. 使用带参数构造方法,为这个包子变量赋值
        	public BaoZiPu(BaoZi bz) {
        		this.bz = bz;
        	}
        	
        	//设置线程任务(run), 生产包子
        	public void run() {
        		//定义一个变量,这个变量决定到底要做什么馅的包子
        		int count = 0;
        		//死循环,让包子铺一直生产包子
        		while(true) {
        			//必须使用同步技术保证两个线程只能有一个在执行
        			synchronized(bz) {
        				//对包子的状态进行判断
        				if(bz.flag == true) {
        					//已经有包子了,不用生产,包子铺调用wait方法进入等待状态
        					try {
        						bz.wait();
        					} catch (InterruptedException e) {
        						e.printStackTrace();
        					}
        				}
        				//被吃货唤醒之后,证明没有包子了,又要开始生产包子
        				//增加一些趣味性:交替生产包子
        				if(count % 2 == 0) {
        					//生产 薄皮三鲜馅饺子
        					bz.pi = "薄皮";
        					bz.xian = "三鲜馅";
        				}else {
        					//生产 冰皮 牛肉大葱馅
        					bz.pi = "冰皮";
        					bz.xian = "牛肉大葱馅";
        				}
        				count++;
        				System.out.println("包子铺正在生产:" + bz.pi + bz.xian + "包子");
        				//生产包子需要3秒钟
        				try {
        					Thread.sleep(3000);
        				} catch (InterruptedException e) {
        					e.printStackTrace();
        				}
        				//包子铺生产好了包子,修改包子的状态为true有
        				bz.flag = true;
        				//唤醒吃货线程起来吃包子
        				bz.notify();
        				//包子铺已经生产好了包子
        				System.out.println("包子铺已经生产好了" + bz.pi + bz.xian + "包子" + "吃货可以开始吃了");
        			}
        		}
        
        	}
        }
        
      • 消费者类(吃货类):是一个线程类,可以继承Thread

        • 设置线程任务(run):吃包子

        • 对包子的状态进行判断

          false:没有包子

          ​ 吃货线程调用wait方法进入等待状态

          true:有包子

          ​ 吃货吃包子

          ​ 吃货吃完包子

          ​ 修改包子的状态为false没有

          ​ 吃货唤醒包子铺线程,生产包子

        public class ChiHuo extends Thread{
        	
        	//1. 需要在成员位置创建一个包子变量,作为锁对象
        	private BaoZi bz;
        	
        	//2. 使用带参数构造方法,为这个包子变量赋值
        	public ChiHuo(BaoZi bz) {
        		this.bz = bz;
        	}	
        	//设置线程任务(run), 吃包子
        	public void run() {
        		//死循环,让吃货一直吃包子
        		while(true) {
        			//必须使用同步技术保证两个线程只能有一个在执行
        			synchronized(bz) {
        				//对包子的状态进行判断
        				if(bz.flag == false) {
        					//发现没有包子,吃货调用wait方法进入等待状态,等包子铺做好包子
        					try {
        						bz.wait();
        					} catch (InterruptedException e) {
        						e.printStackTrace();
        					}
        				}
        				//被包子铺唤醒之后,证明有包子吃了
        				System.out.println("吃货正在吃:" + bz.pi + bz.xian + "的包子");
        				//吃货吃完包子
        				//修改包子的状态为false没有
        				bz.flag = false;
        				//吃货唤醒包子铺线程,继续生产包子
        				bz.notify();
        				System.out.println("吃货已经把:" + bz.pi + bz.xian + "的包子吃完了");
        				System.out.println("---------------------------------------------------------------------------");
        			}
        		}
        	}
        }
        
      • 测试类:

        • 包含main方法,程序执行的入口,启动程序
        • 创建包子对象:创建包子铺程序,创建吃货程序,开启
        public class test {
        public static void main(String[] args) {
        	//创建包子对象,作为锁对象
        	BaoZi bz = new BaoZi();
        	//创建包子铺线程
        	new BaoZiPu(bz).start();
              //开启吃货线程
        	new ChiHuo(bz).start();		
        }
        }
        

    第五章:线程池

    5.1 线程池

    如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。

    • 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源

    • 线程池的底层原理

      在这里插入图片描述

    • 工作原理

      在这里插入图片描述

    5.2 线程池的使用

    线程池:JDK1.5之后提供的

    java.util.concurrent.Executors是线程池的工厂类,用来生成线程池

    • Executors类中的静态方法:

    static ExecutorService newFixedThreadPool(int nThreads)创建一个可重用固定线程数的线程池

    • 参数:int nThreads:创建线程池中的线程数量
    • 返回值:ExecutorService接口返回的是ExecutorService接口的实现类对象,我们可以使用ExecutorService接口接收(面向接口编程)
    • java.util.concurrent.Executors:线程池接口
    • sumbit(Runnale task)用来从线程池中获取线程,调用start方法,执行线程任务
    • void shutdown() 关闭销毁线程池的方法
    • 线程池的使用步骤
    1. 使用线程池的工厂类Executors里边提供的静态方法newFixedThreadPool生产一个指定数量的线程池
    2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
    3. 调用ExecutorService中的方法sumbit,传递线程任务(实现类),开启线程,执行run方法
    4. 调用ExecutorService中的方法shutdown销毁线程池(不建议使用)

    代码演示:

    • 写一个Runnable的子类,实现Runnable接口

      public class RunnableImplements implements Runnable{
      	//2. 创建一个类,实现Runnable接口,重写run方法,设置线程任务
      	@Override
      	public void run() {
      		System.out.println(Thread.currentThread().getName() + "创建一个新的线程");
      	}
      }
      
    • 在main中实验

      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class threadpool {
      	public static void main(String[] args) {
      		//1. 使用线程池的工厂类Executors里边提供的静态方法`newFixedThreadPool`生产一个指定数量的线程池
      		ExecutorService es = Executors.newFixedThreadPool(2);
      		//3. 调用`ExecutorService`中的方法`sumbit`,传递线程任务(实现类),开启线程,执行run方法
      		es.submit(new RunnableImplements());
      		//线程池会一直开启,除非调用shutdown方法
      		//使用完线程之后,会自动把线程归还给任务(实现类),线程可以继续使用
      		es.submit(new RunnableImplements());
      		es.submit(new RunnableImplements());
      		
      		//4. 调用`ExecutorService`中的方法shutdown销毁线程池(不建议使用)
      		es.shutdown();
      	}
      }
      
      /*输出结果: 
          pool-1-thread-1创建一个新的线程
      	pool-1-thread-2创建一个新的线程
      	pool-1-thread-2创建一个新的线程
      	
      	因为线程池只能放2个线程,当Thread-2执行完任务之后,就会自动回到实现类中,再接着执行第二个任务
       */
      
    展开全文
  • JAVA线程间通信问题

    千次阅读 2009-07-18 23:00:00
    JAVA线程间通信问题 关键字: java线程间通信问题问题在前一小节,介绍了在多线程编程中使用同步机制的重要性,并学会了如何实现同步的方法来正确地访问共享资源。这些线程之间的关系是平等的,彼此之间并不存在任何...
  • 使用wait()和notify()实现的生产者与消费者模型,可以了解如何使用wait()和notify()进行线程间通信。(上一次上传的代码有一个问题没有考虑到,这次修补了——CSDN没法撤销资源,只能再上传了)
  • java线程间通信方式

    2018-10-13 17:41:11
    本质上就是共享内存式的通信,这个共享内存在java的内存模型中就是主内存,相当于通过主内存的数据进行线程通信。因Synchronized解锁时会将工作内存中的数据刷新到主内存中,Synchronized加锁时会将工作内存中的值...
  • JAVA线程间通信的几种方式

    万次阅读 多人点赞 2017-08-12 21:55:12
    “编写两个线程,一个线程打印1~25,另一个线程打印字母A~Z,打印顺序为12A34B56C……5152Z,要求使用线程间通信。” 这是一道非常好的面试题,非常能彰显被面者关于多线程的功力,一下子就勾起了我的兴趣。这里...
  • Java线程间通信-回调的实现方式

    千次阅读 2011-02-20 20:47:00
    Quotes: http://lavasoft.blog.51cto.com/62575/98796<br />Java线程间通信-回调的实现方式 Java线程间通信是非常复杂的问题的。线程间通信问题本质上是如何将与线程相关的变量或者对象传递给别的线程,从而实现...
  • 本文将介绍常用的线程间通信工具CountDownLatch、CyclicBarrier和Phaser的用法,并结合实例介绍它们各自的适用场景及相同点和不同点。
  • package Test;/** /** * @author Administrator * @createdate 2017-10-10 */ public class demo1 { public static void main(String[] args){ Thread thread1 = new Thread(new Runnable() { ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 113,196
精华内容 45,278
关键字:

java线程间通信

java 订阅