并发编程实践笔记_第五章

本文深入探讨了Java中并发容器的特点与使用,包括同步容器与并发容器的区别、CopyOnWriteArrayList的工作原理、ConcurrentHashMap的特性及其实现高效的缓存策略等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

5.1: 什么是同步容器?线程安全的容器类,arraylist,hashmap,hashtable,vector等等到时容器,只有vector和hashtable才是线程安全的,其他诸如list等容器亦存在其同步包装类(通过Collection.synchronizaexx方法获得)

5.11:同步容器中的问题:同步容器类只保证其单个操作的线程安全,若然对着一些操作复合应用的话就需要进行同步。(注意对同步容器的加锁上:同步容器支持客户端加锁,锁应该加载同步容器对象自身,但是对于其他底层加锁的策略未被了解的类不应该随便加锁)


5.1.2:concurrentmodifacationexception和迭代器:对于同步容器使用迭代器或通过for循环进行迭代(如list的安全包装类)时,若然容器被其他线程修改会爆出该异常。

注意:对容器来说,tostring,hashcode,equals方法,用作构造器参数,用作key,containsall,removerall,retainAll等发发都会引发其对容器的隐性迭代。


5.2:

1.注意同步容器和并发容器的分别:不同于collections.synchronizexx获得的同步容器,concurrentxx的并发容器,拥有更高得
可扩展性,并且支持常见的符合操作,但会有稍高的风险性


2.concurrentHashMap:

《1》提供不会抛出concurrentModifacationException的迭代器,再非是及时失败的,而是提供弱一致性

《2》对于如size,isempty等操作的并发性被削弱,可能只返回一个估算值。

《3》不同于同步容器,不会提供独占性访问(不在是通过锁容器对象本身提供同步),不能再使用客户端加锁来创建新的原子操作,但一些常见的符合操作的实现已经是原子化的

《4》只有需要在独占访问中加锁时,并发容器才无法胜任


3.copyOnWriteArrayList:

《1》提供了更好的并发性和避免了加锁和复制

《2》“写入时复制”:通过正确发布不可变对象使得不需要更多的同步操作。通过创建并发布新的不可变对象实现修改。

《3》避免了容器的复制,依然需要在每次创建容器时复制基础数组,一样会在大数据量时有较大开销

《4》如事件通知系统:对监听器的注册与注销(对容器的修改)远少于接受到时间通知(对容器的迭代)的次数。这样的情况下适用


4.阻塞队列与生产者消费者模式(Blocking queue):

《1》Blocking queue:提供可阻塞的put和take方法,队满时put等待,对空时take等待。(长度可以为无限,此时put方法永远不会阻塞)

《2》Priority Blocking queue:不是通过先进先出方式来获得执行资源,而是同过comparator来排序决定执行顺序

《3》SynchroizeQueue:在下一个线程准备做put或take操作之前一直阻塞,使用于消费者较多的情况


5.3 : 双端队列和窃取工作模式:

《1》双端工作队列:两种新容器:ArrayDeque和LinkedBlockingDeque ,分别扩展至queue和BlockingQueue。

《2》窃取工作模式:工作者线程不在共享队列,各自拥有一个双端队列,工作线程从对头获取任务,当一个工作线程完成队列中的任务后会尝试从其他线程的队列的队尾‘窃取’任务。对比生产者消费者模式更具有可伸缩性。(参考java7 的Fork(分叉)/Join(合并)框架)


5.4:阻塞与中断

《1》阻塞:被阻塞的线程必须等待某事件才能继续操作,当一个线程可以排除interruptedException是告诉我们这是一个可以中断的方法,几种阻塞的情况: sleep()-----可中断阻塞,等待锁(wait())-------不可中断阻塞,等待i/o----不可中断阻塞

转载的一些相关资料:

1 Thread.sleep()是可中断的阻塞;IO引起的阻塞不可中断;synchronized引起的阻塞不可中断。

2 “IO引起的阻塞不可中断”的解决方案:关闭底层资源,即关闭IO流。

3 wait(),notify(),notifyAll()只能在同步方法或者是同步方法块中调用;sleep()可以在飞同步方法中调用;sleep()不会发起锁;wait()会放弃锁,然后等待notify()或者notifyAll()的唤醒或者时间片到。

《2》中断:一个阻塞的线程如果可以被中断则可以提前结束阻塞。

关于中断的相应:

【1】传递interruptedException,直接抛出给调用者或者通过try/catch作简单的处理后抛出给调用者

【2】恢复中断:有时候不能排除interruptedException,例如在runnable代码中时。这种情况下必须在方法中捕获该异常,并通过interrupt()方法恢复该中断(存在一个boolean的属性,通过interrupt()会把该变量置为true,通过这样一个方法通知调用对象),注意关于interrupt()方法是通过协作方式中断线程,并不会立刻中断

public static boolean interrupted测试当前线程是否已经中断。线程的中断状态 由该方法清除。换句话说,如果连续两次调用该方法,则第二次调用将返回 false(在第一次调用已清除了其中断状态之后,且第二次调用检验完中断状态前,当前线程再次中断的情况除外)。
public boolean isInterrupted()测试线程是否已经中断。线程的中断状态 不受该方法的影响。
public void interrupt()中断线程。

【3】捕获InterruptedException,并通过调用interrupt重新恢复中断状态(再次调用interrupt方法会把该布尔值置为false,把中断状态恢复)。

import java.util.logging.Level;
import java.util.logging.Logger;

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 *
 * @author Administrator
 */
public class TestIntterupt {

    public static class T extends Thread {
        public void run() {
            try {
                this.sleep(3000);
            } catch (InterruptedException ex) {
                System.out.println("-----1------"+this.isInterrupted());//false
                //若恢复后循环前没有打印,则看不出结果
//                this.interrupt();//若通过interrupt方法恢复中断状态,在run内代码执行完之前,当前线程和其他线程都能看到状态为true,不调用的话t2永远看到false
                System.out.println("--------------------------------------------恢复中断状态-------------------------------------------------------");
            }
            int k = 0;
            while(k<=10000){
                k++;
                System.out.println("k~~"+k);
            }
            System.out.println("=============================================xunhuan2=============================================");//线程执行完后中断状态为false
        }
    }
    public static void main(String[] args) {
        final T t = new T();
        t.start();
        try {
            Thread.currentThread().sleep(1000);
        } catch (InterruptedException ex) {
            Logger.getLogger(TestIntterupt.class.getName()).log(Level.SEVERE, null, ex);
        }
        t.interrupt();
        System.out.println("-----ahhhhhhhhhhhhhhhh------"+t.isInterrupted());//此处有可能打印true,原因未知
        Thread t2 = new Thread(){
        public void run() {
                 while(true){
                    System.out.println("-----2------"+t.isInterrupted());
                }
        }
        };
        t2.start();
        while(true){
                    System.out.println("-----main------"+t.isInterrupted());
        }
    }

}

【4】不要掩盖中断(捕获异常而不作任何操作)

5.5sychronizer:sychronizer是一个对象,它根据本身的状态调节线程的控制流

1.可以扮演sychronizer角色:阻塞队列,信号量(semaphore),关卡(barrier),闭锁(latch)

2.闭锁:是一种sychronizer,它可以延迟线程的进度知道线程到达终点状态。

                闭锁用于确保一个活动直到其他活动完成后才开始执行

3.CountDownLatch实现得闭锁:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package five;

import java.util.concurrent.CountDownLatch;
import java.util.logging.Level;
import java.util.logging.Logger;

public class TestLatch {
/**
 * 通过使用两个闭锁(使用CountDownLatch实现)来控制多个程序的开始和结束
 * 开始阀门startGate,用于确保所有线程同时开始运行(防止先启动的任务具有领先优势)
 * 结束阀门endGate,用于确保最后计算时间时所有的任务都执行完
 * 
 * CountDownLatch:用以个正数状态表示需要等待的事件数
 * 使用countDown方法对计数器作减操作,使用await等待事件执行完
 * 
 */
    public static void main(String[] args ){
    final CountDownLatch startGate = new CountDownLatch(1);//等待所有任务开始这一件事
    final CountDownLatch endGate = new CountDownLatch(10);//等待n个任务的完成事件
    for(int i = 0 ;i<10;i++){
    Thread t = new Thread(){
    @Override
    public void run(){
                    try {
                        startGate.await();//也就是所await也是可中断阻塞?
                        this.sleep(500);//执行某操作
                        System.out.println(Thread.currentThread().getName()+"执行完了");
                        endGate.countDown();//完成一件事
                    } catch (InterruptedException ex) {
                        Logger.getLogger(TestLatch.class.getName()).log(Level.SEVERE, null, ex);
                    }

    }
    };
    t.start();
    }
    Long t1 = System.currentTimeMillis();
    startGate.countDown();//同时开始执行
        try {
            endGate.await();
        } catch (InterruptedException ex) {
            Logger.getLogger(TestLatch.class.getName()).log(Level.SEVERE, null, ex);
        }
        System.out.println("耗时为:"+(System.currentTimeMillis()-t1));//当所有任务执行完才开始计时
    }

}

4.FutureTask实现得闭锁: 

【1.】FutureTask是通过callable实现得,相当于一个可以返回结果的runnable

【2.】FutureTask确保安全发布的结果在运行计算的线程执行完计算后返回给get()的调用者

【3.】callable可以跑出任何受检查和未受检查的异常并且任何代码都可以跑出error,但是无论抛出什么都会被get方法包装成ExecutionException,所以对于诱因是作为Throwable返回的ExecutionException异常处理较为负载,这些异常通常由三种原因产生:1.受检查异常,runtimeException和error。

【4】尽早开始你的计算,若通过FutureTask的方式执行类似数据库查询的任务,只要确保结果安全发布,可提高效率。

5.semaphore:信号量

6.关卡:阻塞一组线程直到某些事件发生,与闭锁的不同在于,关卡等待的是其他线程,闭锁等待的是事件

【1】CyclicBarrier:

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package five;

import java.util.concurrent.CyclicBarrier;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * CyclicBarrier实现关卡,使得一个线程等待其他所有线程到达关卡点
 * 可以设置CyclicBarrier的线程个数和关卡行为(所有线程到达关卡点后执行的子线程)
 *
 * 线程组的线程执行到指定关卡点后调用awiat方法,所有到达后才执行关卡行为并同时开始往下执行
 *
 * @author Administrator
 */
public class TestCyclicBarrier {

    public  static CyclicBarrier  cyclicBarrier = new CyclicBarrier(10,new Runnable(){
        @Override
        public void run() {
            System.out.println("所有的线程到达关卡点");
        }

    });
    public static class Task extends Thread{
        CyclicBarrier  cyclicBarrier;
        int n ;
        public Task(CyclicBarrier  cyclicBarrier,int n){
        this.cyclicBarrier=cyclicBarrier;
        this.n=n;
        }
        public void run(){
            try {
                System.out.println(Thread.currentThread().getName() + "---开始执行");
                this.sleep(n * 100);
                System.out.println(Thread.currentThread().getName() + "---到达关卡点");
                this.cyclicBarrier.await();
                this.sleep(3000);
            } catch (Exception ex) {
                Logger.getLogger(TestCyclicBarrier.class.getName()).log(Level.SEVERE, null, ex);
            }
        }
    };
    public static void main(String[] args){
        for(int i = 1 ;i<=10;i++){
            new Task(cyclicBarrier,i).start();
        }
    }
}

【2】Exchanger:是一种两部关卡,在关卡点会交换数据,并且为交换为双方的对象建立安全发布
/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package five;

import java.util.concurrent.Exchanger;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *Exchanger是关卡的一种形式
 * 适用于双方进行的活动
 * 通过调用同一Exchanger对象的exchange方法来交换数据
 * exchanger保证交换的对象的安全发布
 * @author Administrator
 */
public class TestExchanger {
    public static Exchanger<Object> exchanger = new Exchanger<Object>();
    public static class Task extends Thread{
    Exchanger<Object> exchanger;
    long time;
    public Task(Exchanger<Object> exchanger,long time){
    this.exchanger = exchanger;
    this.time = time;
    }
    public void run(){
            try {
                this.sleep(time * 1000);
                System.out.println(Thread.currentThread().getName()+"--执行到关卡点");
            } catch (InterruptedException ex) {
                Logger.getLogger(TestExchanger.class.getName()).log(Level.SEVERE, null, ex);
            }
            try {
                Object o= this.exchanger.exchange(Thread.currentThread().getName());
                System.out.println(Thread.currentThread().getName()+"获得的数据:"+o);
            } catch (InterruptedException ex) {
                Logger.getLogger(TestExchanger.class.getName()).log(Level.SEVERE, null, ex);
            }
    }
    };


    public static void main(String args[]){
    new Task(exchanger,1).start();
    new Task(exchanger,2).start();
    }

}


5.6 为计算结果实现高效可伸缩的缓存

《1》使用ConcurrentHashMap(提供原子化的基础操作和简单的复合操作,但不同于stnchronziedMap注意不能通过锁容器对象自身来提供实现原子性的复合操作)而不是hashMap辅以synchronzied来同步,提升效率,无需使用复杂的同步代码。

《2》‘缓存污染’的消除,因为可能有两个任务区执行计算包含相同key值得结果,而在计算完成之前,另一方无法从map中获得结果,导致重复计算。(采用ConcurrentHashMap<A,FUTRUE<V>>,该任务使用ConcurrentHashMap提供的原子性的putifabsent方法保证不会有两个相同的任务执行,而当线程发现当前要计算的任务正在计算中即使用future来等待并获得结果)(当两个函数的参数相同时判断为相同结果的任务)

《3》当计算其中一个计算出现问题,可能导致后面所有相同的计算都不会得到结果,所以需要在反生异常或发现没有在计算的任务时对其执行移除。

/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package five.cache;

import java.util.concurrent.Callable;
import java.util.concurrent.CancellationException;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 *
 * @author Administrator
 */
public class Memoizer {

    public static final ConcurrentHashMap<Integer, FutureTask<Object>> concurrentHashMap = new ConcurrentHashMap<Integer, FutureTask<Object>>();//用来存储任务
    /**
     * 對於FutureTask的get方法,无论callable抛出的是受检查异常,RuntimeException还是error都会抛出ExecutionExceprion
     * 区分处理
     * @param t
     * @return
     */
    public static RuntimeException launderException(Throwable t){
        if(t instanceof RuntimeException){
            return (RuntimeException)t;//运行时异常的话返回供处理
        }else if(t instanceof Error){
        throw (Error)t;//错误的话直接抛出
        }else{
        throw new IllegalStateException("未检查异常",t);
        }
    }
    public static class Task {
        private  ConcurrentHashMap<Integer, FutureTask<Object>> concurrentHashMap;
        public Task(ConcurrentHashMap<Integer, FutureTask<Object>> concurrentHashMap){
            this.concurrentHashMap = concurrentHashMap;
        }
        protected Integer compute(Integer i) {
            try {
                Thread.currentThread().sleep(i * 100);
                System.out.println(Thread.currentThread().getName() + "睡眠了" + (i * 100) + "毫秒");
            } catch (InterruptedException ex) {
                Logger.getLogger(Memoizer.class.getName()).log(Level.SEVERE, null, ex);
            }
            System.out.println(10 * i);
            return 10 * i;
        }

        public Integer computeIfNotComputed(final Integer i) {
            FutureTask<Object> future = concurrentHashMap.get(i);
            if (future == null) {//该任务还未执行
                Callable<Object> callable = new Callable<Object>() {

                    @Override
                    public Object call() throws Exception {
                        return Task.this.compute(i);
                    }
                };
                FutureTask<Object> f = new FutureTask<Object>(callable);
                future = concurrentHashMap.putIfAbsent(i, f);//原子操作,若当前不存在key值相同则返回null,否则不插入,返回当前存在值
                if(future == null){
                future = f;
                future.run();
                }
            }else{
                System.out.println(Thread.currentThread().getName()+"任務已經執行【"+i+"】");
            }
           try {
                     return (Integer) future.get();
                } catch (CancellationException ex) {
                    concurrentHashMap.remove(future);//取消任务,防止相同任务无法执行
                    throw new RuntimeException(Thread.currentThread().getName()+"任务取消");
                } catch (ExecutionException ex) {
                    throw launderException(ex);
                }catch (InterruptedException ex) {
                     Thread.currentThread().interrupt();//恢复中断
                     throw new RuntimeException(Thread.currentThread().getName()+"中断");
                }
        }
    }
    public static void main(String args[]){

        for(int i =1;i<=100;i++){
        new Thread(){
        @Override
        public void run(){
        new Task(concurrentHashMap).computeIfNotComputed((int)(Math.random()*10));
        }
        }.start();
        }
    }


}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值