二十一、并发----Thinking in Java 阅读笔记

1. 并发的多面性

  1. 更快地执行
  2. 改进代码设计

2. 基本的线程机制

  1. 定义任务

    通过实现Runnable接口

  2. Tread类(接收任务并启动线程)

    当在Java程序中创建一个线程,它就被称为用户线程

  3. 使用Executor

    Executor框架同java.util.concurrent.Executor 接口在Java 5中被引入。Executor框架是一个根据一组执行策略调用,调度,执行和控制的异步任务的框架。

    无限制的创建线程会引起应用程序内存溢出。所以创建一个线程池是个更好的的解决方案,因为可以限制线程的数量并且可以回收再利用这些线程。利用Executors框架可以非常方便的创建一个线程池。

    Executors为Executor,ExecutorService,ScheduledExecutorService,ThreadFactory和Callable类提供了一些工具方法。

    Executors可以用于方便的创建线程池。

  4. 从任务中产生返回值(定义能够产生返回值的任务)

    • 通过实现Callable<V>接口中的call()方法,call()方法的返回值与V相同。

    • 由于Callable任务是并行的,我们必须等待它返回的结果。

    • 使用ExecutorService中的submit()方法提交Callable任务,submit()会返回Future<T>对象,可以通过Future中的**get()**方法获取返回值。

    • Future

      Future就是对于具体的Runnable或者Callable任务的执行结果进行取消、查询是否完成、获取结果。必要时可以通过get方法获取执行结果,该方法会阻塞直到任务返回结果。

      Future相当于拿到一个应用,这个引用在线程没有执行完时里面没有内容,但是获取它不报错,因此你可以持有它,一旦有线程执行完后就有数据了。

      FutureTask是Future接口的一个唯一实现类。

    • Future对象为我们解决了Callable任务并行的问题。 调用get()方法时,如果实现了Callable接口的线程没有结束则会对当前线程造成堵塞,直至可以获取到返回值为止。可以通过Future中的**isDone()**判断线程有没有结束。

    class MyCallable implements Callable<Integer>{
        @Override
        public Integer call() throws Exception {
            return null;
        }
    }
    public class Main {
        public static void main(String[] args) 
            throws ExecutionException, InterruptedException {
            ExecutorService exec = Executors.newCachedThreadPool();
            Future<Integer> result = exec.submit(new MyCallable());
            while (true) {
                if (result.isDone()){
                    System.out.println(result.get());//get()会抛出异常
                    break;
                }
            }
            exec.shutdown();
        }
    }
    
  5. 休眠

    • TimeUnit.SECONDS.sleep()是对Thread.sleep方法的包装,实现是一样的,只是多了时间单位转换和验证,然而TimeUnit枚举成员的方法却提供更好的可读性。如果忽略性能方面考虑,从可读性方面建议使用TimeUnit枚举成员的sleep方法。

      //TimeUnit中的sleep源码
      public void sleep(long timeout) throws InterruptedException {
          if (timeout > 0) {
              long ms = toMillis(timeout);
              int ns = excessNanos(timeout, ms);
              Thread.sleep(ms, ns);
          }
      }
      

      TimeUnit也是枚举实现一个很好的实例,Doug Lea太神了,佩服佩服!

  6. 优先级

    通过Thread中的setPriority()为线程设置优先级,优先级的级别常用的有三种:Thread.MAX_PRIORITY、Thread.NORM_PRIORITY)、Thread.MIN_PRIORITY;

  7. 让步

    通过静态方法Thread.yield(),发送一个建议给线程调度器(线程调度器可能不会理睬,这取决于具体平台和JVM),告诉它当前线程重要部分已执行完毕,可以执行其他线程了。

    class MyTest implements Runnable{    
        @Override    
        public void run() {             
            while(true){            
                //重要事务            
                Thread.yield();        
            }        
            System.out.println("end task"+id);    
        }
    }
    
  8. 后台进程守护线程

    通过Thread中的setDaemon(true)方法将线程设置为后台线程;

    后台进程是在后台执行并且不会阻止JVM终止的线程。当没有用户线程在运行的时候,JVM关闭程序并且退出。

    由后台线程创建的线程默认也是后台线程;

  9. ThreadFactory

    通过实现ThreadFactory中的newThread()方法在线程执行前做一些通用工作(如优先级,后台进程等),ThreadFactory通常配合ExecutorService一起使用,Executors中的每一个获取ExecutorService的静态方法都重载了一个带ThreadFactory参数的方法。

    class MyThreadFactory implements ThreadFactory{
        @Override
        public Thread newThread(Runnable r) {
            Thread t = new Thread(r);
            //通用工作如:t.setPriority();
            return t;
        }
    }
    public class TestUncaughtException {
        public static void main(String[] args) 
            throws ExecutionException, InterruptedException, IOException {
    
            ExecutorService exec = Executors.newCachedThreadPool(
                new MyThreadFactory());
    		......
        }
    }
    
  10. 编码的变体

  11. 加入一个线程

    使用Thread中的join()方法,如果某个线程a在另一个线程t上调用t.join(),a线程将被挂起,直至t线程结束。

  12. 线程组:一次不成功的尝试,最好忽略它——Joseph Stiglitz

  13. 捕获异常

    线程中逃逸的异常会直接传播到控制台,不能通过常规的方式捕获他。

    捕获线程中异常可以通过:(1)实现Thread.UncaughtExceptionHandler接口,并在线程开始前通过Thread中的setUncaughtExceptionHandler()为该线程添加未捕获异常处理器;(2)通过静态方法Thread.**setDefaultUncaughtExceptionHandler()**为所有线程添加异常处理器,这种处理器只有在不存在线程专有的未捕获异常处理器的情况下才会被调用。

    class MyUncaughtExceptionHandler 
        implements Thread.UncaughtExceptionHandler{    
        @Override    
        public void uncaughtException(Thread t, Throwable e) {
            
        }
    }
    public class Main {
        public static void main(String[] args) 
            throws ExecutionException, InterruptedException, IOException {
    		Thread.setDefaultUncaughtExceptionHandler(
                							new MyUncaughtExceptionHandler());
            Thread t = new Thread(r);
            t.setUncaughtExceptionHandler(new MyUncaughtExceptionHandler());
           
        }
    }
    

3. 共享受限资源

  1. 不正确的访问资源

  2. 解决共享资源竞争

    • 使用synchronized关键字

      当在方法声明中使用synchronized修饰时,所有对象都自动含有单一的锁,因此当调用该对象中被synchronized修饰的方法时,此对象会被加锁。对于某个特定对象来说,其所有synchronized方法共享同一个锁。

    • 使用显式的Lock对象

      Lock接口比同步方法和同步块提供了更具扩展性的锁操作。他们允许更灵活的结构,可以具有完全不同的性质,并且可以支持多个相关类的条件对象。

      它的优势有:可以使锁更公平;可以使线程在等待锁的时候响应中断;可以让线程尝试获取锁,并在无法获取锁的时候立即返回或者等待一段时间;可以在不同的范围,以不同的顺序获取和释放锁。

    • **对象锁:**对象锁也叫实例锁,对应synchronized关键字,当多个线程访问多个实例时,它们互不干扰,每个对象都拥有自己的锁,如果是单例模式下,那么就是变成和类锁一样的功能。对象锁防止在同一个时刻多个线程访问同一个对象的synchronized块。如果不是同一个对象就没有限制。

      private synchronized void method(){
      
      }
      
    • 类锁: 类锁对应的关键字是static sychronized,是一个全局锁,无论多少个对象否共享同一个锁(也可以锁定在该类的class上或者是classloader对象上),同样是保障同一个时刻多个线程同时访问同一个synchronized块,当一个线程在访问时,其他的线程等待。

      private static synchronized void method(){
      
      }
      
  3. 原子性与易变性

    • 原子操作是指一个不受其他操作影响的操作任务单元。

    • 不要试图使用原子性来替代同步

    • volatile的作用当使用volatile关键字去修饰变量的时候,所有线程都会直接读取该变量并且不缓存它。这就确保了线程读取到的变量是同内存中是一致的。

  4. 原子类

    • 原子性变量类:AtomicInteger、AtomicLong

    • Timer计时器类

      new Timer().schedule(new TimerTask() {
                  @Override
                  public void run() {
                      
                  }
              }, 6000);
      
  5. 临界区(同步控制块)

    临界区是为了防止多个线程同时访问方法内部的部分代码而不是整个方法而分离出来的代码段,也被称为同步控制块

    使用synchronized关键字指定某个对象,并使用此对象的锁对花括号内的代码进行同步。

    synchronized (syncObject){//对象锁
    
    }
    synchronized (syncObject.class){//类锁
    
    }
    
  6. 在其他对象上同步

    synchronized块必须给定一个对象,当对不同的对象使用synchronized块时,各个synchronized块互相不受影响。

  7. 线程本地存储

    防止任务在共享资源上产生冲突的第二种方式是根除对变量的共享。线程本地存储是一种自动化机制,可以为使用相同变量的每个不同线程都创建不同的存储。

    Java中创建和管理线程本地存储可以使用TreadLocal类来实现

4. 终结任务

  • 线程状态

    1)新建(new)

    2)就绪(Runnable)

    3)阻塞(Blocked)

    4)死亡(Dead)

  1. 中断的原理

    Java中断机制是一种协作机制,也就是说通过中断并不能直接终止另一个线程,而需要被中断的线程自己处理中断。

    Java中断模型也是这么简单,每个线程对象里都有一个boolean类型的标识(不一定就要是Thread类的字段,实际上也的确不是,这几个方法最终都是通过native方法来完成的),代表着是否有中断请求(该请求可以来自所有线程,包括被中断的线程本身)。例如,当线程t1想中断线程t2,只需要在线程t1中将线程t2对象的中断标识置为true,然后线程2可以选择在合适的时候处理该中断请求,甚至可以不理会该请求,就像这个线程没有被中断一样。

  2. 中断的响应

    当在线程上调用interrupt()时,中断发生的唯一时刻是在任务要进入到阻塞状态中,或者已经在阻塞状态中。

    程序里发现中断后该怎么响应?这就得视实际情况而定了。有些程序可能一检测到中断就立马将线程终止,有些可能是退出当前执行的任务,继续执行下一个任务……作为一种协作机制,这要与中断方协商好,当调用interrupt会发生些什么都是事先知道的,如做一些事务回滚操作,一些清理工作,一些补偿操作等。若不确定调用某个线程的interrupt后该线程会做出什么样的响应,那就不应当中断该线程。

  3. 中断的使用

    在Java中中断线程的方式有两种:(1)使用Thread中的interrupt()方法;(2)使用Executor中的**shutdownNow()方法或Future中的cancel()**方法。

    • interrupt()

      如果有一个线程已经被阻塞,或者试图执行一个阻塞操作,那么设置这个线程的中断状态将会抛出InterruptedException异常。

      当抛出异常或任务调用Thread.interrupted()方法时,中断状态将会复位。

    • shutdownNow()/cancel()

      为了使用interrupt()方法,必须先要持有Thread对象。但新的concurrent类库(似乎)避免对Thread的操作。在新的类库中如果想要中断线程,可以调用Executor中的**shutdownNow()**方法,该方法会向它启动的所有线程发送interrupt()消息。

      由于**shutdownNow()方法是对所有线程中断,而实际使用当中可能会中断单个线程,这时可以调用submit()方法来启动任务,而不是execute()方法。submit()方法返回一个Future对象,可以调用其上的cancel()**方法来中断任务。使用cancel()方法时需要传递一个true来获得在该线程上发送interrupt()消息的权限。

    • 不同的阻塞情况

      sleep()方法引起的阻塞可以中断。

      I/O和synchronized引起的阻塞不可以被中断。

      ReentrantLock上的阻塞任务可以被中断。

  4. 检查中断

    检查中断的典型惯用法

    public void run() {
        try {
            while (!Thread.interrupted()) {
                ...
                TimeUnit.SECONDS.sleep(1);
                ...
            }
        } catch (InterruptedException e) {
            ...
        }
    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
1 目标检测的定义 目标检测(Object Detection)的任务是找出图像中所有感兴趣的目标(物体),确定它们的类别和位置,是计算机视觉领域的核心问题之一。由于各类物体有不同的外观、形状和姿态,加上成像时光照、遮挡等因素的干扰,目标检测一直是计算机视觉领域最具有挑战性的问题。 目标检测任务可分为两个关键的子任务,目标定位和目标分类。首先检测图像中目标的位置(目标定位),然后给出每个目标的具体类别(目标分类)。输出结果是一个边界框(称为Bounding-box,一般形式为(x1,y1,x2,y2),表示框的左上角坐标和右下角坐标),一个置信度分数(Confidence Score),表示边界框中是否包含检测对象的概率和各个类别的概率(首先得到类别概率,经过Softmax可得到类别标签)。 1.1 Two stage方法 目前主流的基于深度学习的目标检测算法主要分为两类:Two stage和One stage。Two stage方法将目标检测过程分为两个阶段。第一个阶段是 Region Proposal 生成阶段,主要用于生成潜在的目标候选框(Bounding-box proposals)。这个阶段通常使用卷积神经网络(CNN)从输入图像中提取特征,然后通过一些技巧(如选择性搜索)来生成候选框。第二个阶段是分类和位置精修阶段,将第一个阶段生成的候选框输入到另一个 CNN 中进行分类,并根据分类结果对候选框的位置进行微调。Two stage 方法的优点是准确度较高,缺点是速度相对较慢。 常见Tow stage目标检测算法有:R-CNN系列、SPPNet等。 1.2 One stage方法 One stage方法直接利用模型提取特征值,并利用这些特征值进行目标的分类和定位,不需要生成Region Proposal。这种方法的优点是速度快,因为省略了Region Proposal生成的过程。One stage方法的缺点是准确度相对较低,因为它没有对潜在的目标进行预先筛选。 常见的One stage目标检测算法有:YOLO系列、SSD系列和RetinaNet等。 2 常见名词解释 2.1 NMS(Non-Maximum Suppression) 目标检测模型一般会给出目标的多个预测边界框,对成百上千的预测边界框都进行调整肯定是不可行的,需要对这些结果先进行一个大体的挑选。NMS称为非极大值抑制,作用是从众多预测边界框中挑选出最具代表性的结果,这样可以加快算法效率,其主要流程如下: 设定一个置信度分数阈值,将置信度分数小于阈值的直接过滤掉 将剩下框的置信度分数从大到小排序,选中值最大的框 遍历其余的框,如果和当前框的重叠面积(IOU)大于设定的阈值(一般为0.7),就将框删除(超过设定阈值,认为两个框的里面的物体属于同一个类别) 从未处理的框中继续选一个置信度分数最大的,重复上述过程,直至所有框处理完毕 2.2 IoU(Intersection over Union) 定义了两个边界框的重叠度,当预测边界框和真实边界框差异很小时,或重叠度很大时,表示模型产生的预测边界框很准确。边界框A、B的IOU计算公式为: 2.3 mAP(mean Average Precision) mAP即均值平均精度,是评估目标检测模型效果的最重要指标,这个值介于0到1之间,且越大越好。mAP是AP(Average Precision)的平均值,那么首先需要了解AP的概念。想要了解AP的概念,还要首先了解目标检测中Precision和Recall的概念。 首先我们设置置信度阈值(Confidence Threshold)和IoU阈值(一般设置为0.5,也会衡量0.75以及0.9的mAP值): 当一个预测边界框被认为是True Positive(TP)时,需要同时满足下面三个条件: Confidence Score > Confidence Threshold 预测类别匹配真实值(Ground truth)的类别 预测边界框的IoU大于设定的IoU阈值 不满足条件2或条件3,则认为是False Positive(FP)。当对应同一个真值有多个预测结果时,只有最高置信度分数的预测结果被认为是True Positive,其余被认为是False Positive。 Precision和Recall的概念如下图所示: Precision表示TP与预测边界框数量的比值 Recall表示TP与真实边界框数量的比值 改变不同的置信度阈值,可以获得多组Precision和Recall,Recall放X轴,Precision放Y轴,可以画出一个Precision-Recall曲线,简称P-R
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值