线程间通信

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

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

  使用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;
    }
}

 

posted @ 2016-12-01 10:19  展云  阅读(317)  评论(0编辑  收藏  举报