zoukankan      html  css  js  c++  java
  • 线程间通信

    前言:学习笔记,以供参考

      线程是操作系统中独立的个体,但是这些个体如果不经过特殊处理就不能成为一个整体,线程间通信就成为整体的必用方式之一。

      使用wait/notify方法实现线程间的通信。这两个方法都是Object类的方法。

      1.必须配合关键字synchronized使用;

      2.wait方法释放锁,notify方法不释放锁。

    1.以下是关于阿里的一道面试题,通过这道题,我们理解线程间通信。

    示例:阅读以下代码,通过wait和notify方式对这个代码进行重构

    package com.wp.test;
    import java.util.ArrayList;
    import java.util.List;
    public class ListAdd1 {
        private volatile static List list = new ArrayList<>();
        public void add(){
            list.add("wp");
        }
        public int size(){
            return list.size();
        }
        public static void main(String args[]){
            final ListAdd1 l = new ListAdd1();
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        for(int i=0;i<10;i++){
                            l.add();
                            System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
                            Thread.sleep(500);
                        }
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            },"t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    while(true){
                        if(list.size()==5){
                            System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"  list.size()==5 线程停止");
                            throw new RuntimeException();
                        }
                    }
                }
            },"t2");
            t1.start();
            t2.start();
        }
    }
    View Code

    结果:

    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程收到通知:t2  list.size()==5 线程停止
    Exception in thread "t2" java.lang.RuntimeException
        at com.wp.test.ListAdd1$2.run(ListAdd1.java:36)
        at java.lang.Thread.run(Thread.java:745)
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    View Code

    重构,改成wait和notify方式:

    package com.wp.test;
    import java.util.ArrayList;
    import java.util.List;
    public class ListAdd2 {
        private volatile static List list = new ArrayList<>();
        final static Object lock = new Object();
        public void add(){
            list.add("wp");
        }
        public int size(){
            return list.size();
        }
        public static void main(String args[]){
            final ListAdd2 l = new ListAdd2();
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized(lock){
                        try {
                            for(int i=0;i<10;i++){
                                l.add();
                                System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
                                Thread.sleep(500);
                                if(list.size()==5){
                                    System.out.println("已发出通知!");
                                    lock.notify();
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }        
                    }
                }
            },"t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized(lock){
                        if(list.size()!=5){
                            try {
                                lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"  list.size()==5 线程停止");
                        throw new RuntimeException();
                    }
                }
            },"t2");
            t2.start();
            t1.start();
        }
    }
    View Code

    结果:

    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    已发出通知!
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程收到通知:t2  list.size()==5 线程停止
    Exception in thread "t2" java.lang.RuntimeException
        at com.wp.test.ListAdd2$2.run(ListAdd2.java:47)
        at java.lang.Thread.run(Thread.java:745)
    View Code

    问题:有什么弊端?操作不是实时的。 解决方式:使用CountDownLatch实现实时通知。

    package com.wp.test;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.concurrent.CountDownLatch;
    public class ListAdd2 {
        private volatile static List list = new ArrayList<>();
        final static Object lock = new Object();
        final static CountDownLatch countDownLatch = new CountDownLatch(1);
        public void add(){
            list.add("wp");
        }
        public int size(){
            return list.size();
        }
        public static void main(String args[]){
            final ListAdd2 l = new ListAdd2();
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //synchronized(lock){
                        try {
                            for(int i=0;i<10;i++){
                                l.add();
                                System.out.println("当前线程:"+Thread.currentThread().getName()+"添加了一个元素");
                                Thread.sleep(500);
                                if(list.size()==5){
                                    System.out.println("已发出通知!");
                                    //lock.notify();
                                    countDownLatch.countDown();
                                }
                            }
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }        
                   // }
                }
            },"t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    //synchronized(lock){
                        if(list.size()!=5){
                            try {
                                countDownLatch.await();
                                //lock.wait();
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                        System.out.println("当前线程收到通知:"+Thread.currentThread().getName()+"  list.size()==5 线程停止");
                        throw new RuntimeException();
                   // }
                }
            },"t2");
            t2.start();
            t1.start();
        }
    }
    View Code

    结果:是实时的操作。

    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    已发出通知!
    当前线程:t1添加了一个元素
    当前线程收到通知:t2  list.size()==5 线程停止
    Exception in thread "t2" java.lang.RuntimeException
        at com.wp.test.ListAdd2$2.run(ListAdd2.java:51)
        at java.lang.Thread.run(Thread.java:745)
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    当前线程:t1添加了一个元素
    View Code

     2.使用wait和notify模拟LinkedBlockingQueue

      BlockingQueue:是一个队列,并且支持阻塞机制,阻塞的放入和得到数据。我们要实现LinkedBlockingQueue下面的两个简单的方法put和take。

      Put(anObject):把anObject加到BlockingQueue里,如果BlockQueue没有空间,则调用此方法的线程被阻塞,直到BlockingQueue里面有空间再继续。

      Take:取走BlockingQueue里排在首位的对象,若BlockingQueue为空,阻塞进入等待状态,直到BlockingQueue有新的数据被加入。

    package com.wp.test;
    import java.util.LinkedList;
    import java.util.concurrent.atomic.AtomicInteger;
    public class MyQueue {
        private final LinkedList<Object> list = new LinkedList<Object>();
        private final AtomicInteger count = new AtomicInteger(0);
        private final int maxSize;
        private final int minSize = 0;
        private final Object lock = new Object();
        public MyQueue (int maxSize){
            this.maxSize = maxSize;
        }
        public void put (Object obj) {
            synchronized(lock){
                while(count.get() == maxSize){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                list.add(obj);
                count.getAndIncrement();
                System.out.println(" 元素 " + obj + " 被添加 ");
                lock.notify();
            }
        }
        public Object take(){
            Object temp = null;
            synchronized (lock) {
                while(count.get() == minSize){
                    try {
                        lock.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                count.getAndDecrement();
                temp = list.removeFirst();
                System.out.println(" 元素 " + temp + " 被消费 ");
                lock.notify();
            }
            return temp;
        }
        public int size(){
            return count.get();
        }
        public static void main(String[] args) throws Exception {
            final MyQueue m = new MyQueue(5);
            m.put("a");
            m.put("b");
            m.put("c");
            m.put("d");
            m.put("e");
            System.out.println("当前元素个数:" + m.size());
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    m.put("h");
                    m.put("i");
                }
            }, "t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        Object t1 = m.take();
                        //System.out.println("被取走的元素为:" + t1);
                        Thread.sleep(1000);
                        Object t2 = m.take();
                        //System.out.println("被取走的元素为:" + t2);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "t2");
            t1.start();
            Thread.sleep(1000);
            t2.start();
        }
    }
    View Code

    结果:

    元素 a 被添加 
     元素 b 被添加 
     元素 c 被添加 
     元素 d 被添加 
     元素 e 被添加 
    当前元素个数:5
     元素 a 被消费 
     元素 h 被添加 
     元素 b 被消费 
     元素 i 被添加
    View Code

    3.ThreadLocal

      ThreadLocal概念:线程局部变量,是一种多线程间并发访问变量的解决方案。与其synchronized等加锁的方式不同,ThreadLocal完全不提供锁,而是用以空间换时间的手段,为每个线程提供变量的独立副本,以保障线程安全。

      从性能上说,ThreadLocal不具有绝对优势,在并发不是很高的时候,加锁的性能会更好,但作为一套与锁完全无关的线程安全解决方案,在高并发量或者竞争激烈的场景,使用ThreadLocal可以在一定程度上减少锁竞争。

    package com.wp.test;
    public class ConnThreadLocal {
        public static ThreadLocal<String> th = new ThreadLocal<String>();
        public void setTh(String value){
            th.set(value);
        }
        public void getTh(){
            System.out.println(Thread.currentThread().getName() + ":" + this.th.get());
        }
        public static void main(String[] args) throws InterruptedException {
            final ConnThreadLocal ct = new ConnThreadLocal();
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    ct.setTh("张三");
                    ct.getTh();
                }
            }, "t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        Thread.sleep(1000);
                        //ct.setTh("李四");
                        ct.getTh();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }, "t2");
            t1.start();
            t2.start();
        }
    }
    View Code

    结果分析:线程之间通过ThreadLocal使用属于自己的变量。ThreadLocal中有一个map容器key是当前线程t,value是当前线程对应变量的值。

    4.单例&多线程

      单例模式,最常见的就是饥饿模式和懒汉模式,一个是在声明的同时直接实例化对象,一个在调用方法时进行实例化对象。在多线程模式中,考虑到性能和线程安全的问题,我们一般选择下面两种比较经典的单例模式,在性能体高的同时,要保证了线程安全。

      dubble check instance

      static inner class

    1. Double check instance(属于懒汉模式)

    package com.wp.test;
    public class DoubleSingleton {
        private static DoubleSingleton ds;
        public static DoubleSingleton getDs(){
            if(ds == null){
                try {
                    //模拟初始化对象的准备时间...
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (DoubleSingleton.class) {
                    if(ds == null){
                        ds = new DoubleSingleton();
                    }
                }
            }
            return ds;
        }
        public static void main(String[] args) {
            Thread t1 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(DoubleSingleton.getDs().hashCode());
                }
            },"t1");
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(DoubleSingleton.getDs().hashCode());
                }
            },"t2");
            Thread t3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    System.out.println(DoubleSingleton.getDs().hashCode());
                }
            },"t3");
            t1.start();
            t2.start();
            t3.start();
        }
    }
    View Code

      结果分析:输出结果一样。如果在生成对象方法中,去掉ds == null 判断,那么输出结果不一样。原因?

    2.Static inner class(类似饿汉模式)

    package com.wp.test;
    public class InnerSingleton {
        private static class Singletion {
            private static Singletion single = new Singletion();
        }
        public static Singletion getInstance(){
            return Singletion.single;
        }
    }
  • 相关阅读:
    kafka.common.FailedToSendMessageException: Failed to send messages after 3 tries.
    MYSQL 大数据
    MYSQL 数据库优化
    原始代理需要改进的地方
    JDK动态代理
    使用CGLIB生成代理
    Spring
    Struts2面试题
    hibernate面试题
    Mac下创建隐藏用户
  • 原文地址:https://www.cnblogs.com/zhanxiaoyun/p/6102387.html
Copyright © 2011-2022 走看看