• 博客园logo
  • 会员
  • 众包
  • 新闻
  • 博问
  • 闪存
  • 赞助商
  • HarmonyOS
  • Chat2DB
    • 搜索
      所有博客
    • 搜索
      当前博客
  • 写随笔 我的博客 短消息 简洁模式
    用户头像
    我的博客 我的园子 账号设置 会员中心 简洁模式 ... 退出登录
    注册 登录

夕水溪下

  • 博客园
  • 联系
  • 订阅
  • 管理

公告

View Post

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的效率确实不同凡响,比其他的快一倍左右!!推荐使用他了。

posted on 2011-11-03 14:49  夕水溪下  阅读(569)  评论(0)    收藏  举报

刷新页面返回顶部
 
博客园  ©  2004-2025
浙公网安备 33010602011771号 浙ICP备2021040463号-3