java线程揭秘
1. 什么是线程
线程是进程中可执行代码流的序列,他被操作系统调度,并在处理器或内核上运行。线程是一份独立运行的程序,有自己的运行栈。一个线程包括给定的指定的序列(代码),一个栈(在给定方法中定义的变量)以及一些共享数据(类一级的变量)。或者通俗的讲线程就是程序的一条执行线索,有了多线程,程序就有了多条执行线索,也就是说程序可以同时运行。
public class TestThread {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第一个线程 姓名:" + Thread.currentThread().getName());
}
}
}.start();
new Thread(){
@Override
public void run() {
while(true){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("第二个线程 姓名:" + Thread.currentThread().getName());
}
}
}.start();
System.out.println("main线程 姓名:"+Thread.currentThread().getName());
}
}
程序打印结果:
main线程 姓名main
main线程 姓名main
第一个线程 姓名:Thread-0
第二个线程 姓名:Thread-1
第一个线程 姓名:Thread-0
在程序中我新建了两个线程,还有一个主线程,一共三个线程,可以看出三个线程也就是三条执行线索他们是同时执行的,也就是并发执行。
2. 线程的两种创建方式
线程有两种创建方式,一种是继承Thread类,一种是实现Runnable接口。两种创建方式更推荐使用第二种,因为他更加面向对象一些,也便于我们理解。下面是例子:
public class CreateThread {
public static void main(String[] args) {
//第一种创建方式(继承类)
new Thread(){
@Override
public void run() {
System.out.println("线程1 姓名:" + Thread.currentThread().getName());
}
}.start();
//第二种创建方式(实现接口)
new Thread(new Runnable(){
@Override
public void run() {
System.out.println("线程2 姓名:" + Thread.currentThread().getName());
}
}).start();
}
}
3. 线程并发
我们上面说过,每个线程都有一个单独的栈,所以线程并发执行时不会存在局部变量的共享,但是当多个线程共享一份类级别的变量时(一般是实例变量)就会引起冲突。所以java在处理多线程时引入了一个同步(synchrinized)的概念。其实同步机制不是说在数据共享时线程让同步进行,而是在数据共享时大家排好队一个个来,这才是synchronized要干得事,组织大家排队等公交!关于线程同步,有下面几点需要我们去注意:
1) 线程同步就是线程排队。大家原来在没有冲突的时候一起上,有冲突的时候排队上,不打架。
2) 只有共享资源的读写才需要同步,如果没有资源共享,那么排队就没有意义了。
4. 同步锁
我们先来想想,如果多个线程共享一份数据,要同步,要加锁,我们应该加在哪里?按照常理,我们肯定会认为应该加在共享的资源上,但是在实际的程序中我们无法将锁加在共享的实例变量上,只能将锁加在要共享资源的代码块上。使用synchronized关键字给代码段加锁的语法为:
synchronized(同步锁){
//访问共享资源需要同步的代码段
}
这里要注意同步锁本身一定要是共享的对象,理解这句话很重要。这个共享的对象我们要保证他在内存中是同一个内存地址,这样才能打到同步的目的。先举个例子:
public class TraditionalThreadSynchronized {
public static void main(String[] args) {
new TraditionalThreadSynchronized().init();
}
private void init(){
final Printer printer = new Printer();
new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
printer.print1("123456789");
}
}
}).start();
new Thread(new Runnable(){
@Override
public void run() {
while(true){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
printer.print1("987654321");
}
}
}).start();
}
//内部类
class Printer{
public void print1(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
}
首先先说下这个类的结构,类中有一个内部类去完成我们的打印数据的业务,然后在外部类中去完成主要的逻辑,在外部类中只实例化一个printer对象,两个线程同时使用这一个对象,所以就会有资源冲突,需要我们去同步。从面向对象的角度看,我们需要将同步部分的代码加在printer类中,现在有一个问题,在外部类中共享的是printer对象,我们在printer类中如何去同步了?因为我们要锁得是这个对象,在类中怎么能知道对象的名字了?这个时候我们或许忘记了this这个关键字了。修改内部类代码如下:
class Printer{
public void print1(String name){
int len = name.length();
synchronized (this)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
这个时候我们就可以解决同步锁得问题了。上面我们提到过同步锁的内存地址要一样,这样才能保证锁住,这个时候我们再想想,一样么?呵呵,肯定是一样的了。或许我们会有这样一个思路:
class Printer{
public void print1(String name){
int len = name.length();
synchronized (name)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
这样可以么?将传进来的name作为同步锁。再次考虑我们的同步锁条件:内存地址一样,那么name的内存地址一样么?那肯定不一样啦。所以这样是不可以的。如果我们在Printer中定义一个实例变量了?看如下代码:
这样我们将实例变量作为了同步锁,这样也是可以的。原因还是因为内存地址一样,但是要注意lock不能为null(连内存地址都没,谈何锁?报空指针错误)。虽然加实例变量的方法也可以解决冲突,但是我们还是推荐使用第一种方法。
上面我们一直考虑的是同一段代码共享一个资源,现在我们去探讨不同代码去共享同一个资源的问题:
class Printer{
public void print1(String name){
int len = name.length();
synchronized (this)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
public void print2(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
在printer类中又添加了print2方法,我们在外部类中的第二个线程中修改代码让他调用print2方法。这样,在外部类的两个线程,一个调用print1(),一个调用print2()。但是他们都调用同一个Printer对象。这个时候还是会引起多线程并发的问题(外部类共享同一printer对象)。要解决这个问题,还是老办法,加锁。修改如下:
class Printer{
public void print1(String name){
int len = name.length();
synchronized (this)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
public void print2(String name){
int len = name.length();
synchronized (this)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
在print2中加锁,注意要往对象上加!还有一个就是如果有static的方法了?如何同步?这个时候就要同步synchronized(Printer.class)。看下面的例子。
我们还经常看到直接在方法上加synchronized,不推荐这样做,比较耗性能,我们应该在需要同步的代码块上加同步,而不是在整个方法上。如果在类的普通方法上加synchronized就相当于synchronized(this),如果再static方法上加synchronized(Printer.class)。
static class Printer{
public void print1(String name){
int len = name.length();
synchronized (Printer.class)
{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
public static synchronized void print2(String name){
int len = name.length();
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}
}
要同步一个静态的方法一个非静态方法,只有在非静态方法中取同步类对象,而不是类实例了,所以需要:synchronized(Printer.class)。
下面简单说下java5中的锁,java5中的锁比传统线程中的synchronized更加面向对象,与生活中的锁类似,锁本身也应该是一个对象,两个线程执行的代码要实现同步互斥的效果,他们必须用同一个lock对象。锁是上在代表要操作的资源的类的内部方法中,而不是线程的代码中,以下是例子:
class Printer{
Lock lock = new ReentrantLock();
public void output(String name){
int len = name.length();
lock.lock();
try{
for(int i=0;i<len;i++){
System.out.print(name.charAt(i));
}
System.out.println();
}finally{
lock.unlock();
}
}
}
注意lock 是接口,具体实现类是ReentrantLock,还是就是解锁的时候注意要在finally中进行,以防发生异常意外,导致锁只锁住却没有解开。
5. 线程范围内共享变量——ThreadLocal
ThreadLocal主要就是用于实现线程内的数据共享,即相对于相同的程序代码,多个模块在同一线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。典型的例子就是我们hibernate的设计,每个线程都有一份独立的数据库连接对象,如何去保证线程范围内的数据共享了?这里就需要用到ThreadLocal了。模拟代码如下:
private static final ThreadLocal threadSession = new ThreadLocal();
public static Session getSession() throws InfrastructureException {
Session s = (Session) threadSession.get();
try {
if (s == null) {
s = getSessionFactory().openSession();
threadSession.set(s);
}
} catch (HibernateException ex) {
throw new InfrastructureException(ex);
}
return s;
}
6. java.util.concurrent.atomic 包的使用
atomic是原子的意思。我们上面说过,没有办法对实例变量进行加锁,这里面就提供了相应的类进行加锁!不过这些类不常用,了解下,以后能认识。具体使用的时候再探究。
7. 线程池
先从tomcat的工作原理说起,tomcat本身就是一个线程池,当一个客户端访问tomcat时,会从线程池中拿线程(如果线程池中又空闲的线程,使用。如果没有并且线程池中的线程没有达到tomcat所能承受的最大数量,则创建)。线程池的优点就是我们在用完线程后不要销毁,而是存起来,下次再用,这样就减少了创建和销毁线程的开支。
在线程池的编程模式下,任务是提交给了整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,他就在内部找有空闲的线程,再把任务交给内部空闲的线程,这就是封装。记住,任务是交给线程池。
下面是例子:
public static void main(String[] args) {
ExecutorService threadPool = Executors.newFixedThreadPool(3);
for(int i=1;i<=10;i++){
final int task = i;
threadPool.execute(new Runnable(){
@Override
public void run() {
for(int j=1;j<=10;j++){
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " has complete " + j*10 + "% for task of " + task);
}
}
});
}
System.out.println("all of 10 tasks have committed! ");
}
我们创建了一个有三个线程的线程池,然后往线程池里扔10个任务。从打印可以看出,先执行前三个任务,其他的任务在等待。还可以看出,早就打印出all of 10 tasks have committed这句话,也就是说for循环早就执行完了。任务都已经提交给了线程池了。就是等着他去做了。上面说的是固定大小的线程池。还有就是缓冲线程池:
ExecutorService threadPool = Executors.newCachedThreadPool();
缓冲线程池就是需要多少个线程就创建多少个,比方上面有10个任务,他就会创建10个线程。还有就是单一线程池,就是在整个线程池中始终有一个线程:
ExecutorService threadPool = Executors.newSingleThreadExecutor();通过他可以实现在线程池后马上启动一个线程。
8. JAVA5新特性——读写锁
读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由jvm自己控制的,我们只需要上好相应的锁即可。如果代码为只读数据,可以很多人同时读,但是不能同时写,那就上读锁。如果代码修改数据,只能有一个人在写,且不能同时读取,就上写锁。总之,读的时候上读锁,写的时候上写锁。下面是例子:
public class ReadWriteLockTest {
public static void main(String[] args) {
final Queue3 q3 = new Queue3();
for(int i=0;i<3;i++)
{
new Thread(){
public void run(){
while(true){
q3.get();
}
}
}.start();
new Thread(){
public void run(){
while(true){
q3.put(new Random().nextInt(10000));
}
}
}.start();
}
}
}
class Queue3{
private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
ReadWriteLock rwl = new ReentrantReadWriteLock();
public void get(){
rwl.readLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to read data!");
Thread.sleep((long)(Math.random()*1000));
System.out.println(Thread.currentThread().getName() + "have read data :" + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.readLock().unlock();
}
}
public void put(Object data){
rwl.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName() + " be ready to write data!");
Thread.sleep((long)(Math.random()*1000));
this.data = data;
System.out.println(Thread.currentThread().getName() + " have write data: " + data);
} catch (InterruptedException e) {
e.printStackTrace();
}finally{
rwl.writeLock().unlock();
}
}
}
读写锁的出现在我看来作用主要是:提高线程同步时的效率。
9. JAVA5新特性——Callable与Future
首先介绍下Callable接口,Callable接口的功能类似于Runnable接口。他们都是规定线程的执行任务。两者的不同有:
(1)Callable规定的方法是call(),Runnable规定的方法是run().
(2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得。
(3)call方法可以抛出异常,run方法不可以
(4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
下面是一个例子:
public static void main(String[] args) throws Exception{
ExecutorService singlePool = Executors.newSingleThreadExecutor();
Future<String> future =
singlePool.submit(
new Callable<String>() {
public String call() throws Exception {
Thread.sleep(4000);
return "guolei";
};
}
);
System.out.println("等待.....");
System.out.println(future.isCancelled());
System.out.println("结果:" + future.get());
}
10. JAVA5新特性——Condition
Condition与传统线程中的wait和notify作用很像,先回顾下wait和notify,他们是为了解决多线程之间相互协调工作,具体不细说,从一道经典的面试题开始谈起:子线程循环10次,接着主线程循环10次,接着又回到子线程循环10次,接着再回到主线程循环10次。
public class ThreadCommunication {
public static void main(String[] args) throws InterruptedException {
final Business business = new Business();
new Thread(
new Runnable() {
@Override
public void run() {
try {
for(int i=0;i<2;i++)
business.sub();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
).start();
for(int i=0;i<2;i++)
business.main();
}
}
class Business {
private boolean bShouldSub = true;
public synchronized void sub() throws InterruptedException{
while(!bShouldSub){
this.wait();
}
for(int j=1;j<=10;j++){
System.out.println("sub thread sequence of " + j);
}
bShouldSub = false;
this.notify();
}
public synchronized void main() throws InterruptedException{
while(bShouldSub){
this.wait();
}
for(int j=1;j<=10;j++){
System.out.println("main thread sequence of " + j);
}
bShouldSub = true;
this.notify();
}
}
首先要细心理解上面的代码,这是关键。注意使用wait和notify的前提是必须先获得锁,即先同步,这是必须的。如果使用Condition,那么bussiness类应该改为:
class Business {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
private boolean bShouldSub = true;
public void sub(){
lock.lock();
try{
while(!bShouldSub){
try {
condition.await();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("sub thread sequence of " + j);
}
bShouldSub = false;
condition.signal();
}finally{
lock.unlock();
}
}
public void main(){
lock.lock();
try{
while(bShouldSub){
try {
condition.await();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
for(int j=1;j<=10;j++){
System.out.println("main thread sequence of " + j);
}
bShouldSub = true;
condition.signal();
}finally{
lock.unlock();
}
}
}
首先注意要加锁,其次注意解锁一定要放在finally中。condition.await()和condition.signal()配合使用。
11. JAVA5新特性——Semaphore
Semaphore可以维护当前访问自身的线程个数,并提供了同步机制。使用他可以控制同时访问资源的线程个数。例子:
public class SemaphoreTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Semaphore sp = new Semaphore(3);
for(int i=0;i<10;i++){//往线程池里扔10个任务
Runnable runnable = new Runnable(){
public void run(){
try {
sp.acquire();
} catch (InterruptedException e1) {
e1.printStackTrace();
}
System.out.println(sp.availablePermits());
sp.release();
}
};
service.execute(runnable);
}
}
}
那么Semaphore sp = new Semaphore(1);就可以保证某个方法始终有一个线程访问。sp对象可以实现互斥锁的功能,什么意思了?因为此对象只允许一个线程进入,他不就可以保证互斥吗?他的优点就是这个了。他可以应用于一些死锁恢复的一些场合。还有就是一道经典的面试题,将她用的淋漓尽致:
/**
*
* 题目描述:有三个线程名字分别是A、B、C,每个线程只能打印自己的名字,在
* 屏幕上顺序打印 ABC,打印10次。不准使用线程的sleep()
* @author guolei
* @version 1.0
* @created 2011-9-30 下午02:36:21
* @history
* @see
*/
public class SemaphoreThread extends Thread {
private Semaphore current;
private Semaphore next;
public SemaphoreThread(String name, Semaphore current, Semaphore next) {
super(name);
this.current = current;
this.next = next;
}
public void run() {
for (int i = 0; i < 10; i++) {
try {
current.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.print(this.getName());
next.release();
}
}
public static void main(String[] args) {
Semaphore a = new Semaphore(1);
Semaphore b = new Semaphore(0);
Semaphore c = new Semaphore(0);
new SemaphoreThread("A", a, b).start();
new SemaphoreThread("B", b, c).start();
new SemaphoreThread("C", c, a).start();
}
}
12. JAVA5新特性——CyclicBarrier
他有什么作用了?有三个线程共同执行一个任务,当大家都执行完这个任务的时候我们才能接着往下执行,这个时候就用到了CyclicBarrier。例子:
public class CyclicBarrierTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CyclicBarrier cb = new CyclicBarrier(3);
for(int i=0;i<3;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
System.out.println("begin now");
System.out.println(cb.getNumberWaiting());//有一个等待,结果为0(即得到的结果为实际数减1)
if(cb.getNumberWaiting()<2)
cb.await();
System.out.println("end once");
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
service.shutdown();
}
}
13. JAVA5新特性——CountdownLatch
这个类的主要作用还是同步,我以运动员赛跑为例来解释他:
public class CountdownLatchTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final CountDownLatch cdOrder = new CountDownLatch(1);
final CountDownLatch cdAnswer = new CountDownLatch(3);
for(int i=0;i<3;i++){
Runnable runnable = new Runnable(){
public void run(){
try {
System.out.println("运动员:" + Thread.currentThread().getName() +
"准备完毕,等待裁判发号。");
cdOrder.await();
System.out.println("运动员:" + Thread.currentThread().getName() +
"已接受命令");
Thread.sleep((long)(Math.random()*10000));
System.out.println("运动员" + Thread.currentThread().getName() +
"已到终点,等待裁判宣判");
cdAnswer.countDown();
} catch (Exception e) {
e.printStackTrace();
}
}
};
service.execute(runnable);
}
try {
Thread.sleep((long)(Math.random()*10000));
System.out.println("裁判" + Thread.currentThread().getName() +
"即将打信号枪");
cdOrder.countDown();
System.out.println("裁判" + Thread.currentThread().getName() +
"已打信号枪,等待运动员赛跑结束");
cdAnswer.await();
System.out.println("裁判" + Thread.currentThread().getName() +
"已知道结果,宣判比赛结果");
} catch (Exception e) {
e.printStackTrace();
}
service.shutdown();
}
}
CountDownLatch这个类在初始化的时候给他一个计数,每次调用countDown方法计数器会减1,减到0的时候开始向下执行。用给定的计数 初始化 CountDownLatch。由于调用了 countDown() 方法,所以在当前计数到达零之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。
14. JAVA5新特性——Exchanger
顾明思义,他的作用是做数据交换的同步,现在我确实还没有想到他在实际中有什么作用。。。举例:
public class ExchangerTest {
public static void main(String[] args) {
ExecutorService service = Executors.newCachedThreadPool();
final Exchanger exchanger = new Exchanger();
service.execute(new Runnable(){
public void run() {
try {
String data1 = "zxx";
System.out.println("线程" + Thread.currentThread().getName() +
"正在把数据" + data1 +"换出去");
Thread.sleep((long)(Math.random()*10000));
String data2 = (String)exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() +
"换回的数据为" + data2);
}catch(Exception e){
}
}
});
service.execute(new Runnable(){
public void run() {
try {
String data1 = "lhm";
System.out.println("线程" + Thread.currentThread().getName() +
"正在把数据" + data1 +"换出去");
Thread.sleep((long)(Math.random()*10000));
String data2 = (String)exchanger.exchange(data1);
System.out.println("线程" + Thread.currentThread().getName() +
"换回的数据为" + data2);
}catch(Exception e){
}
}
});
}
}
15. JAVA5新特性——ArrayBlockingQueue
在java5中,提供了一个阻塞队列供我们来使用,什么是队列我就不多说了,数据结构的东西。以往我们在使用队列时肯定要考虑多线程并发的情况,现在我们使用java5提供的队列什么也不用考虑了,人家已经替我们考虑好了。比方说如果队列中数据已经满了,再放数据可以阻塞,也可以抛出异常。。。ArrayBlockingQueue实现了BlockingQueue接口,在这个接口中规定了从队列中存数据和取数据的各种方法,以及他们的异同。在这里不再多说,请看API。
16. JAVA5新特性——ConcurrentHashMap
从现在开始,多线程的时候不要再使用HashTable,请选择使用ConcurrentHashMap,他的效率更高。下面是我见一个高手写的测试同步的HashMap,HashTable,ConcurrentHashMap三个集合在多线程情况下的效率的类,从人家这个程序来看,不得不佩服人家的设计,学习学习:
public class T {
static final int threads = 1000;
static final int NUMBER = 1000;
public static void main(String[] args) throws Exception {
Map<String, Integer> hashmapSync = Collections
.synchronizedMap(new HashMap<String, Integer>());
Map<String, Integer> concurrentHashMap = new ConcurrentHashMap<String, Integer>();
Map<String, Integer> hashtable = new Hashtable<String, Integer>();
long totalA = 0;
long totalB = 0;
long totalC = 0;
for (int i = 0; i <= 10; i++) {
System.out.println("A开始"+totalA);
totalA += testPut(hashmapSync);
System.out.println("A结束"+totalA);
totalB += testPut(concurrentHashMap);
totalC += testPut(hashtable);
}
System.out.println("Put time HashMapSync=" + totalA + "ms.");
System.out.println("Put time ConcurrentHashMap=" + totalB + "ms.");
System.out.println("Put time Hashtable=" + totalC + "ms.");
totalA = 0;
totalB = 0;
totalC = 0;
for (int i = 0; i <= 10; i++) {
totalA += testGet(hashmapSync);
totalB += testGet(concurrentHashMap);
totalC += testGet(hashtable);
}
System.out.println("Get time HashMapSync=" + totalA + "ms.");
System.out.println("Get time ConcurrentHashMap=" + totalB + "ms.");
System.out.println("Get time Hashtable=" + totalC + "ms.");
}
public static long testPut(Map<String, Integer> map) throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < threads; i++) {
new MapPutThread(map).start();
}
while (MapPutThread.counter > 0) {
Thread.sleep(1);
}
return System.currentTimeMillis() - start;
}
public static long testGet(Map<String, Integer> map) throws Exception {
long start = System.currentTimeMillis();
for (int i = 0; i < threads; i++) {
new MapPutThread(map).start();
}
while (MapPutThread.counter > 0) {
Thread.sleep(1);
}
return System.currentTimeMillis() - start;
}
}
class MapPutThread extends Thread {
static int counter = 0;
static Object lock = new Object();
private Map<String, Integer> map;
private String key = this.getId() + "";
MapPutThread(Map<String, Integer> map) {
synchronized (lock) {
counter++;
}
this.map = map;
}
public void run() {
for (int i = 1; i <= T.NUMBER; i++) {
map.put(key, i);
}
synchronized (lock) {
counter--;
}
}
}
class MapGetThread extends Thread {
static int counter = 0;
static Object lock = new Object();
private Map<String, Integer> map;
private String key = this.getId() + "";
MapGetThread(Map<String, Integer> map) {
synchronized (lock) {
counter++;
}
this.map = map;
}
public void run() {
for (int i = 1; i <= T.NUMBER; i++) {
map.get(key);
}
synchronized (lock) {
counter--;
}
}
}
这是输出结果:
Put time HashMapSync=12781ms.
Put time ConcurrentHashMap=5845ms.
Put time Hashtable=12859ms.
Get time HashMapSync=12749ms.
Get time ConcurrentHashMap=5500ms.
Get time Hashtable=12783ms.
从结果可以看到ConcurrentHashMap的效率确实不同凡响,比其他的快一倍左右!!推荐使用他了。