• 极*Java速成教程


    Java高级特性

    注解

    注解可以在代码之外添加更多的信息,更加完整地描述程序,帮助编译器进行工作,或者实现某些特定的Java代码之外的功能。
    注解可以简化某些重复的流程,自动化那些过程。

    注解的使用

    注解的使用与其他修饰符的使用没有区别。Java提供了三种标准注解:

    • @Override
      表明该方法将要覆盖父类的方法,当方法签名与被覆盖方法不同时,编译器就会报错。
    • @Deprecated
      如果开发者使用了被注解的元素,那么编译器就会发出警告。
    • @SuppressWarnings
      关闭不当的编译器警告信息。

    注解的定义

    在使用注解前,需要对注解进行定义。注解不支持继承,注解的定义像一个空的接口,使用@interface的修饰,定义注解时,上方需要一些元注解。
    元注解有四种,用于注解其他的注解:

    • @Target 用于定义你的注解应用于什么地方,比如一个方法或者一个域
      • CONSTRUCTOR 构造器的声明
      • FIELD 域声明
      • LOCAL_VARIABLE 局部变量声明
      • METHOD 方法声明
      • PACKAGE 包声明
      • PARAMETER 参数声明
      • TYPE 类、接口(包括注解类型)或enum声明
    • @Retention 用来定义注解在哪一个级别可以用,如源代码中、类文件中或运行时。
      • SOURCE 注解将被编译器丢弃
      • CLASS 注解在class文件中可用,但会被VM丢弃
      • RUNTIME VM将在运行期也保留注解,因此可以通过反射机制读取注解的信息
    • @Documented 将此注解包含在Javadoc中
    • @Inherited 允许子类继承父类中的注解

    注解中可以包含一些元素以表示某些值,如果没有元素,那么这种注解叫标记注解。注解的元素可以有默认值。

    注解的定义和使用如下:

    import java.lang.annotation.*;
    
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface UseCase {
      public int id();
      public String description() default "no description";
    }
    
    import java.util.*;
    
    public class PasswordUtils {
      @UseCase(id = 47, description =
      "Passwords must contain at least one numeric")
      public boolean validatePassword(String password) {
        return (password.matches("\w*\d\w*"));
      }
    }
    

    在设置了注解后,还需要创建注解处理器,对注解进行处理。可以使用类的getDeclaredMethods()获取本类除继承以外的所有方法,getAnnotation()方法获取指定类型的方法注解。

    注解元素

    注解中可以使用注解元素来记录注解信息。
    注解中可以使用如下类型:

    • 所有基本类型
    • String
    • Class
    • enum
    • Annotation
    • 以上类型的数组

    元素必须有默认值,否则就必须在使用注解时提供值。对于非基本类型元素,不能以null值为其值(但是空字符串""和特定含义的负值是允许的)

    Java的apt工具

    在Java中,可以使用apt工具调用注解处理器,使其进行针对源代码的注解处理。必须提供一个工厂类或者工厂类的路径。
    但在Java7以上,推荐使用javax.annotation.processing工具进行注解处理器的开发。相关内容参考注解处理器详解

    并发

    并行的方法

    使用runnable接口

    线程可以驱动任务,任务可以通过实现Runnable接口并编写run()方法来描述。
    将Runnable对象提交给Thread构造器,可以创建一个线程来驱动任务。然后使用Thread类的start()方法,就可以启动任务。

    通过java.util.concurrent包中的Executor类,可以对Thread对象进行管理。
    通过newCachedThreadPool()方法创建线程池,然后对ExecutorService对象调用execute()方法注册任务,即可进行并行处理。调用shutdown()方法即可停止接受新任务。
    除了CachedThreadPool,还有FixedThreadPool等线程池可以使用,FixedThreadPool可以一次性预先执行代价高昂的线程分配任务,同时对线程数量也可以进行限制。SingleThreadExecutor同时只会运行一个线程,这个执行器会序列化提交的任务,同时维护一个任务队列,依次执行。
    在所有线程池中,在现有线程可能的情况下,都会被自动复用。

    使用Callable接口

    通过实现Callable接口,类也可以并发执行,通过call()方法可以在任务完成后返回一个返回值。这种方法必须使用ExecutorService对象的submit()方法提交任务。
    submit()方法会产生Future对象,该对象可以用isDone()方法查询是否已经完成,当任务完成时,可以使用get()方法获取返回结果。get()方法同时也是一个阻塞方法。

    可以在类的内部,通过内部类的方法继承Thread类或者是实现runnable接口,达到多线程的效果。

    并行的控制

    通过Thread.sleep()方法或者是TimeUnit.MILLISECONDS.sleep()方法,可以使线程休眠。
    在run()方法内(在构造器中设置无效)使用线程对象的setPriority()方法可以设置线程优先级,getPriority()方法可以获取线程优先级,JDK的优先级和操作系统优先级并不对应,可移植的方法是使用Thread.MIN_PRIORITY,Thread.MAX_PRIORITY和Thread.NORM_PRIORITY。
    使用yield()可以暗示在线程调度中可以做出让步(并不保证实现)。
    在线程启动前,使用Thread对象的setDeamon()方法可以设置线程为后台线程。通过实现ThreadFactory接口,可以在接口内的newThread()方法中对创建的Thread进行setDeamon()设置。可以通过isDeamon()查询线程是否为后台线程。
    对线程对象调用join()方法,可以将本线程挂起,直到超时或目标线程t结束才恢复。interrupt()方法会中断join,并且设置中断标志。isInterrupted()方法根据中断标志返回值,然而中断时会抛出异常,异常被捕获时会清理这个标志。

    并行的异常处理

    可以通过在ThreadFactory类中通过setUncaughtExceptionHandler()方法将一个实现了Thread.UncaughtExceptionHandler接口的异常处理器附加到线程上,如果线程抛出异常,那就交由异常处理器进行处理。还可以使用setDefaultUncaughExceptionHandler()方法设置线程的默认异常处理器。

    并行的竞争控制

    可以通过synchronized关键词对共享资源进行修饰,Java即可自动检查资源是否有锁,是否可用。在使用synchronized关键字时,域应该时private的,否则关键字不能保证其他任务不会访问域造成冲突。
    还可以通过synchronized关键词对代码块进行同步控制,语法如下,this可替换为进行同步的对象:

    synchronized(this){
        XXX;
    }
    

    还可以使用java.util.concurrent类库中的locks类显式地加锁。lock类可以通过lock();tryLock()和unlock()方法加锁去锁。ReentrantLock允许尝试获取但是最终没有获取锁,当没有获取到锁时可以离开进行其他处理而不是等待。

    原子性和易变性

    long和double以外的基本数据类型的简单操作具有原子性,对long和double变量加以volatile修饰,可以使其具有赋值和返回操作的原子性。
    volatile保证所修饰的对象是在所有处理器缓存和主存中是同步的,这对于多个线程同时访问一个对象很有意义。
    除非是真正的专家,否则不要依赖原子性。

    使用原子类

    AtomicInteger等相关类等特殊原子性变量类可以保证原子性,但是被设计用来构建java.util.concurrent中的类,因此尽可能不要用,用了也要确保不会出现其他问题,依赖于锁更安全。

    线程本地储存

    使用ThreadLocal类可以为每个线程储存其独一份的对象,因此不会出现竞争。ThreadLocal类常当作静态域储存。

    结束任务

    在进程任务中,有时候不得不突然性地打断任务,可以(但是最好不要)使用Thread的interrupt()或interrupted()方法,也可以使用Executor的shutdownNow()方法关闭执行器上的所有线程任务,或者通过持有任务的Future对象,调用其cancel()方法打断执行器上某一个线程的任务。
    但是I/O任务和synchronized块上的等待不可打断。此时为了中断阻塞的线程,只能通过关闭底层资源来释放锁。使用ReentrantLock锁可以被打断。

    并行任务的协作

    可以通过wait()和notify()/notifyAll()来实现进程间的握手问题。Java还提供了具有await()和signal()方法的Condition对象,通过锁上的Condition对象进行协作通信,这是一种更安全的方式。
    当线程wait()时,将会释放对象的锁。而搭配使用notifyAll()的时候,只有等待特定锁的任务才会被唤醒。
    在进行协作时,必须对一个特定的检查条件来对wait()操作条件进行检查,否则可能因为操作顺序的原因导致死锁的发生。

    while(Something){
        wait()
    }
    

    同步队列

    java.util.concurrent.BlockingQueue接口提供了同步队列,任何时刻都只允许一个任务插入或移除元素,这个接口有LinkedBlockingQueue和ArrayBlockingQueue(固定尺寸)等实现。

    管道

    通过使用PipedWriter(允许任务向管道写)和PipedReader(允许不同任务从管道中读)类,可以通过管道,让多个线程从中进行通信。
    没有数据传输时,管道自动阻塞。管道I/O可以被interrupt中断,这一点不同与系统I/O。

    一些关于并发的支持

    CountDownLatch

    这个类可以设置一个计数,线程每次调用countDown()方法都会使计数-1,而await()方法在计数归零前会持续阻塞。

    CyclicBarrier

    这个类提供一个计数功能,该类的对象进行await()可以使计数-1,当计数归零时,CyclicBarrier会执行一次某一特定任务,然后await()所在的线程被唤醒。

    DelayQueue

    该类可以存放实现了Delayed接口的对象,该对象有一个延迟时间,只有在到期时才能从队列中取走,到期时间越长的排的越前。如果没有到期的,那么队列就没有头元素,poll()将返回空。

    PriorityBlockingQueue

    优先级队列,可以进行阻塞性的读取操作。

    ScheduledExecutor

    ScheduledThreadPoolExecutor类提供了schedule()(在延迟后运行一次任务)和scheduleAtFixedRate()(每隔一段时间重复执行任务)方法。

    Semaphore

    信号量机制,最多可以签发n个许可,允许n个任务同时访问这个资源,当没有资源时会进行阻塞。可以通过acquire()方法获取许可,通过release()方法解锁许可。

    Exchanger

    该类可以对两个线程持有的对象进行互换。在执行exchange()方法之前两个进程是互斥等待的。

    免锁容器

    CopyOnWriteArrayList在写入的时候创建原数组的副本进行写入,写入完成后使用一个原子性的操作将新的数组换入,不影响在写的同时读数据。
    CopyOnWriteArraySet基于CopyOnWriteArrayList实现免锁行为
    ConcurrentHashMap和ConcurrentLinkedQueue使用了类似计数,可以并发读取和写入,但是容器中只有部分内容而不是整个容器能被复制和修改,在修改完成前,读取者依然不能看到它们。

    乐观锁

    针对Aromic对象,可以执行decreamentAndGet()这样的原子方法,但是某些类允许使用compareAndSet()这样的方法,该方法会乐观地认为在操作期间没有其他人修改当前对象,该方法会同时提交新值和旧值,如果旧值不同,那么就需要开发者进行一些相应的处理了。

    ReadWriteLock

    读写锁对不频繁写入但是频繁读取的情况进行了优惠,可以同时拥有多个读取者,只要他们不写入即可,但是如果有人获得了写锁,那么在写锁释放前所有读者都不可以访问这个对象。

  • 相关阅读:
    POJ 1149 PIGS(Dinic最大流)
    HDU 4514 湫湫系列故事——设计风景线(并查集+树形DP)
    [kuangbin带你飞]专题十一 网络流个人题解(L题留坑)
    BZOJ 3931 网络吞吐量(最短路+拆点最大流)
    学习RMQ-ST表
    `这个符号在mysql中的作用
    用一条mysql语句插入多条数据
    Leetcode 257 Binary Tree Paths 二叉树 DFS
    Leetcode 203 Remove Linked List Elements 链表
    Leetcode 38 Count and Say 传说中的递推
  • 原文地址:https://www.cnblogs.com/CoveredWithDust/p/Fast_JAVA_8.html
Copyright © 2020-2023  润新知