renew java

盲猜工作主力语言为java,因此renew一下基础知识

JAVA泛型

(1)泛型类和接口

1
2
3
4
5
public class Stack<E>{
private Arraylist<E> list = new Arraylist<E>();
public E peek(){}; // 类内方法,返回对象为 E
}
// 调用泛型类一般使用 new Stack<String>() 但其构造方法应该是 public Stack()

(2)泛型方法

1
2
3
4
5
6
7
8
9
10
11
public class Main{
public static void main(String[] args){
Integer[] ints = { 1,2,3} ;
Main.<Integer>print(ints) ; //调用泛型方法
}
public static <E> void print(E[] list){ //静态内部泛型方法
for(int i =0 ; i < list.length ; i++)
System.out.print(list[i] +" ");
System.out.println();
}
}

(3)受限类型

1
2
3
4
5
6
7
8
9
10
11
public class Main{
public static void main(String[] args){
Circle A = new Circle(2);
Circle B = new Circle(3);
Main.<Object>euqal(A, B) ; //调用泛型方法
}
public static <E extends Object> boolean(E o1 ,E o2){ //静态内部泛型方法
return o1.getArea() == o2.getArea() ;
}
}
// 有些泛型方法参数必须要是Object的实例,因此用 E extends Object 加以限制

(4)通配泛型

1
?可代表全部  ? extends E 代表E或者E的子类型 ?super E代表E或者E的父类型

(5)泛型的一些限制

1
2
3
4
5
6
7
8
9
1. 不能使用 new E() 不能使用泛型类型参数创建实例
2. 不能使用 new E[] 不能使用泛型类型创建数组
3. 在泛型类的静态环境中不允许类的参数是泛型
静态方法可以用引用类型参数,但是必须是自声明,不能由这个类来声明:
Class A<T1>{
static b<T1 para> // 错误
static <T2> c<T2 para> // 正确
}
4. 异常类不能是泛型的

JAVA多线程

(1)通过实现Runnable接口定义一个任务类

1
2
3
4
5
6
7
8
9
10
11
12
13
class Task implements Runnable{
public Task(){}
public run(){
// how to work
}
}
public class Client{
public void someMethod(){
Task task =new Task();
Thread thread1 = new Thread(task); //开辟一个线程去承接改task
thread1.start();
}
}

(2)通过定义Thread的子类,并实现其run方法来定义线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class WelcomeApp {

public static void main(String[] args) {
// 创建线程
Thread welcomeThread = new WelcomeThread();

// 启动线程
welcomeThread.start();

// 输出“当前线程”的线程名称
System.out.printf("1.Welcome! I'm %s.%n", Thread.currentThread().getName());
}
}

// 定义Thread类的子类
class WelcomeThread extends Thread {

// 在该方法中实现线程的任务处理逻辑
@Override
public void run() {
System.out.printf("2.Welcome! I'm %s.%n", Thread.currentThread().getName());
}
}

(3)synchronized 关键字

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SafeCircularSeqGenerator implements CircularSeqGenerator {
private short sequence = -1;

@Override
public synchronized short nextSequence() {
if (sequence >= 999) {
sequence = 0;
} else {
sequence++;
}
return sequence;
}
}
//synchronized关键字会使其修饰的方法在任一时刻只能被一个线程运行 是内部锁的实现方式
public short nextSequence(){
synchronized (this){
if(sequence>=999)
sequence = 0;
else sequence ++ ;
return sequence;
}
}
/*
synchronized关键字修饰的代码块被称为同步块,锁句柄是一个对象的引用,通常使用 private final修饰
因为锁句柄一旦改变,会导致多线程使用不同的锁,从而导致竞态
线程执行临界区代码时必须获得临界区的引导锁,该锁的获得和申请均由jvm负责,因此称为内部锁
*/

(4)volatile 关键字

1
2
3
4
5
6
7
volatile long value;
//java中long/double型变量是非原子性的,因此要用volatile修饰
private volatile boolean toCancel = false ;
//volatile还能提示JIT编译器该变量被多线程共享,避免做优化;也可以使处理器执行刷新处理器缓存的操作以防止可见性问题
//volatile 实际上通过保证每次读取 变量都从内存,高速缓冲区中读取 来保证其原子性
//volatile 在实现上更轻量级,比锁开销小,但是其功能也少,且对于 复制操作难以保证原子性
//volatile 每次都要从高速缓冲或主内存中读取,无法赞存在寄存器中,从而无法发挥访问的高效性

(5)显示锁

1
2
3
4
5
6
7
8
9
private final Lock lock = new Lock();
lock.lock(); //申请锁 lock
try{
//do something
}finally{
//总是在finally块中释放锁,避免锁泄漏
lock.unlock();// 释放锁
}
// ReentrantLock(boolean fair) fair == true -> 公平锁 一般公平锁开销大适用于锁被持有时间长的情况

(6)tryLock方法

1
2
3
4
5
6
7
8
9
10
11
12
13
Lock lock = ...;
if(lock.tryLock()){
try{
//再次访问共享数据
}finally{
lock.unlock();
}
}
else{
// 执行其他操作
}
// Lock接口定义了一个其他的tryLock方法,可以尝试申请该锁,如果成功返回 True 否则返回 false
// tryLock(long time, TimeUnit unit) 多载方法,可以指定一个时间,如果没在指定时间内申请到对应锁就 false

(7)isLocked() 和 getQueueLength()

1
2
isLocked() 用于检测相应锁是否被某个线程持有
getQueueLength() 用于检查相应锁的等待线程数量

(8)内部锁和显示锁的选用

1
默认使用内部锁,当需要使用显示锁的特性时再使用

(9)读写锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class ReadWriteLockUsage{
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = reLock.writeLock();

public void reader(){
readLock.lock(); // 申请读锁
try{
// 读取共享变量
}finally{
readLock.unlock(); // finally中释放锁,以免锁泄露
}
}

public void writer(){
writeLock.lock(); // 申请读锁
try{
// 写共享变量
}finally{
writeLock.unlock(); // finally中释放锁,以免锁泄露
}
}
}
// 总的来说 ReadWriteLock 已经内部集成了读写锁的控制,通过readLock 和wirteLock 分别设置读写锁,囊括了读写锁之间的PV操作细节

(10)写锁降级为读锁

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
public class ReadWriteLockUsage{
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock readLock = rwLock.readLock();
private final Lock writeLock = reLock.writeLock();

public void operationWithLockDowngrade(){
boolean readLockAcquired = false ;
writeLock.lock(); // 申请读锁
try{
// 写共享变量
// 如果有需要当前的线程完成了写操作 想不与其他线程竞争直接获得读锁那么就可以直接在释放写锁前
// 获得读锁完成降级
readLock.lock();
readLockAcquired =true ;
}finally{
writeLock.unlock(); // finally中释放锁,以免锁泄露
}

if(readLockAcquired){
try{
// 读取共享变量
}finally{
readLock.unlock(); // 释放读锁
}
}else{
//.....
}
}
}

(11)读锁升级为写锁

1
ReentrantReadWriteLock 不支持锁的升级,读线程如果要转而申请写锁,必须先释放读锁然后才能申请写锁

(12)java单例模式

单例(singleton)模式时GOF(Gang of Four)设计模式较容易理解的,运用也较广泛的模式,单例模式所要实现的目标效果非常简单:保持一个类有且仅有一个实例

单线程版单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class SingleThreadedSingleton{
// 保存该类的唯一实例
private static SingleThreadedSingleton instance = null ;
// 省略实例变量声明
/*
* 私有构造器使其他类无法直接通过new 创建该类的实例
*/
private SingleThreadedSingleton(){
// do nothing
}
/**
* 创建并返回该类的唯一实例<BR>
* 即只有该方法被调用时该类的唯一实例才会被创建
* @return
*/
public static SingleThreadedSingleton getInstance(){
if(null == instance){ //操作 ①
instance = new SingleThreadedSingleton();
}
return instance ;
}
public void someService(){
// 省略其他
}
}

但是这样的单线程单例模式在多线程时会出现问题,线程A在执行到操作①时都判定该实例未被创建,但可能此时线程B刚好对其赋值,可线程A仍会进入到if语句中,也就是会继续创建一个实例

通过简单内部锁实现的单例模式
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public class SimpleMultithreadedSingleton{
// 保存该类的唯一实例
private static SimpleMultithreadedSingleton instance = null ;
// 省略实例变量声明
/*
* 私有构造器使其他类无法直接通过new 创建该类的实例
*/
private SimpleMultithreadedSingleton(){
// do nothing
}
/**
* 创建并返回该类的唯一实例<BR>
* 即只有该方法被调用时该类的唯一实例才会被创建
* @return
*/
public static SimpleMultithreadedSingleton getInstance(){
synchronize(SimpleMultithreadedSingleton.class){
if(null == instance){ //操作 ①
instance = new SimpleMultithreadedSingleton();
}
}
return instance ;
}
public void someService(){
// 省略其他
}
}

这种利用Synchronized关键字进行简单的加锁,尽管是线程安全的,但是也意味着每次getInstance()都需要申请锁,于是为了避免锁的开销,有人就想出了先检查instacne是否为null的方法

基于双重检查锁定的错误单例模式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
public class IncorrectDCLSingleton{
// 保存该类的唯一实例
private static IncorrectDCLSingleton instance = null ;
// 省略实例变量声明
/*
* 私有构造器使其他类无法直接通过new 创建该类的实例
*/
private IncorrectDCLSingleton(){
// do nothing
}
/**
* 创建并返回该类的唯一实例<BR>
* 即只有该方法被调用时该类的唯一实例才会被创建
* @return
*/
public static IncorrectDCLSingleton getInstance(){
if(null == instance){ //操作① : 第一次检查
synchronize(IncorrectDCLSingleton.class){
if(null == instance){ //操作 ② :第二次检查
instance = new IncorrectDCLSingleton();
}
}
}
return instance ;
}
public void someService(){
// 省略其他
}
}
// 从可见行角度分析,该检测法似乎既避免了锁的开销又保障了线程安全,但是考虑重排序,这个实例可能
// 会出错,具体原因暂时懵懂,有待深入理解重排序
基于双重检查锁定的正确单例模式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class DCLSingleton{
// 保存该类的唯一实例
private static volatile DCLSingleton instance = null ;
// 省略实例变量声明
/*
* 私有构造器使其他类无法直接通过new 创建该类的实例
*/
private DCLSingleton(){
// do nothing
}
/**
* 创建并返回该类的唯一实例<BR>
* 即只有该方法被调用时该类的唯一实例才会被创建
* @return
*/
public static DCLSingleton getInstance(){
if(null == instance){ //操作① : 第一次检查
synchronize(DCLSingleton.class){
if(null == instance){ //操作 ② :第二次检查
instance = new DCLSingleton();
}
}
}
return instance ;
}
public void someService(){
// 省略其他
}
}
// 利用volatile关键字 一方面保证了可见行,另一方面保证了有序性,让系统禁止对其进行重排序
基于静态内部类的单例模式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class StaticHolderSingleton{
// 私有构造器 防止外部类借此new子类
private StaticHolderSingleton(){
// do nothing
}
private static class InstanceHolder{ // 静态内部类 类内成员为唯一的 单例
final static StaticHolderSingleton instance = new StaticHolderSingleton();
}
public static StaticHolderSingleton getInstance(){ // 获取实例的方法
return InstanceHolder.instance;
}
public void SomeService(){
// do something
}
public static void main(String[] args){
StaticHolderSingleton.getInstance().SomeService();
}
}
// 基于静态内部类实现的单例模式 很巧妙的利用了 java类加载机制的特点
// 类的静态变量被初次访问时会触发java虚拟机对该类进行初始化 当我们调用类中静态方法 getInstance时
// 虚拟机会初始化这个方法访问的静态内部类 InstanceHolder 从而创建一个唯一实例
// 由于类的静态变量只会创建一次 因此StaticHolderSingleton(单例类) 只会被创建一次
基于枚举类型的单例模式实现
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class EnumBasedSingletonExample{
public static void main(String[] args){
Thread t = new Thread(){
@override
public void run(){
Singleton.INSTANCE.someService();
};
};
t.start();
}

public static enum Singleton{
INSTANCE;
Singleton(){
Debug.info("Singleton inited");
}
public void someService(){
Debug.info("someService invoked.");
}
}
}
// 枚举类型Singleton 相当于一个单例类,INSTANCE相当于一个实例,当Singleton.INSTANCE初次被调用时才会初始化

(13)线程设置数的原则

设 Nc表示一个系统的处理器数目,具体的线程数可以根据以下规则:
●对于CPU密集型线程,因为占用CPU资源,因此可以考虑设置为 Nc 个线程数,考虑到有时候会因为一些中断被切出,因此将数量设置为Nc+1
●对于I/O密集型线程,考虑到I/O操作可能导致上下文切换,可以设置为 2Nc个线程数
商用软件往往规定CPU使用率要低于一个阈值,一般为75%,可以用 Nt = Nc
Uc *(1+WT/ST)
Nt为线程数的合理大小 Uc为使用率阈值 WT为程序等待IO的时间 ST时程序占用CPU的时长

线程间协作

(1)等待与通知

● Object.wait()实例
1
2
3
4
5
6
7
8
9
10
//调用wait方法前获得相应对象的内部锁
synchronized(someObject){
while(保护条件不成立){
// 调用Object.wait()暂停当前线程
someObject.wait();
}
//代码执行到这里说明保护条件满足了
//执行目标动作
doAction();
}
● Object.wait()注意点
1
2
3
等待线程对保护条件的判断,Object.wait()调用总是放在相应对象所引导的临界区的循环语句中
等待线程对保护条件的判断,Object.wait()的执行以及目标动作的执行必须放在同一个对象(内部锁)引导的临界区中
Object.wait()暂停当前线程时释放的锁只是与该wait方法所属对象的内部锁,当前线程持有的其他锁不会被释放
● Object.notify()实例
1
2
3
4
5
6
synchronized(someObject){
// 更新等待线程的保护条件涉及的共享变量
updateSharedState();
// 唤醒其他线程
someObject.notify();
}
● Object.notify()注意点
1
2
3
Object.notify()方法调用总是放在相应对象内部锁引导的临界区之中
Object.notify()的执行线程持有的相应对象的内部锁只有在Ojject.notify()调用所在的临界区代码执行结束后才会释放
Object.notify()一般放在整个临界区的末尾部分,不然容易出现Object.notify()之后,临界锁未释放的场景
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
// 单例模式 和 wait/notify 的混合
public class Alarmgent{
private final static Alarmgent INSTANCE = new Alarmgent();
private boolean connectedToServer = false ;
private final HeartbeatThread heartbeatThread = new HeartbeatThread();
private Alarmgent(){
// do nothing
}
public static AlarmAgent getInstance(){
return INSTANCE;
}
public void init(){
connectToServer();
heartbeatThread.setDaemon(true);
heartbearThread.start();
}
private void connectToServer(){
new Thread(){
@override
public void run(){
doConnect();
}
}.start();
}

private void doConnect(){
Tools.randomPause(100);
synchronized(this){
connectedToServer =true ;
notify();
}
}
public void sendAlarm(String message) throws InterruptedException{
synchronized(this){
while(! connectedToServer){
Debug.info("Alarm agent was not connected to server");
wait();
}
doSendAlarm(message);
}
}

private void doSendAlarm(String message){
Debug.info("Alarm sent:%s" , message);
}

class HeartbeatThread extends Thread{
@override
public void run(){
try{
Thread.sleep(1000);
while(true){
if(checkConnection()){
connectedToServer = true ;
}else{
connectedToServer = false ;
Debug.info("Alarm agent was disconnected to Server");
connectToServer();
}
Thread.sleep(2000);
}
}catch(InterruptedException e){

}
}
private boolean checkConnection(){
boolean isConnected = true;
final Random random = new Random();
int rand = random.nextInt(1000);
if(rand<=500){
isConnected = false;
}else isConnected= true;
return isConnected ;
}
}
}