美文网首页
Java中的多线程进阶篇

Java中的多线程进阶篇

作者: dafaycoding | 来源:发表于2017-02-07 16:48 被阅读46次

    这一篇主要是学习Effecttive Java并发章节的笔记。

    1、同步访问共享的可变数据

    如果没有同步,一个线程的变化就不能被其他线程看到。同步不仅可以阻止一个线程看到对象处于不一致的状态之中,它还可以保证进入同步方法或者同步代码块的每个线程,都看到由同一个锁保护的之前所有的修改效果。

    为了在线程之间进行可靠的通信,也为了互斥访问,同步是必要的。

    1、不要使用Thread.stop
    阻止一个线程妨碍另一个线程的任务,Java中提供了Thread.stop方法,但这个方法本质上是不安全的——使用它会导致数据遭到破坏。

    要阻止一个线程妨碍另一个线程,建议的做法是让一个线程轮询(poll)一个boolean域,这个域一开始为false,但是可以通过第二个线程设置为true,以表示第一个线程终止自己。由于boolean域的读写操作都是原子的,程序员在访问这个域的时候不再使用同步:
    错误示例:

    
    //这个程序不会终止:因为后台线程永远在循环!
    private static boolean stopRequested;
        private void test_1(){
            Thread backgroundThread=new Thread(new Runnable() {
                @Override
                public void run() {
                    int i=0;
                    LogUtils.w("stopRequested="+stopRequested);
                    while(!stopRequested){
                        i++;
                        LogUtils.w("i="+i);
                    }
                }
            });
    
            backgroundThread.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            stopRequested=true;
        }
    
    

    正确示例1:

    //注意读和写方法都要同步
        private static boolean stopRequested;
    
        private static synchronized void requestStop(){
            stopRequested=true;
    
        }
        private static synchronized boolean stopRequested(){
            return stopRequested;
        }
    
        private void test_2(){
            Thread backgroundThread=new Thread(new Runnable() {
                @Override
                public void run() {
                    int i=0;
                    while(!stopRequested()){
                        i++;
                        LogUtils.w("i="+i);
                    }
                }
            });
    
            backgroundThread.start();
    
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            requestStop();
    
        }
    
    

    正确示例2:

    //volatile修饰符不执行互斥访问,但它可以保证任何一个线程在读取该域的时候都将看到最近刚被写入的值。
    private static volatile boolean stopRequested;
    
    
        private void test_2(){
            Thread backgroundThread=new Thread(new Runnable() {
                @Override
                public void run() {
                    int i=0;
                    while(!stopRequested){
                        i++;
                        LogUtils.w("i="+i);
                    }
                }
            });
    
            backgroundThread.start();
    
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            stopRequested=true;
    
        }
    
    

    建议:最好的办法是不共享可变的数据,要么共享不可变的数据。换句话说,将可变数据限制在单个线程中。

    2、过度同步

    /**集合类*/
    public class ForwardingSet<E> implements Set<E> {
        private final Set<E> s;
    
        public ForwardingSet(Set<E> s) {
            this.s = s;
        }
    
        @Override
        public int size() {
            return 0;
        }
    
        @Override
        public boolean isEmpty() {
            return s.isEmpty();
        }
    
        @Override
        public boolean contains(Object o) {
            return s.contains(o);
        }
    
        @NonNull
        @Override
        public Iterator<E> iterator() {
            return s.iterator();
        }
    
        @NonNull
        @Override
        public Object[] toArray() {
            return s.toArray();
        }
    
        @NonNull
        @Override
        public <T> T[] toArray(T[] a) {
            return s.toArray(a);
        }
    
        @Override
        public boolean add(E e) {
            return s.add(e);
        }
    
        @Override
        public boolean remove(Object o) {
            return s.remove(o);
        }
    
        @Override
        public boolean containsAll(Collection<?> c) {
            return s.containsAll(c);
        }
    
        @Override
        public boolean addAll(Collection<? extends E> c) {
            return s.addAll(c);
        }
    
        @Override
        public boolean retainAll(Collection<?> c) {
            return s.retainAll(c);
        }
    
        @Override
        public boolean removeAll(Collection<?> c) {
            return s.removeAll(c);
        }
    
        @Override
        public void clear() {
            s.clear();
        }
    
        @Override
        public boolean equals(Object obj) {
            return s.equals(obj);
        }
    
        @Override
        public int hashCode() {
            return s.hashCode();
        }
    
        @Override
        public String toString() {
            return s.toString();
        }
    
    
    }
    
    
    /**添加观察者模式*/
    public class ObservableSet<E> extends ForwardingSet<E> {
    
        public ObservableSet(Set<E> s) {
            super(s);
        }
    
        private final List<SetObserver<E>> observers=new ArrayList<>();
    
        public void addObserver(SetObserver<E> observer){
            synchronized (observers){
                observers.add(observer);
            }
        }
    
        public boolean removeObserver(SetObserver<E> observer){
            synchronized (observers){
                return observers.remove(observer);
            }
        }
    
        private void notifyElementAdded(E element){
            synchronized (observers){
                for(SetObserver<E> observer: observers){
                    observer.added(this,element);
                }
            }
        }
    
        public  boolean add(E element){
            boolean added=super.add(element);
            if(added){
                notifyElementAdded(element);
            }
            return added;
        }
    
        public boolean addAll(Collection<? extends E> c){
            boolean result=false;
            for(E elemnet:c){
                result|=add(elemnet);
            }
            return result;
        }
    
        
        public interface SetObserver<E>{
            void added(ObservableSet<E> set,E element);
        }
    }
    

    下面的测试中 迭代是是在一个同步的块中,可以防止并发修改,但是无法防止迭代线程本身回调到可观察的集合中,也无法防止修改它的observers列表。

    /**
         * 测试 ConcurrentModificationException异常 修改正在遍历的集合
         * 测试没有出现问题???
         */
        private void test_1() {
            ObservableSet<Integer> set=new ObservableSet<Integer>(new HashSet<Integer>());
    
            set.addObserver(new ObservableSet.SetObserver<Integer>() {
                @Override
                public void added(ObservableSet<Integer> set, Integer element) {
                    Log.w("addObserver","element="+element);
                    if(element==23){
                        set.removeObserver(this);
                    }
                }
            });
    
            for(int i=0;i<100;i++){
                LogUtils.w(this,"i="+i);
                set.add(i);
            }
        }
    
    

    后台线程调用s.removeObserver,它企图锁定observers,但它无法获取该锁,因为主线程已经有锁了。在这期间,主线程一直在等待后台线程来完成对观察者的删除,这正是造成死锁的原因。

    /**死锁测试*/
    private void test_2() {
            ObservableSet<Integer> set=new ObservableSet<Integer>(new HashSet<Integer>());
    
            set.addObserver(new ObservableSet.SetObserver<Integer>() {
                @Override
                public void added(final ObservableSet<Integer> s, Integer element) {
                    Log.w("addObserver","element="+element);
                    if(element==23){
                        ExecutorService executor= Executors.newSingleThreadExecutor();
                        final ObservableSet.SetObserver<Integer> observer=this;
                        try {
                            executor.submit(new Runnable() {
                                @Override
                                public void run() {
                                    s.removeObserver(observer);
                                }
                            }).get();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        } catch (ExecutionException e) {
                            e.printStackTrace();
                        }
                    }
                }
            });
    
            for(int i=0;i<100;i++){
                LogUtils.w(this,"i="+i);
                set.add(i);
            }
        }
    

    修改方法 将外来方法的调用移出同步的代码块,使用并发集合CopyOnWriteArrayList。

    public class ObservableSet<E> extends ForwardingSet<E> {
    
        public ObservableSet(Set<E> s) {
            super(s);
        }
    
    
    //    private final List<SetObserver<E>> observers=new ArrayList<>();
        private final List<SetObserver<E>> observers=new CopyOnWriteArrayList<>();
    
        public void addObserver(SetObserver<E> observer){
                observers.add(observer);
        }
    
        public boolean removeObserver(SetObserver<E> observer){
                return observers.remove(observer);
        }
    
        private void notifyElementAdded(E element){
                for(SetObserver<E> observer: observers){
                    observer.added(this,element);
                }
        }
    
        public  boolean add(E element){
            boolean added=super.add(element);
            if(added){
                notifyElementAdded(element);
            }
            return added;
        }
    
        public boolean addAll(Collection<? extends E> c){
            boolean result=false;
            for(E elemnet:c){
                result|=add(elemnet);
            }
            return result;
        }
    
    
        public interface SetObserver<E>{
            void added(ObservableSet<E> set,E element);
        }
    }
    
    

    你应该在同步区域内做尽可能少的工作。
    不要过度同步,在多核的时代,过度同步的实际成本并不是指获取锁所花费的cpu时间,而是指失去了并行的机会,以及因为需要确保每个核都有一个一致的内存视图而导致的延迟。过度同步另一个潜在开销在于,它会限制VM优化代码执行的能力。
    StringButter实例几乎总是被用于单个线程中,而它们执行的却是内部同步。因此,StringButter基本上都是由StringBuilder代替。

    executor和task优先于线程

    Java平台中增加了java.util.concurrent。这个包中包含了一个Executor Framework,这是一个很灵活的基于接口的任务执行工具。它创建了一个在各方面都比好的工作队列,只需一行代码:

    ExecutorService executor=Executors.newSingleThreadExecutor();
    //提交一个runnable方法
    executor.execute(runnable);
    //executor终止(如果做不到这一点,虚拟机可能将不会退出)
    executor.shutdown();
    

    可以利用executor service 完成很多事情:

    • 等待完成一项任务
    • 等待一个任务集合中的任务或者所有任务完成(利用invokeAny或者invokeAll方法)
    • 等待executor service优雅地完成终止(利用awaitTermination方法)
    • 任务完成时逐个地获取这些任务的结果(利用ExecutorCompletionService)
      如果想让不止一个线程来处理来自这个队列的请求,只要调用一个不同的静态工厂,这个工厂创建了一种不同的executor service,称作线程池。
      可以直接使用ThreadPoolExecutor类,这个类允许你控制线程池操作的几乎每个方面。

    如果编写的是小程序,或者轻载服务器,使用Executors.newCachedThreadPool是个不错的选择
    如果服务器负载太重,以致它所有的CPU都完全被占用,Executors.newCachedThreadPool 当有更多的任务时,就会创建更多的线程,这样只会使情况更糟。因此,在大负载的产品服务中,最好使用Executors.newFixedThteadPool(固定数目的线程池)。想最大限度的控制它,使用ThreadPoolExecutor类。

    任务用两种,Runnable及其近亲Callable(有返回值)

    本质上讲,Executor Famework所做的工作是执行。
    Executor Framework也有一个可以代替java.util.Timer的东西,timer只用一个线程执行任务,这在面对长期运行的任务时,会影响到定时的准确性。如果timer唯一的线程抛出未被捕获的异常,timer就会停止执行。使用ScheduledThreadPoolExecutor可支持多个线程,并且优雅地从抛出未受检异常的任务中恢复。
    可参考Java 并发专题 : Timer的缺陷 用ScheduledExecutorService替代


    未完待续~

    相关文章

      网友评论

          本文标题:Java中的多线程进阶篇

          本文链接:https://www.haomeiwen.com/subject/encmittx.html