读Java并发编程实践记录_原子性_锁_同步容器详解_任务执行

原子性: 单独的,不可分割的操作

  • 不要使用过期状态值来决策当下的状态, 一定要先检查再执行(不检查, 将引发数据修改,丢失)
  • 避免延迟初始化(懒加载: 先查看对象 == null, 然后new), 有可能查看对象状态的时候, 对象已经new出来, 只不过还没将对象赋给引用
  • 避免复合操作, 例: count++; 非原子性操作, 包含读-改-写三个过程, 不加锁, 必然出错; 强行加锁, 使得整个复合操作变为原子性
  • 使用atomic包下类(线程安全类), 读写操作都是加锁状态
  • 修饰代码块:大括号括起来,作用于对象
  • 修饰方法:方法名前使用, 作用于对象
    在方法内部使用synchronized代码块,与修饰方法时效果是一致的
  • 修饰静态方法:整个静态方法,作用于所有对象
  • 修饰类:类名前添加,作用于所有对象
    注:
    1. 作用于对象时,不同调用对象之间不影响
    2. 使用synchronized的父类(synchronized不属于方法声明的一部分),子类继承后需要对其方法重新添加synchronized修饰,不然不能使用同步
  • 特点:
    synchronized:不可中断锁,适用于竞争不激烈,可读性好
    Lock:可中断锁,多样化同步,竞争激烈时能维持常态
    Atomic:竞争激烈时能维持常态,比Lock性能好

  1. 在线程中使用atomic包下类, 不一定是线程安全的, 例:
		AtomicInteger i1 = new AtomicInteger(1);AtomicInteger i2 = new AtomicInteger(1);if(i1.incrementAndGet() > 1){//输出简写sout(i2.get());}
	看似i1, i2各自是线程安全的, 但是他们合起来的操作就不再是原子性的, 多个线程操作必然出错.
  1. synchronized可以锁住对象引用, 也可锁对象本身, 从Thread, Runnable实现线程就可知
/*** @author regotto* 测试线程锁住公共资源*/public class Demo2 {public static void main(String[] args) {StringBuffer sb = new StringBuffer();new People("张三", sb).start();new People("李四", sb).start();}}class People extends Thread{private StringBuffer sb;public People(){}public People(String name, StringBuffer sb){this.setName(name);this.sb = sb;}@Overridepublic void run() {final int c = 10;/*这里的线程都是在extends Thread的基础上这里不能使用synchronized(this)(在函数名前加synchronized效果一样),由于前面新建两个线程,对应的this都是不同的,获取的不是同一把锁,当使用同一个Thread就可使用synchronized(this)当使用synchronized(sb)就是正确的,此时锁住的是sb,两个线程都拿着同一个sb,所以可以正确锁住,*///        synchronized (this){//            for (int i = 0; i < c; i++) {//                sb.append(this.getName()).append(" ");//                System.out.println("我是:"+this.getName()+" sb:"+sb);//                try {//                    Thread.sleep(200);//                } catch (InterruptedException e) {//                    e.printStackTrace();//                }//            }//        }synchronized(sb){for (int i = 0; i < c; i++) {sb.append(this.getName()).append(" ");System.out.println("我是:"+this.getName()+" sb:"+sb);try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}}}}}
  1. 重入锁(ReentrancyLock): 当前线程可以多次获得同一把锁, 例如:lock();lock();此时线程获得2把锁(线程内部具有锁计数器)

  2. long/double在JVM中会被拆分为两个32位读取, 多线程下, 将会导致读取到一个凭空而来的值(而不是过期或者正确的数值)

  3. volatile不具有原子性, 他不能使count++具有原子性, 他只具有可见性, 线程中共享变量使用它

  4. 使用volatile的标准:
    1. 确保只有单一的线程修改变量的值
    2. 变量不需要与其他状态变量共同参与不变约束
    3. 访问变量, 没有其他原因要加锁

  5. ThreadLocal, 线程本地变量, JDBC中使用ThreadLocal存储Connection, 使得每个线程都能从连接池中获得自己的Connection

  6. 尝试将单线程中的内容迁移到多线程中, 尝试将共享变量转化到ThreadLocal中

  7. 不可变对象天生就就是线程安全的, 其状态永远都无改变

  8. 使用不可变对象, 保证操作的原子性, 在1中说到两个原子类的和操作并不一定是原子操作(他们各自的操作是原子操作), 这里我可以提供一个不可变容器, 例如:
    final AtomicInteger i3;
    final AtomicInteger i4;
    //不可变容器
    public init(AtomicInteger i1, AtomicInteger i2){
    final AtomicInteger i3 = i1; final AtomicInteger i4 = i2;
    }
    //执行此操作的时候, 多线程情况下不会出现i3或i4为过期值,解决了原子操作
    public AtomicInteger sum(){ return i3.get()+i4.get();}
    注: 每次执行都开辟内存, 但是相比于synchronized, 这点性能损耗不算什么.

  9. 使用线程安全的容器, 例如Hashtable, synchronizedMap, ConcurrentMap, Vector, BlockingQueue, ConcurrentLinkedQueue存储对象, 多线程下保证了从容器中取出的对象总是线程安全

  10. 不可变对象可以通过任意机制发布, 线程安全容器中的对象必须安全发布, 可变对象必须要安全发布,并且使用锁保护

  11. 共享对象线程安全的有效策略:
    1. 线程限制, ThreadLocal处理
    2. 共享只读, 使用不可变对象/使用线程安全容器存储该对象
    3. 共享线程安全, 在对象内部同步, 使用公共接口进行访问
    4. 被守护对象, 只能通过特定的锁进行访问

  12. 设计线程安全的类:
    1. 确定对象状态由哪些变量组成
    2. 限制状态的不变约束
    3. 制定一个并发管理策略–使用12中策略

  13. 任意所对象都可以用来保护对象状态:
    1. 使用私有对象作为锁对象, 而不是对象内部锁;
    2. 线程使用的对象可以是克隆对象, 避免线程对真正的对象产生修改

  14. 解决1中的问题的时候, 由于两个变量是相互独立, 且之间存在一定的联系, 所以加锁的时候可以使用公共锁进行处理使用一个公共变量作为锁对象, 每当需要处理其中一个变量的时候, 都是通过公共锁防止其他线程修改另一变量, 导致两个变量之间的关系出现变化
    注: 面对这样的问题的时候, 使用volatile已经没有用, volatile不能处理这样的复合操作, 使得整个操作具有原子性

  15. 状态变量是否线程安全取决于当前发布的状态(暴露给用户的状态,可以对公共接口进行加锁, 对当前变量设置final, 可以使用volatile, 可以使用线程安全的容器)

  16. "先检查再运行"将导致严重的线程安全问题, 例如延迟加载; 在add操作的时候,对象还没添加到容器中, 另一线程检查将会发现也可以进行add, 这样将会有多次add, 导致同一个容器存在多份相同的对象

  17. 向一个类中添加一个原子操作, 最有效的选择是组合

同步容器:

  1. 同步容器都是线程安全的, 但是针对同步容器的复合操作不是原子性的, 需要对操作之间存在关系的那个对象/变量加锁, 使得复合操作是原子性的
    例如: 多个线程对Vector执行remove, get将有可能引发数组越界的问题/访问元素不存在, 这时候需要重新对list加锁, 保证只有一个线程对公共资源进行操作
  2. 迭代器/forEach遍历需要加锁, 在遍历过程中, 另一线程很有可能remove某些元素, 导致遍历过程出现修改异常(ConcurrentModificationException)但是迭代过程中, 如果对容器加锁, 将会导致性能极大下降, 还有可能出现死锁, 饥饿风险, 采用的解决方案是复制容器, 这样即使容器被修改了, 但是复制品也不会被修改, 避免遍历过程出现的修改异常, 但是复制也将导致性能损耗;还有一个问题就是, 在程序中很有可能出现隐藏的迭代器, 这也将引起非常隐蔽的线程安全问题
例: for(){sout("AAAAAAB"+i); //这里的字符串连接隐含这是使用迭代器遍历,多线程操作很容易引起修改异常}
  1. ConcurrentHashMap使得不再是只有一个线程能同时访问容器; 内部使用分离锁, 使得可以多个线程执行读, 写操作且不影响性能; 由于并发操作, 使用size, isEmpty被弱化;
    synchronizedMap却是为Map中每个方法同步操作而言, 并发情况下具有性能上的损耗
  2. CopyOnWriteArrayList避免了迭代期间对容器的加锁与复制, 底层存在的是一个不可变的基础数组引用, 对数组的修改总是用复制品去操作, 避免线程读的时候出现修改异常,但是修改频率较高的时候就会出现性能上的下降.
  3. 阻塞队列–生产-消费者模式, 有效避免了负载, 例如: 线程池, BlockingQueue维护的是一个线程队列, 他不维护队列元素储存空间
  4. 双端队列–窃取工作, 当自己队列中任务已经完成, 就窃取其他队列末尾的工作(避免竞争), 相比于传统消费者工作模式, 双端队列具有更强的伸缩性(生产者生产的内容很多, 窃取工作很容易加快消费者的消费行为, 使得每个线程都能高效运行)
  5. 阻塞可中断: 可阻塞方法使用interrupt提前中断, Runnable的interrupt必须进行捕获, extends Thread的可以抛给调用者
  6. Synchronizer: 一个对象, 根据本身状态调节线程控制流; 例如阻塞队列, Semaphore等
  7. 闭锁: 延迟线程的进度直到, 直到终点状态到达, 才允许所有线程往下执行,
    例如:
    1. 资源R已经被初始化, 但R执行的活动都在闭锁中等待
    2. 确保一个服务不会开始, 直到他所依赖的所有服务都已开始
    3. 等待所有活动都做好准备, 然后接着往下执行
    4. CountDownLatch:允许一个或多个线程等待一个事件集的发生, 内部使用countDown计数, 当由线程到达, 执行计数且进行await,当计数器达到某一状态, 就notifyAll
    5. FutureTask ==> 有返回值的Runnable, 使用Future.get获得返回值, 当线程还在运行中还没返回值, 此时get将会阻塞, 直到线程具有返回值, 阻塞到一定状态将会抛出异常
  8. Executor利用FutureTask完成异步操作
  9. ThreadPoolExecutor:
    1. corePoolSize: 核心线程数, 运行的线程数小于corePoolSize时,直接创建新的线程,即使存在空闲线程
    2. maximumPoolSize: 线程最大线程数
    3. workQueue: 阻塞队列,存储等待执行的任务,会对线程池运行过程产生重大的影响
    4. 只有当线程数大于maximumPoolSzie但workQueue还没满,则将线程放入workQueue中, 若workQueue满了, 则选择合适的策略执行后续操作
    5. keepAliveTime: 当其没有任务执行,除核心线程,当超出指定时间,这些线程就销毁
    6. unit: keepAliveTime的时间单位
    7. threadFactory: 线程工程, 用于创建线程
    8. rejectHandler: workQueue中的策略处理
    具有如下策略: 1.抛出异常, 2.用调用者所在的线程执行任务(默认), 3.丢弃阻塞队列中最靠前的任务,执行当前任务,4.直接丢弃当前任务
  10. Executor异步执行任务, 不能正确关闭将会阻止JVM结束
    1. 线程池状态:
      1. running: 可以处理新提交的任务以及阻塞队列中的任务
      2. shutdown: 调用shutdown()进入,只能处理新提交的任务, 但是可以处理workQueue中的剩余任务, 阻塞队列为空, 线程池中线程数为0, 进入tidying
      3. stop:调用shutdownNow(),不能处理任何任务(任务队列中任务全部取消),线程池中线程数为0, 进入tidying
      4. tidying: 线程池处理后续任务
    2. 常用方法
      1. execute(): 提交任务, 交给线程池执行
      2. submit(): 提交任务, 能返回执行结果, execute+Future
      3. shutdown(): 关闭县此次, 等待任务执行完
      4. shutdownNow(): 关闭线程池, 不等待任务执行完
      5. getTaskCount(): 线程池已执行和未执行的任务总数
      6. getCompletedTaskCount(): 已完成的任务总数
      7. getPoolSize(): 线程池当前的线程数量
      8. getActiveCount(): 当前线程池中正在执行的任务数量
      9. newFixedThreadPool: 创建定长的线程池, 每当提交一个任务就创建一个线程, 直到达到最大长度(长度就不再变化)
      10. newCachedThreadPool: 创建的线程超出Max,则灵活收回空闲线程,需要增加时, 灵活添加, 对池的大小不做限制
      11. newSingleThreadExecutor: 创建单线程化executor, 当该线程异常结束, 会立即创建新线程取代, 从而保证任务队列中任务按照规定顺序执行(FIFO, LIFO, 优先级)
      12. newScheduledThreadPool: 创建定长线程池, 可定时周期性任务执行

同步容器详解

  1. AQS
    1. 使用Node实现FIFO队列, 用于构建锁或其他同步装置的基础框架
    2. 利用int类型表示状态, 表示当前锁是哪种类型(轻量级, 重量级, 重入锁, 偏向锁…)
    3. 使用方法是继承,内部使用模板方法
    4. 子类通过继承并通过使用实现它的方法管理其状态(acquire, release方法操作状态)
    5. 可以同时实现排它锁和共享锁模式(独占, 共享)
  2. AQS同步组件:
    1. CountDownLatch: 通过计数判断线程是否需要阻塞
    2. Semaphore: 控制同一时间的并发数目
    3. CyclicBarrier: 与CountDownLatch相似
    4. ReentrantLock
    5. Condition
    6. FutureTask
  3. CountDownLatch:
    线程调用await将会阻塞, 其他线程调用countDown()会使计数器减1, 当计数器值为0时, 因调用await()而等待的线程将由await变为唤醒
    此种情况只会出现一次, 计数器不能被重置, 业务上需要重置, 就使用CyclicBarrier
	例:public static void main(String[] args){Executor exec = new Executors.newCachedThreadPool();final CountDownLatch cdl = new CountDownLatch(200);for(int i = 0; i < 200; i++){final int threadNum = i;exec.execute(()->{try{test(threadNum);}catch(Exception e){e.printStack();}finally{cdl.countDown();//一个线程执行后计数器减1}});}cdl.await();//只有当计数器为0才唤醒所有线程, 否则线程执行此行代码就线程阻塞sysout("finish");exec.shutdown();//等线程池中线程执行完,就关闭线程池}public void static test(int t){Thread.sleep();sysout(t);}

运用场景: 在指定时间内完成指定任务, cdl.await(10, TimeUtit.MILLISECONDS)//10代表大小, 后面代表单位,10毫秒,等待的时间是指从线程开始执行到await的时间.

  1. Semaphore: 提供对有限资源的访问次数, 类似于生产消费者模式
	例:public static void main(String[] args){Executor exec = new Executors.newCachedThreadPool();final Semaphore sp = new Semaphore(3);//执行一次放出3个许可for(int i = 0; i < 200; i++){final int threadNum = i;exec.execute(()->{try{sp.acquire();//获取许可,默认获取一个,执行一次放出3个许可, 此处一次获取1个即表示一次可以同时执行3个线程//如果写成sp.acquire(3)表示一次需要获取3个许可,而构造只给出3个, 因此类似单线程执行, 每次等待3个线程,然后3个都执行完才进行releasetest(threadNum);sp.release();//释放许可,与获取许可一直, 可以单个逐一释放, 也可以一次性释放多个}catch(Exception e){e.printStack();}});}exec.shutdown();//等线程池中线程执行完,就关闭线程池}public void static test(int t){Thread.sleep();sysout(t);}///if(sp.tryAcquire()){test(threadNum);sp.release();}//表示尝试获取许可,如上述代码, 尝试获取3个许可, 当时这三个线程还未执行完, 其他线程无法获取许可, 就不能执行if中的内容, 相当于丢弃其他线程, 可传入参数设定尝试许可数以及等待时间

注: Semaphore并没有真正向线程分配许可, 一个线程得到的许可可能是由另外的一个线程释放, 使用Semaphore实现资源池, 有资源的时候acquire, 资源使用结束后release放回资源池, 一个创建资源池最简单的方式就是使用BlockingQueue

  1. CyclicBarrier:一组线程同时等待, 只有当所有的线程都到达屏障点, 所有线程才能继续往下执行后续代码
	例:private static CyclicBarrier cb = new CyclicBarrier(5);//一次5个线程为一组, 5个执行完再执行后续线程//在声明CyclicBarrier的时候可以指定Runnable//表示线程到达await的时候优先执行Runnable中的内容//例: new CyclicBarrier(5, ()->{sysout("----------");//每一组线程都到await时,先执行Runnable,然后再执行后续代码});public static void main(String[] args){Executor exec = new Executors.newCachedThreadPool();for(int i = 0; i < 200; i++){final int threadNum = i;Thread.sleep(1000);exec.execute(()->{try{test(threadNum);}catch(Exception e){e.printStack();}});}exec.shutdown();//等线程池中线程执行完,就关闭线程池}public void static test(int t) throws Exception{Thread.sleep(1000);try{barrier.await(2000, TimeUnit.MILLISECONDS);//在规定时间等待线程, 超出2秒后就抛出异常} catch(BrokenBarrierException | TimeoutException e) {sysout(e);//不捕获异常就会导致后续代码不执行}sysout(t);}

当线程到达屏障, 调用await, 直到所有的线程都到达, 进行notifyAll
注: 可用做并行迭代算法, 问题分解

  1. ReentrantLock(可重入锁):属于自旋锁, 死循环调用CAS实现锁, 而非等待数据进入内核态加锁
    1. ReentrantLock与synchronized的区别:
      可重入性,二者区别不大,使用所计数器,计算当前锁的数目
      锁的实现, ReentrantLock依赖于API, synchronized依赖于JVM
      性能, 当synchronized引入轻量级,偏向,重量级锁后, 二者性能差不多
    2. ReentrantLock独有功能
      可指定公平锁还是非公平锁
      提供一个Condition类, 可以分组唤醒需要唤醒的线程, 而synchronized的单个唤醒是随机的
      提供中断等待锁的线程机制, lock.lockInterruptibly()实现
例:private final static Lock lock = new ReentrantLock();public static void main(String[] args){ExecutorService es = new Executors.newCachedThreadPool();final CountDownLatch cd = new CountDownLatch(2000);final Semaphore sp = new Semaphore(200);for(int i = 0 ;i < 2000; i++){es.execute(()->{try{sp.acquire();add();sp.release();}catch(Exception e){sysout(e);}});}cd.await();es.shutdown();sysout("finish"+count);//看执行结果是否为2000}private static void add(){lock.lock();try{count++;}finally{lock.unclock();}}
  1. ReentrantReadWriteLock://使用场景不多
    内部两个核心成员: ReentrantReadWriteLock.ReadLock readLock; ReentrantReadWriteLock.WriteLock writeLock;
    在读写未被打扰的情况下才能执行读写锁的操作
	例:ReentrantReadWriteLock lock = new ReentrantReadWriteLock();Lock readLock = lock.readLock();Lock writeLock = lock.writeLock();针对读写操作分别使用readLock和writeLock加锁实现的悲观操作, 当获取writeLock的时候不允许有读操作, 当读的操作频率大于写的操作时就会造成线程饥饿(写操作总会因为读操作而中断)
  1. StampedLock://在读操作较多的场景下具有优势
    使用悲观锁(强行认为读的时候不能有写操作)和乐观锁(认为读写发生的冲突很小)操作
    先使用乐观策略, 当出现错误的时候转为悲观锁
    StampedLock sl = new StampedLock();
    long stm = sl.writeLock();//加锁会有long型返回值
    sl.unclock(stm);//释放锁传入stm参数

  2. Condition://用的很少
    ReentrantLock rl = new ReentrantLock();
    Condition c = rl.newCondition();
    在run()中写如下
    rl.clock();//线程加锁
    c.awatir();//当前线程从AQS队列中移除, 相当于锁的释放, 将该线程加入到AQS中的单向队列中, 等待信号
    c.signalAll()//唤醒所有的等待线程, 也可以单个唤醒
    注: 一定要记住有unclock的存在

  3. FutureTask//很重要, 融合了Thread, Runnable, Future, Callable
    Callable(有返回值,可抛出异常), Runnable
    Future(监视目标线程调用call()情况, 获取返回值, 若call未返回值, 则Future线程阻塞)
    FutureTask实现Runnable, Future, 可以操作Runnable对象也可以获得线程返回值

例:FutureTask<String> futureTask = new FutureTask<String>(new Callable<String>(){@Overridepublic String call() throws Exception {Thread.sleep(5000);return "Done";}});new Thread(futureTask).start();Thread.sleep(1000);String result = futureTask.get();//获取返回值FutureTask的构造方法:FutureTask(Runnable r, V v);v表示返回值类型, 此时的Runnable也可具有返回值; 是将Runnable转化为Callable接口, 然后再调用call方法
  1. Fork/Join
    将一个大的任务拆分多个小任务, 每个小任务放置到不同的双端队列中, 每个线程执行自己队列里面的值, 当自己队列中任务执行完就去其他队列中偷取任务执行, 每次任务的偷取是从队列的首开始, 原本执行该任务的线程取任务是从队列的末端开始
    局限性:
    任务只能使用Fork/Join的同步操作, 不能使用其他的同步操作
    任务不是抛出检查异常, 不能执行IO操作

  2. BlockingQueue
    对满的队列进行put(o),将阻塞; add(o),将抛出异常; offer(o)//将返回false,反之true; offer(o, timeOut, timeunit)//操作在指定时间未执行, 就返回false
    对空的队列进行take(),将阻塞; remove(o)//抛出异常; poll()//返回false,同上; poll(timeout,timenuit)//同上

  3. 生产-消费者模式
    ArrayBlockingQueue//容量有限, 初始化后不能改变, 先进先出
    DelayQueue//内部有序,按照元素过期时间排序
    LinkedBlockingQueue
    PriorityBlockingQueue//允许插入null, 插入对象按照Comparable接口的方式排序, 使用迭代器获取元素的时候并不一定按照优先级获取
    SynchronousQueue//内部仅容纳一个元素

任务执行

  1. 在线程中执行任务:
为任务创建线程, 例:socket,如果只是用单线程处理每个用户的请求,那么性能将会是糟糕的.final ServerSocket socket = new ServerSocket(8080);while(true){Socket connection = socket.accept();new Thread(()->{execute(connection);}).start;}
  1. 根据Socket得出如下结论:
    1. 让执行任务脱离主线程,防止因执行任务出现异常导致主线程error; 让主线程在完成前面的请求之前接收新的请求, 提高响应速度
    2. 并行处理任务, 使得多个请求可以同时得到服务
    3. 执行的任务必须保证线程安全
  2. Executor 线程池(生产者-消费者模式)
    //创建100大小的线程池
    Executor exec = Executor.newFixedThreadPool(100)
  3. 创建周期性,延迟性任务
    Timer存在缺陷, 他只采用唯一线程执行,导致多个任务执行的时候影响其他任务的准确性并且他还没有处理检查异常,线程执行的过程中将出现无法预料的错误, 应考虑ScheduleThreadPoolExecutor
  4. 寻找可强化的并行性
    1. 顺序执行页面渲染, 将导致渲染图片是cpu资源的浪费,如果将问题分散到独立并发执行中, 将会获得更好的cpu性能: 使用一个线程渲染文本, 一个线程渲染图片, 通过Future.get判断当前线程执行的程度
    2. 可携带结果的任务: Callable, Future, Executor使用Runnable, 但是Runnable具有局限性(不能返回值或抛出检查异常), 可以使用PrivilegedAction将任务类型封装成Callable
    3. 并行的局限性, 分配任务的时候, 任务协调上的开销, 不能多于并行性带给生产力的提高; 例如:1中页面渲染,若渲染文字的速度远远大于渲染图片,那么使用多线程将会导致程序复制到增加,代码更加冗余繁杂;
    注: 大量相互独立且同类的任务进行并发处理, 将不同的任务量分配到不同的任务中才能获得性能上的提升
    4. CompletionService: 执行批量操作的线程池, 当使用Executor执行批量操作的时候带来的问题就是:每获得一个结果都需要使用Future.get(),此过程还会出现error,阻塞等问题;
    5. CompletionService执行批量操作(一般使用子类ExecutorCompletionService, 它在构造函数中创建BlockingQueue储存所有任务),将所有任务结果封装到一个QueueingFuture(FutureTask子类)中, 使用队列操作从中获得线程返回的结果;
    多个ExecutorCompletionService可以共享单一的Executor
    6. 为任务设置时限, 避免销毁过多的资源, 使用Future.get(get在计算时间的时候是使用当前时间减去预测时间, 任务最开始执行的时候得到的结果可能是负数, 在concurrent包下,所有时间负数都按0处理,所以不需要担心),当抛出TimeoutException,就可以直接Future.cancel,取消当前任务
		try{//在timeLeft的时间内等待,以NANOSECONDS为单位x = f.get(timeLeft, NANOSECONDS);}catch(Exception e){x = default;//超出时间,抛异常,取消当前任务f.cancel(true);}

未完待续…

上面有错, 还请指出, 如果认为我写的还不错, 还请点个赞, 多多支持一下, O(∩_∩)O~~

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/468648.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

和我一起探索嵌入式

&#xff11;.本文为微信群管理员小磊投稿作品&#xff0c;作者计划编写一系列文章&#xff0c;该篇为第一篇&#xff0c;如果有做STM32的同学这将是一个非常好的系列教程&#xff0c;欢迎关注。我15年刚建立了一个BLE的QQ群&#xff0c;很有幸认识了小磊同学&#xff0c;一个非…

经纬度坐标系转东北天_大地坐标系(WGS-84)、地心地固坐标系(ECEF)与东北天坐标系(ENU)的相互转换C语言代码分享...

//ECEF ---> WGS84//pcg为WGS-84坐标系结构体指针&#xff0c;pcc为ECEF坐标系结构体指针void ECEFToWGS(PWGS pcg, PECEF pcc){double B0, R, N;double B_, L_;double X pcc->x;double Y pcc->y;double Z pcc->z;R sqrt(X * X Y * Y);B0 atan2(Z, R);while …

2.简易的登录页面(表单验证)(HTML+JavaScript+Jquery)

//HTML部分 <!DOCTYPE html><html> <head> <meta charset"UTF-8"> <title>登录页面</title> <link rel"stylesheet" type"text/css" href"css/login.css"/> <s…

Java NIO_I/O基本概念_Java中的缓冲区(Buffer)_通道(Channel)_网络I/O

I/O基本概念 缓冲区基础 缓冲区是I/O的基础, 进程使用read(), write()将数据读出/写入从缓冲区中; 当缓冲区写满, 内核向磁盘发出指令, 将缓冲区中数据写入磁盘中(这一步不需要CPU), 当磁盘控制器将缓冲区装满, 内核将缓冲区数据拷贝到进程中指定的缓冲区; 操作如下图: 当中…

跟一个大佬前辈交流了一下

&#xff11;.最近&#xff0c;跟我们公司的测试总监聊天&#xff0c;我随便问了下他几个问题&#xff0c;他也给出了答案&#xff0c;在这里随便聊下&#xff0c;希望给大家的职业生涯中有一些借鉴的作用。也能给新入职场的同学一些方向和指引。2.先介绍下这个技术总监&#x…

python获取目录树_Python读取文件目录树——os.walk

os.walk是Python的内置函数用来遍历文件目录树。[python]import osrootDir d:\assafor dirName, subdirList, fileList in os.walk(rootDir):print(Folder: %s % dirName)for fname in fileList:print(t%s % fname)import osrootDir d:\assafor dirName, subdirList, fileLis…

LINQ简记(1):基本语法

关于LINQ&#xff08;语言集成查询&#xff09;是.NET 3.5和Visual Studio 2008以上版本中引入的一种有趣的全新概念&#xff0c;语言版本有VB和C#&#xff0c;由于C#与.NET平台结合最为紧密&#xff0c;也是MS当初首推的语言&#xff0c;因此&#xff0c;本系列文章的示例代码…

我认识的一位前辈~

&#xff11;.我最近认识了一个老前辈&#xff0c;关注了我的公众号加了我的好友认识的&#xff0c;我想介绍一下这位前辈&#xff0c;不是因为他有多成功&#xff0c;也不是因为他给了我很多钱&#xff0c;我觉得他是一个在平常不过的人了&#xff0c;因为太过于平凡的思考方式…

软件测试缺陷等级划分_缺陷等级的各种划分方法 - 测试新客~~小懒~~ - 51Testing软件测试网 51Testing软件测试网-软件测试人的精神家园...

《缺陷等级标准》缺陷严重级别定义:o 最高级--导致运行中断(应用程序崩溃),预期的功能没有得到实现,测试工作无法继续进行等.o 紧急---事件非常重要,并且需要马上给予关注.o 高级---事件是重要的,并且应该在紧急的事件处理之后尽快得到解决.o 中级---事件是重要的,但是由于解决…

机器学习_决策树_ID3算法_C4.5算法_CART算法及各个算法Python实现

下面的有些叙述基于我个人理解, 可能与专业书籍描述不同, 但是最终都是表达同一个意思, 如果有不同意见的小伙伴, 请在评论区留言, 我不胜感激. 参考: 周志华-机器学习 https://blog.csdn.net/xiaohukun/article/details/78112917 https://blog.csdn.net/fuqiuai/article/d…

http数据绑定spring mvc详解

转载于:https://www.cnblogs.com/panxuejun/p/6834365.html

ESP32 入门教学,不入门,不教学

&#xff11;.Internet of things &#xff08;iot&#xff09;这个概念非常火&#xff0c;物联网是什么&#xff1f;把所有物品通过射频识别等信息传感设备与互联网连接起来&#xff0c;实现智能化识别和管理。 物联网通过智能感知、识别技术与普适计算、泛在网络的融合应用&a…

opencv检测相交点_OpenCV:曲线的检测与提取-0

//寻找曲线 &#xff1a;void findLines(Mat &binaryImg, vector> &outLines){//八邻域vector neighborPtVec;neighborPtVec.push_back(Point(-1,-1));neighborPtVec.push_back(Point(0,-1));neighborPtVec.push_back(Point(1,-1));neighborPtVec.push_back(Point(1…

一个从华为离职的朋友

1、我在之前的很多文章里面都谈到了我有一个过硬的华为朋友&#xff0c;我很少去炫耀自己有多厉害&#xff0c;认识了谁谁&#xff0c;但是我非常在意那些跟自己有过交情的朋友&#xff0c;这些朋友不是说你离开了就失去了&#xff0c;也不是你落魄了就不能吹水了&#xff0c;今…

(原)PyTorch中使用指定的GPU

转载请注明出处&#xff1a; http://www.cnblogs.com/darkknightzh/p/6836568.html PyTorch默认使用从0开始的GPU&#xff0c;如果GPU0正在运行程序&#xff0c;需要指定其他GPU。 有如下两种方法来指定需要使用的GPU。 1. 类似tensorflow指定GPU的方式&#xff0c;使用CUDA_VI…

机器学习_简单线性回归与多元回归方程原理推导_处理二值数据_最小二乘法解或梯度下降解多元回归方程(详细推导)以及Python代码实现_回归方程度量方式

下面的有些叙述基于我个人理解, 可能与专业书籍描述不同, 但是最终都是表达同一个意思, 如果有不同意见的小伙伴, 请在评论区留言, 我不胜感激. 参考: 周志华-机器学习 最小二乘法求解多元回归方程: https://blog.csdn.net/weixin_39445556/article/details/83543945 梯度下…

python逆序数的程序_计算逆序数(归并法)程序问题 (Python)

计算一个tuple里面的逆序数&#xff0c;用merge sort的办法。我写了以下代码&#xff0c;但是每次统计的时候&#xff0c;count设置为全局变量了&#xff1a;Count inversionInput: a sequence as tuple of integersOutput: The inversion number as an integer#Merge Sort Met…

手写Java线程池_超详细解说_绝对能运行_代码超详细注释

线程池 问题背景 只是单纯使用 new Thread(runnable).start(); 的方式创建线程, 将会导致严重的程序性能问题: 1.线程创建, 销毁需要消耗很大的系统资源; 2.虚拟机创建线程的数量是有限的; 2.线程调度切换也将使程序性能下降; 针对这些问题, 对线程数量进行管理, 有效地重复利…

分享一个非常 nice 的工具

最近有个问题&#xff0c;我需要经常使用远程连接工具&#xff0c;原因很简单&#xff0c;我需要控制另外一台电脑&#xff0c;我刚开始使用的是 teamviewer 这个软件&#xff0c;刚开始用的时间是非常爽的&#xff0c;不过有一天他给我来了个提示&#xff0c;说我的软件被商用…

Java并发性和多线程介绍目录

http://ifeve.com/java-concurrency-thread-directory/转载于:https://www.cnblogs.com/hanfeihanfei/p/6840359.html