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