可重入锁

一 说明

java自1.5之后提供了一种新的同步互斥机制——可重入锁.相关的类位于java.util.concurrent.locks包下面.

二 类图

其中最重要的就是Lock和Condition这两个接口,这两个接口描述了一把锁应该具有的功能.Condition的作用一开始可能让人迷惑,不理解java语言设计人员设计这个接口的目的是什么.其实如果把Lock的定位是为了取代或补充synchronized关键,那么Condition接口对标的就是Object类的wait(),notify(),notifyAll()这三个方法.这样理解的话,Condition的作用和用法就非常明确和具体了.

当我第一次接触ReadWriteLock这个接口的时候,往往会被它的名字迷惑,因为这名字让我相信这个接口的肯定是对Lock这个接口的扩展,然后会包含两类方法: 以读的方式加锁的方法和以写的方式加锁的方法,但其实不是.这其实是如何设计读写锁的问题了.我的第一观念的来源可能是受到读写文件相关的接口设计的影响.很多语言的读写文件的接口的设计往往是先打开文件,再读写文件.而在打开文件的方法参数中往往会带一个打开方式的参数(读,写或读写),即在动作里面描述(在这个过程我们只看到访问者和资源).但是java在设计读写锁的时候使用了另一种方式.即把读写锁封装成一个锁,但是两种锁的存在均对外暴露,而两种锁的性质由锁本身自己实现.这样的好处是把锁的状态集中在一起,便于锁的实现,读锁和写锁之间的协作会很容易比如说,读锁和写锁之间可以轻松的了解对方的加锁状态以及加锁的线程数,这两个信息对当前是否可以上锁很有必要.如果分开的话,两个锁之间的协调就是一个很大的问题.

如果像打开文件那样,把锁的方式用参数传递,比如可以这样设计读写锁:

public interface ReadWriteLock extends Lock {
    public void lockRead();
    public void unlockRead();

    …其他方法…

}

这样的话就需要对Lock接口中的每个方法都提供一个读方式的接口方法,这样就显得很臃肿.所以java设计者直接把两种锁暴露出来,让程序员自己先去获取具体的锁,然后在对锁进行操作.从设计角度来看,ReadWriteLock并不是锁,而是两个锁的管理者.

三 可重入的内涵

四 Lock的使用方式

对于一把锁,很容易想到的两个基本功能就是加锁和解锁.lock()和unlock()就是对这两个基本功能的描述.如果再考虑等待锁的时候线程是否可以被中断,就可以再引入lockInterruptibly().如果再要请求锁的时候不阻塞,而是通过返回值来判断锁是否可用,所以又引入了tryLock()方法.

lock(): 请求锁,如果不可用则阻塞当前线程,直到锁可用为止.

lockInterruptibly(): 等待锁的时候该可以被其他线程中断,抛出InterruptedException.

boolean tryLock(): 非阻塞方式请求锁.

boolean tryLock(time, timeUnit): 非阻塞方式请求锁, 但是会等待一段时间,如果时间结束后,锁还不可用的话,就返回.

unlock(): 解锁.

newCondition(): 获得锁上的条件对象.

如何构造锁?

java设计者已经为我们提供了一个Lock的实现类,ReentrantLock, 创建一个ReentrantLock对象即可.

Lock的使用示例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class Data {
	private int flag;
	private Lock lock = new ReentrantLock();
	
	public int getFlag() {
		return flag;
	}

	public void setFlag(int flag) {
		try{
			lock.lock(); //加锁
			System.out.println(Thread.currentThread().getName() + " in.");
			Thread.sleep(3000);
			this.flag = flag;
			System.out.println(Thread.currentThread().getName() + " out.");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			lock.unlock(); //释放锁, 一定要放在finally块中
		}
	}
}
class Runner implements Runnable{
	private Data data;
	private int value;
	public Runner(Data data, int value) {
		this.data = data;
		this.value = value;
	}
	
	@Override
	public void run() {
		data.setFlag(value);
	}
}
public class LockDemo {
	public static void main(String[] args) {
		Data data = new Data();
		Thread t1 = new Thread(new Runner(data, 1), "T1");
		Thread t2 = new Thread(new Runner(data, 2), "T2");
		t1.start();
		t2.start();
	}
}

输出结果:

有一点要注意: 释放锁的操作一定要放在finally块中. 和synchronized同步方法相比,锁虽然更加灵活,但是也带来了风险,用得不好,很可能就会导致死锁发生.

五 ReadWriteLock的使用

与Lock相比ReadWriteLock的read锁的lock()方法可以被多个线程调用而不阻塞(读共享),而write锁的lock()方法则与write锁的lock()和read锁的lock()是互斥的,即有各个操作已经发生,则会导致其他线程阻塞(写互斥)

使用读写锁的基本流程如下:

1)创建读写锁对象,java已经提供了ReentrantReadWriteLock类,可以创建一个这种类的对象.

2)拿到具体的锁对象,如获取读锁: Lock readLock = readwriteLock.readLock(); 获取写锁: Lock writeLock = readwriteLock.writeLock();

3)加锁,readLock.lock()或writeLock.lock()

4)释放锁.


与普通的锁在使用上的区别是: 读写锁在上锁之前要先拿到对应类型的锁.


ReadWriteLock使用示例:

import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

class Disk {
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	private String data = "";
	
	public String read() {
		try {
			System.out.println(Thread.currentThread().getName() + "准备读...");
			lock.readLock().lock();
			System.out.println(Thread.currentThread().getName() + "开始读...");
			Thread.sleep(30000);
			System.out.println(Thread.currentThread().getName() + "完成读...");
			return data;
		} catch(Exception e) {
			e.printStackTrace();
			return null;
		} finally {
			lock.readLock().unlock();
		}
	}
	
	public void write(String data) {
		try {
			System.out.println(Thread.currentThread().getName() + "准备写...");
			lock.writeLock().lock();
			System.out.println(Thread.currentThread().getName() + "开始写...");
			Thread.sleep(50000);
			this.data = data;
			System.out.println(Thread.currentThread().getName() + "写完成...");
		} catch(Exception e) {
			e.printStackTrace();
		} finally {
			lock.writeLock().unlock();
		}
	}
}

class Reader implements Runnable {
	private Disk disk;
	public Reader(Disk disk) {
		this.disk = disk;
	}
	@Override
	public void run() {
		String data = disk.read();
		System.out.println("从磁盘读到数据: " + data);
	}
}

class Writer implements Runnable {
	private Disk disk;
	public Writer(Disk disk) {
		this.disk = disk;
	}
	@Override
	public void run() {
		String data = System.currentTimeMillis() + "";
		disk.write(data);
		System.out.println("成功向磁盘写入数据: " + data);
	}
}

public class ReadWriteLockDemo {
	public static void main(String[] args) {
		Disk disk = new Disk();
		Thread r1 = new Thread(new Reader(disk), "R1");
		Thread r2 = new Thread(new Reader(disk), "R2");
		Thread w1 = new Thread(new Writer(disk), "W1");
		r1.start();
		w1.start();
		r2.start();
	}
}

运行结果:

六 Condition的使用

以后再补充.

Semaphore类

一 说明

java.util.concurrent.Semaphore.

这是java提供的对信号量的支持类.

信号量是用来对某一共享资源所能访问的最大个数进行控制.

二 主要方法

acquire(): 申请一个许可证书,如果没有可用的许可证书的话,就一直等着,在等待过程中可以被中断.

acquire(int permits): 申请permits个许可证书.

acquireUninterruptibly(): 与acquire()方的区别是,线程等待的时候无法被中断.

acquireUninterruptibly(int permits): 申请permits个许可证书.

release(): 释放一个证书.

release(int permits): 释放permits个证书.

boolean tryAcquire(): 尝试获取一个证书.这个方法不会阻塞,而是马上返回,通过返回值可以判断是否获取成功,即这个方法可以看成是acquire()方法的异步版本.

boolean tryAcquire(long timeout, TimeUnit unit): 与tryAcquire(): 尝试获取一个证书,如果没有成功,它会尝试一段时间.

boolean tryAcquire(int permits)

boolean tryAcquire(int permits, long timeout, TimeUnit unit)

isFair(): 唤醒等待线程的策略是否公平的(FIFO).

boolean hasQueuedThreads(): 是否有线程在等待.

int getQueueLength(): 等待线程的数量.

三 重要属性

1) permits: 许可证数量. 包括许可证总数量, 可用的许可证数量.

2) queue: 等待线程队列, 用于存放等待线程.

3) fair: 等待线程的调度策略(是否公平), 是否是FIFO.

四 使用场景

留待以后补充.

java线程池

一 接口与类

先看看线程池相关类之间的关系:

其中,

Executor: 是执行引擎的接口,可以具体化地类比成一台机器,它只有一个方法: void execute(Runnable command). 显然这符合机器的最主要的功能特点, 即: 只需要给它发送指令(任务).

ExecutorService: 是执行引擎管理者(服务)的接口,同理可以具体类比为一个工厂. 这个接口对工厂的主要功能进行了提取和抽象, 提供了机器的启停,状态监控等功能的描述.

ScheduledExecutorService: 这是一种可以可调度的机器管理者(工厂),发送到它上面的指令(任务)可以调度执行,不过它支持的调度模式仅支持周期性执行任务的策略.

ThreadPoolExecutor: 一个工厂为了加快生产过程,内部肯定置办了多台机器,程序中对应的就是线程池.所有的ExecutorService实现类的功能也是直接依赖这个类,这个类的功能特点直接影响java线程池相关的功能,理解这个类的工作模式是理解java线程池的关键.

Excecutors: 普通程序员在使用线程池时,可能更喜欢根据自己这样的模式: 只需要描述自己需要的线程池的特点,就能直接获得线程池.Executors类就是就是出于这样的考虑. 或者说java设计者把常用的线程池的创建工作已经做了,普通程序员只需要选择自己需要的线程池(当然你要清楚自己的功能需求,以及可供选择的几种线程池的特性). 当然如果确实有需要,程序要也可以自己手动创建线程池,java也提供了可供选的基础类,就是前面提到的ThreadPoolExecutor类.

编码开发的时候,最常见的方式都是面向ExecutorService这个接口编程的,所以掌握ExecutorService接口是非常重要的. ExecutorService接口主要的方法如下:

void execute(Runnable command): 这是继承自Executor接口的方法,用于提交任务,如果线程池已经关闭,则会抛出RejectedExecutionException异常.

<T> Future<T> submit(Callable<T> task): 提交实现了的Callable接口的任务.同样,如果线程池已经关闭,则会抛出RejectedExecutionException异常.

void shutdown(): 关闭线程池,但是会等待线程池中所有任务执行完毕,而且不能再向线程中提交新的任务.

List<Runnable> shutdownNow(): 马上关闭线程池.不等所有任务完成,而是强制停止正在运行的任务,并返回等待执行(还没有运行)的任务.

isShutdown(): 线程池是否已经关闭.也即线程池是否已经调用了shutdown()或shutdownNow()等方法.

isTerminated(): 线程池是否已经终止,可以把这时的线程池状态理解为:已经调用了shutdown()或shutdownNow()等方法,而且所有线程都已经运行完毕[对此,还不是很确定,还需要后续检验].

二 各种线程池

前面java设计者已经为们提供了很多现有的线程池,这些线程池已经能满足常规的开发工作,前面也提到我们需要做的就是理解各个线程池的特性,然后根据自己的需求选择适合的线程池.现有的线程池的创建工作封装在Executors类的静态方法中,重要的有下面个(类):

1)newSingleTheadExecutor():  这是最简单的线程池,简单到java设计人员甚至不认为它是一个池,因为它内部只有一个线程,用机器和工厂的类比来说,这更像是一台机器,而不是工厂(包含多台机器).

2)newFixedThreadPool(int count): 固定线程数量的线程池.

3)newCachedThreadPool(): 一种弹性线程池,它基于这样的策略来管理池中的线程: 线程不够的就新建,一段时间内没有使用就回收.它试图在这两个问题之间做好平衡: 创建线程耗资源, 维护线程同样也耗资源.

4)newScheduledThreadPool(int count): 它是为这样的需求提供的: 周期性的执行某个任务.

前面说明了各种线程池的特性,但是该怎么选择呢?

single线程池和schedule线程的使用场景比较明显也比较少用到.经常用到的是fixed线程池和cached线程池.

single线程池

single线程适池用于并发量不大,不需要并发甚至不能并发的场景,而且还有一个重点: single线程可以确保提交给它的任务顺序执行,这在很多需求下很有用.

schedule线程池

这种线程池的使用场景比较明显

fixed线程池和cached线程池

这两个线程池其实都是在线程创建和线程维护之间取舍.根本上看,可以从这三个方面考虑:

1)任务量

2)提交任务的频度

3)单个任务执行的耗时长短

一般来说:

任务量大, 提交任务频度小: 使用cached线程池

任务量小, 单个任务执行的耗时长: 使用fixed线程池

其他场景的化就得具体分析了.

三 ThreadPoolExecutor分析

其实前面提到的各种线程池都是基于ThreadPoolExecutor类.它们的很多特性都依赖ExecutorThreadPool.其中最主要的两个特性就是线程数(fixed线程池)和线程回收(cached线程池).

从ThreadPoolExecutor的构造函数就能很容易理解这一点,ThreadPoolExecutor的构造函数如下:

public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue)

其中参数的含义:

corePoolSize: 初始化的线程池中的线程数

maximumPoolSize: 线程池中最大线程数

keepAliveTime: 线程的生命长短,用于空闲线程回收,0表示线程池中的空闲线程永远维护,不回收

unit: 时间单位

workQueue: 任务队列

显然,maximumPoolSize和keepAliveTime两个参数就能支撑fixed线程池中的线程数和cached线程池的线程回收特性.

四 调度线程池

ScheduledExecutorService用的不多,留待以后补充.

Thread类

一 重要方法

start()

join()

interrupt()

isInterrupted()

static interrupted()

static yeild()

static sleep()

static currentThread()

[已经废弃的方法]:

stop():强制停止线程,不安全

suspend():暂停线程,可能导致死锁

resume():恢复线程,可能导致死锁

二 重要属性

1)id: 唯一且终生不变. 线程终止之后可以再被使用

2)name: 线程名称

3)state: 线程状态

4)daemon: 标识线程是否为守护线程

5)priority: 优先级

6)threadGroup: 线程组

7)interruption: 中断标识

三 线程状态

NEW: 新建状态,即创建Thread对象之后,调用start()方法之前线程所处的状态

BLOCKED: 阻塞状态,当线程试图进入synchronized区域,等待同步锁时的状态

TIMED_WAITING: 时间等待状态,线程调用sleep()方法之后进入该状态

WAITING: 等待状态,线程调用了Object.wait()方法之后进入该状态

TERMINATED: 终止状态,线程的run()方法运行结束之后的状态

RUNNABLE: 可运行状态,或者正在运行的状态

线程运行状态:

import java.util.concurrent.CountDownLatch;
public class ThreadStateDemo {
	public static CountDownLatch latch = new CountDownLatch(1);
	public static void main(String[] args) throws Exception {
		Thread t1 = new Thread(new Runnable() {
			private String name = "T1";
			@Override
			public void run() {
				try {
					System.out.println(name + " before sleep()");
					Thread.sleep(30000);
					System.out.println(name + " after sleep()");
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
		});
		Thread t2 = new Thread(new Runnable(){
			private String name = "T2";
			@Override
			public void run() {
				System.out.println(name + " before syncMethod()");
				syncMethod();
				System.out.println(name + " after syncMethod()");
			}
		});
		Thread t3 = new Thread(new Runnable(){
			private String name = "T3";
			@Override
			public void run() {
				System.out.println(name + " before syncMethod()");
				syncMethod();
				System.out.println(name + " after syncMethod()");
			}
		});
		Thread.State state1 = t1.getState();
		System.out.println("线程状态: " + state1); //NEW
		t1.start();
		t2.start();
		Thread.sleep(2000);
		t3.start();
		Thread.sleep(2000);
		Thread.State state2 = t1.getState();
		System.out.println("线程状态: " + state2); //TIMED_WAITING
		Thread.State state3 = t2.getState();
		System.out.println("线程状态: " + state3); //WAITING
		Thread.State state4 = t3.getState(); //BROKEN
		System.out.println("线程状态: " +state4);
		ThreadStateDemo.latch.countDown(); //唤醒Run
		Thread.sleep(5000);
		Thread.State state5 = t2.getState();
		System.out.println("线程状态: " + state5); //TERMINATED
		Thread.State state6 = Thread.currentThread().getState();
		System.out.println("线程状态: " + state6); //RUNNABLE
	}
	public synchronized static void syncMethod() {
		try {
			ThreadStateDemo.latch.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

四 关于线程中断

interrupt(): 设置线程的中断状态,必须在线程运行(start方法)之后调用才能生效

isInterrrupted(): 获取线程的中断状态

Thread.interrupted(): 清除当前线程的中断状态

说明:

1) 如果某个线程处于TIMED_WAITING或WAITING状态,即已经调用过了sleep()方法或wait()方法,如果再修改线程的中断状态,即调用该线程对象的interrupt()方法,则会从sleep()或wait()方法的位置抛出InterrupedException

2) 如果某个线程的中断状态为true,即已经调用了线程对象的interrupt()方法,当该线程试图进入TIMED_WAITING或WAITING状态,即如果再调用sleep()方法或wait()方法,则会从sleep()或wait()方法的位置抛出InterrupedException

3)为了避免调用sleep()或wait()方法时抛出异常,可以先用isInterrupted()方法检查线程的中断状态,在调用Thread.interruped()方法清除中断状态.

线程中断:

public class ThreadDemo {
	static Object lock = new Object();
	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				//耗时
				for(int i=0; i&lt;Integer.MAX_VALUE; i++) {
					for(int j=0; j&lt;100; j++) {
						long x = 1;
						long y = 2;
						long z = x * y;
					}
				}
				try {
					System.out.println("线程的中断状态: " + Thread.currentThread().isInterrupted());
					//判断中断状态
					if (Thread.currentThread().isInterrupted()) {
						Thread.interrupted();  //清除中断状态
					}
					//调用sleep()或wait()方法
					synchronized (lock) {
						lock.wait();
					}
					Thread.sleep(1000);
				} catch (Exception e) {
					e.printStackTrace();
				}
			}
		});
		try {
			t.start();
			t.interrupt();
			System.out.println("isInterrupted: " + t.isInterrupted());
			Thread.sleep(3000);
			System.out.println("isInterrupted: " + t.isInterrupted());
			synchronized(lock) {
				lock.notify();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}

五 未捕获异常处理器

setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh): 为线程设置未捕获异常的处理器

static setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh): 为所有线程设置未捕获异常的处理器

接口Thread.UncaughtExceptionHandler仅包含一个方法: public void uncaughtException(Thread t, Throwable e)

代码示例:

import java.lang.Thread.UncaughtExceptionHandler;
class UncaughtExceptionHandlerImpl implements UncaughtExceptionHandler {
	private String name;
	public UncaughtExceptionHandlerImpl(String name) {
		this.name = name;
	}
	@Override
	public void uncaughtException(Thread t, Throwable e) {
		System.out.println(name + " - 捕获到未捕获异常");
		System.out.println(name + " - 抛出异常的线程: " + t);
		System.out.println(name + " - 抛出的异常对象: " + e);
	}
}
public class ThreadDemo {
	static Object lock = new Object();
	public static void main(String[] args) {
		Thread t = new Thread(new Runnable() {
			@Override
			public void run() {
				Object o = null;
				o.toString();//拋出异常
			}
		});
		UncaughtExceptionHandler eHandler = new UncaughtExceptionHandlerImpl("异常处理器");
		UncaughtExceptionHandler defaultHandler = new UncaughtExceptionHandlerImpl("默认的异常处理器");
		t.setUncaughtExceptionHandler(eHandler);
		Thread.setDefaultUncaughtExceptionHandler(defaultHandler);
		t.start();
		//main线程中抛出异常
		Object o = null;
		o.toString();
		System.out.println("main方法将要结束退出");
	}
}

运行结果:

作用说明: 未捕获异常处理器可以在线程异常退出时释放资源,日志记录,防止异常直接展示给客户等

volatile

volatile关键字使得变量具有可见性, 可见性变量具有这样的属性: 一个线程对该变量的修改会马上对别的线程可见.语言规范对线程的约束要宽松很多,只要保证线程在退出临界区(同步块)之后,修改的变量的新值对其他线程可见.

拿下面的代码来说,Data中的value成员变量如果不加volatile关键字,那么可能要等到修改value的writer线程退出synchronized同步块之后,reader线程读到的值才是1;而加了volatile修饰之后就能保证,在writer线程执行了this.value=value之后

reader线程读到的value的值就是1.当然事实上很多java虚拟机在实现上做了很多优化,即不必等到writer退出synchronized同步块value的新值就已经对其他线程可见了.不过加了volatile关键字之后就能完全确保这一点.

volatile关键字示例:

public class VolatileDemo {

    static Data data = new Data();
    
    public static void main(String[] args) throws Exception {
        Thread reader = new Thread(new Runnable(){
            @Override
            public void run() {
                System.out.println(data.getValue());
            }
        });
        
        Thread writer = new Thread(new Runnable(){
            @Override
            public void run() {
                data.setValue(1);
            }
        });
        
        writer.start();
        Thread.sleep(200);
        reader.start();
    }

    static class Data {
        private volatile int value;

        public void setValue(int value) {
            synchronized (this) {
                System.out.println("before update...");
                this.value = value;
                System.out.println("after update...");
                
                //耗时操作
                for(long i=0; i &lt; Long.MAX_VALUE; i++) {
                    for(long j=0; j &lt; 1; j++) {
                        long v = i*j;
                    }
                }
            }
        }
        public int getValue() {
            return this.value;
        }
    }
}

另一个示例:

public class PrintString implements Runnable {

    private boolean isContinuePrint = true;

    public boolean isContinuePrint() {
        return isContinuePrint;
    }

    public void setContinuePrint(boolean isContinuePrint) {
        this.isContinuePrint = isContinuePrint;
    }

    public void printStringMethod() {
        try {
            while (isContinuePrint) {
                System.out.println("run printStringMethod threadName=" + Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void run() {
        printStringMethod();
    }
}

public class Run {

    public static void main(String[] args) {
        PrintString p = new PrintString();
        new Thread(p).start();
        p.setContinuePrint(false);
    }
}

java获取行号

package org.lr.explore;

import java.text.SimpleDateFormat;
import java.util.Date;

public class LineNumberDemo {

	/**
	 * 如何获取当前语句在java文件中所处的行号
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		Logger logger = new Logger(LineNumberDemo.class);
		logger.debug("调试日志");
		logger.info("信息日志");
	}
	
	public String getLineNumber(){
		StackTraceElement[] ste = (Thread.currentThread()).getStackTrace();
		return ste[1].getLineNumber()+"";
	}
}

class Logger{
	private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss SSS");
	private String cn = "";
	public Logger(Class&lt;?&gt; c){
		cn = c.getSimpleName()+".java";
	}
	
	private String getTime(){
		return sdf.format(new Date());
	}
	
	public void debug(Object msg){
		StackTraceElement[] ste = (Thread.currentThread()).getStackTrace();
		long line = ste[2].getLineNumber();
		System.out.println(getTime()+" DEBUG: "+msg+"("+cn+":"+line+")");
	}
	
	public void info(Object msg){
		StackTraceElement[] ste = (new Exception()).getStackTrace();
		long line = ste[1].getLineNumber();
		System.out.println(getTime()+" INFO: "+msg+"("+cn+":"+line+")");
	}
}

输出:

2016-04-12 05:51:09 303 DEBUG: 调试日志(LineNumberDemo.java:15)
2016-04-12 05:51:09 304 INFO: 信息日志(LineNumberDemo.java:16)

java相关技术

Guava类库

Guava类库包含了若干被Google的Java项目广泛依赖的核心库。这些高质量的 API 可以使你的JAVa代码更加优雅,更加简洁。

com.google.common.annotations:普通注解类型。 
com.google.common.base:基本工具类库和接口。 
com.google.common.cache:缓存工具包,非常简单易用且功能强大的JVM内缓存。 
com.google.common.collect:带泛型的集合接口扩展和实现,以及工具类,这里你会发现很多好玩的集合。 
com.google.common.eventbus:发布订阅风格的事件总线。 
com.google.common.hash: 哈希工具包。 
com.google.common.io:I/O工具包。 
com.google.common.math:原始算术类型和超大数的运算工具包。 
com.google.common.net:网络工具包。 
com.google.common.primitives:八种原始类型和无符号类型的静态工具包。 
com.google.common.reflect:反射工具包。 
com.google.common.util.concurrent:多线程工具包。

Preconditions类

Guava类库中的参数检查的类–Preconditions类

Digester

Jakarta Struts中的一个工具。事件驱动,可以XML文件转换成相应的Java对象。在apache-tomcat中有应用。jar包:commons-digester.jar


SAAJ(SOAP with Attachments API for JAVA)

SAAJ支持带附件的SOAP消息。


JAXM(Java API for XML Messaging)
是为Java平台上的应用程序定义的API,用以通过XML(以及SOAP)发送和接收消息,支持同步消息和异步消息。

JAXR(Java API for XML Registries)
与多种类型注册服务进行交互的API。JAXR支持三种注册服务类型:JAXR Pluggable Provider、Registry-specific JAXR Provider、JAXR Bridge Provider(支持UDDI Registry和ebXML Registry/Repository等)。

JAXB(Java Architecture for XML Binding)

是一个业界的标准,是一项可以根据XML Schema产生Java类的技术。


JAF(JavaBeans Activation Framework)

JAF是一个专用的数据处理框架,它用于封装数据,并为应用程序提供访问和操作数据的接口。JAF的主要作用在于让java应用程序知道如何对一个数据源进行查看、编辑和打印等操作。实现:activation.jar


CXF

Apache CXF = Celtix + XFire,开始叫 Apache CeltiXfire,后来更名为 Apache CXF 了。CXF 帮助您利用 Frontend 编程 API 来构建和开发 Services ,像 JAX-WS 。这些 Services 可以支持多种协议,比如:SOAP、XML/HTTP、RESTful HTTP 或者 CORBA ,并且可以在多种传输协议上运行,比如:HTTP、JMS 或者 JBI,CXF 大大简化了 Services 的创建,同时它继承了 XFire 传统,一样可以天然地和 Spring 进行无缝集成。


AXIS(Apache Extensible Interaction System)

阿帕奇可扩展交互系统,Axis本质上就是一个SOAP引擎,提供创建服务器端、客户端和网关SOAP操作的基本框架。


JVM Attach机制

是jvm提供一种jvm进程间通信的能力,能让一个进程传命令给另外一个进程,并让它执行内部的一些操作,比如说我们为了让另外一个jvm进程把线程dump出来,那么我们跑了一个jstack的进程,然后传了个pid的参数,告诉它要哪个进程进行线程dump,既然是两个进程,那肯定涉及到进程间通信,以及传输协议的定义,比如要执行什么操作,传了什么参数等。


Java线程Dump
线程Dump是非常有用的诊断Java应用问题的工具,每一个Java虚拟机都有及时生成显示所有线程在某一点状态的线程Dump的能力。虽然各个Java虚拟机线程dump打印输出格式上略微有一些不同,但是线程dump出来的信息包含线程基本信息;线程的运行状态、标识和调用的堆栈;调用的堆栈包含完整的类名,所执行的方法,如果可能的话还有源代码的行数。


父类中存在子类成员的问题

public class ClassLoop {
    public static void main(String[] args) {
        try {
            Parent p = new Parent(); 
            p.fun(); 
        } catch (Exception e) {
            System.out.println(e); 
        }
    } 
}

class Parent {
    private Parent s = new Sub();
    public void fun() {
        s.fun();
    }
}
class Sub extends Parent {
    public void fun() {
        System.out.println("Hello");
    }
}

运行结果:
抛出栈溢出的异常: java.lang.StackOverflowError