精华内容
下载资源
问答
  • 以下内容是学习张老师Java多线程与线程并发库高级应用时所做的笔记,很有用 网络编辑器直接复制Word文档排版有点乱,提供原始文件下载 先看源文件概貌     张孝祥_Java多线程与并发库高级应用 【视频介绍:】  ...

     

    以下内容是学习张老师Java多线程与线程并发库高级应用时所做的笔记,很有用

    网络编辑器直接复制Word文档排版有点乱,提供原始文件下载

    先看源文件概貌

     

     

    张孝祥_Java多线程与并发库高级应用

    【视频介绍:】

        Java线程是一项非常基本和重要的技术,在偏底层和偏技术的Java程序中不可避免地要使用到Java线程技术,特别是android手机程序和游戏开发中,多线程成了必不可少的一项重要技术。但是,很多Java程序员对Java线程技术的了解都仅停留在初级阶段,在项目中一旦涉及到多线程时往往就表现得糟糕至极,所以,软件公司常常使用Java线程技术来考察面试者的基本功和判断其编码水平的高低。

        本套视频教程是专门为了帮助那些已经学习和了解过、但掌握得并不是很深入的人们提高java线程技术而讲解的,所以,Java线程初学者学习本视频教程时可能会比较吃力,可能必须耐心学习多遍才能渐入佳境,但是,你一旦掌握了其中的内容,你对Java线程技术的了解将会相当出众!

    【视频目录列表:


     

        01. 传统线程技术回顾

        02. 传统定时器技术回顾

        03. 传统线程互斥技术

        04. 传统线程同步通信技术

        05. 线程范围内共享变量的概念与作用

        06. ThreadLocal类及应用技巧

        07. 多个线程之间共享数据的方式探讨

        08. java5原子性操作类的应用

        09. java5线程并发库的应用

        10. CallableFuture的应用

        11. java5的线程锁技术

        12. java5读写锁技术的妙用

        13. java5条件阻塞Condition的应用

        14. java5Semaphere同步工具

        15. java5CyclicBarrier同步工具

        16. java5CountDownLatch同步工具

        17. java5Exchanger同步工具

        18. java5阻塞队列的应用

        19. java5同步集合类的应用

        20. 空中网挑选实习生的面试题1

        21. 空中网挑选实习生的面试题2

        22. 空中网挑选实习生的面试题3

        23. 源代码与资料


     

     

    01.传统线程技术回顾

    传统是相对于JDK1.5而言的

           传统线程技术与JDK1.5的线程并发库

           线程就是程序的一条执行线索/线路。

    创建线程的两种传统方式

    1.         创建Thread的子类,覆盖其中的run方法,运行这个子类的start方法即可开启线程

    Thread thread = new Thread()

           {     @Override

           public void run()

           {

           while (true)

           {

           获取当前线程对象             获取线程名字

           Thread.currentThread()         threadObj.getName()

           让线程暂停,休眠,此方法会抛出中断异常InterruptedException

           Thread.sleep(毫秒值);

    }

    }

    };

                  thread.start();

    2.         创建Thread时传递一个实现Runnable接口的对象实例

    Thread thread = new Thread(new Runnable()

    {

           public void run()

           {}

    });

                  thread.start();

     

    问题:下边的线程运行的是Thread子类中的方法还是实现Runnable接口类的方法

    new Thread(

                         b、传递实现Runnable接口的对象

                         new Runnable()

                         {

                         public void run()

                         {}

    }

    ){

                         a、覆盖Thread子类run方法

    public void run(){}

    }.start();

           分析:new Thread(Runnable.run()){run()}.start();

           子类run方法实际就是覆盖父类中的run方法,如果覆盖了就用子类的run方法,不会再找Runnable中的run方法了,所以运行的是子类中的run方法

     

    总结:

    Thread类中的run方法源代码中看出,两种传统创建线程的方式都是在调用Thread对象的run方法,如果Thread对象的run方法没有被覆盖,并且像上边的问题那样为Thread对象传递了一个Runnable对象,就会调用Runnable对象的run方法。

    多线程并不一定会提高程序的运行效率。举例:一个人同时在三张桌子做馒头

    多线程下载:并不是自己电脑快了,而是抢到更多服务器资源。例:服务器为一个客户分配一个20K的线程下载,你用多个线程,服务器以为是多个用户就分配了多个20K的资源给你。

     

    02.传统定时器技术回顾

    传统定时器的创建:直接使用定时器类Timer

    a、过多长时间后炸

    new Timer().schedule(TimerTask定时任务, Date time定的时间);

    b、过多长时间后炸,以后每隔多少时间再炸

    new Timer().schedule(TimerTask定时任务, Long延迟(第一次执行)时间, Long间隔时间);

    TimerTaskRunnable类似,有一个run方法

    Timer是定时器对象,到时间后会触发炸弹(TimerTask)对象

    示例:

    new Timer().schedule(

    new TimerTask()定时执行的任务

    {

           public void run()

           {

           SOP(“bombing”);

    }

    显示计时信息

    while (true)

    {

           SOP(new Date().getSeconds());

           Thread.sleep(1000);

    }

    }

    10    定好的延迟时间,10秒以后执行任务

    );

    问题:2秒后炸,爆炸后每隔3秒再炸一次

    定时器2秒后炸,炸弹里还有定时器(每3秒炸一次)

    class MyTimerTask extends TimerTask        这就是准备用的子母弹

    {

           public void run()

           {

                  本身就是一颗炸弹

                  SOP(bombing);

                  内部子弹

                  new Timer().schedule(

    new MyTimerTask(), 2000

    );

           }

    }

           放置子母弹,2秒后引爆

           new Timer().schedule(new MyTimerTask(), 2000);

    问题延伸:

           上面的问题延伸,母弹炸过后,子弹每隔3秒炸一次,再每隔8秒炸一次

           1、在MyTimerTask内部定义一个静态变量记录炸弹号,在run方法内将炸弹号加1,每次产生新炸弹,号码就会加1,根据炸弹号判断是3秒炸还是8秒炸。

           注意:内部类中不能声明静态变量

           定义一个静态变量private static count = 0;

           run方法内部:count=count+1%2

           将定时器的时间设置为:2000+2000*count

           2、用两个炸弹来完成,A炸弹炸完后启动定时器安装B炸弹,B炸弹炸完后也启动一个定时器安装A炸弹。

     

    定时器还可以设置具体时间,如某年某月某日某时……可以设置周一到周五做某事,自己设置的话需要换算日期时间,可以使用开源工具quartz来完成。

     

    03.传统线程互斥技术

           线程安全问题例子:银行转账

           同一个账户一边进行出账操作(自己交学费),另一边进行入账操作(别人给自己付款),线程不同步带来的安全问题

    示例:逐个字符的方式打印字符串

    class Outputer

    {

           public void output(String name)

          {

          int len = name.length();

          for (int i=0; i<len; i++)

                 SOP(name.charAt(i));逐个字符打印

          SOP();换行

    }

    }

    public void test()

    {

           Outputer outputer = new Outputer();

           new Thread(

    new Runnable()

    {

           public void run()

           {

                  Thread.sleep(100);

           outputer.output(“zhangxiaoxiang”);

    }

    }).start();

           new Thread(

    new Runnable()

    {

           public void run()

           {

                  Thread.sleep(100);

           outputer.output(“lihuoming”);

    }

    }).start();

    }

    注意:

    内部类不能访问局部变量,要访问需加final

    静态方法中不能创建内部类的实例对象

    打印结果发现的问题:线程不同步所致,两个线程都在使用同一个对象

    互斥方法:

           a、同步代码块

                  synchronized (lock){}

           b、同步方法 

                  方法返回值前加synchronized

                  同步方法上边用的锁就是this对象

                 静态同步方法使用的锁是该方法所在的class文件对象

    使用synchronized关键字实现互斥,要保证同步的地方使用的是同一个锁对象

           public synchronized void output(String name)

          {

          int len = name.length();

          这里就不要再加同步了,加上极易出现死锁

          for (int i=0; i<len; i++)

                 SOP(name.charAt(i));逐个字符打印

          SOP();换行

    }

     

    04.传统线程同步通信技术

           面试题,子线程10次与主线程100次来回循环执行50

           下面是我刚看完面试题就暂停视频自己试着写的代码,还可以,结果完成要求了

    在单次循环结束后让这个刚结束循环的线程休眠,保证另一个线程可以抢到执行权。

    public class ThreadInterViewTest

    {

           /**

            * 刚看到面试题没看答案之前试写

            * 子线程循环10次,回主线程循环100次,

            * 再到子线程循环10次,再回主线程循环100

            * 如此循环50      

            */

           public static void main(String[] args)

           {

                  int num = 0;

                  while (num++<50)

                  {

                         new Thread(new Runnable()

                                       {

                                              @Override

                                              public void run()

                                              {

                                                     circle("子线程运行", 10);

                                              }

                                       }).start();

                         try

                         {

                                //加这句是保证上边的子线程先运行,刚开始没加,主线程就先开了

                                Thread.sleep(2000);

                         } catch (InterruptedException e)

                         {

                                e.printStackTrace();

                         }

                         circle("主线程", 100);  

                  }

           }

          

           public static synchronized void circle(String name, int count)

           {

                  for (int i=1; i<=count; i++)

                  {

                         System.out.println(name+"::"+i);

                  }

                  try

                  {

                         Thread.sleep(5000);

                  } catch (InterruptedException e)

                  {

                         e.printStackTrace();

                  }

           }

    }

     

    张老师讲的方法:

    1、将子线程和主线程中要同步的方法进行封装,加上同步关键字实现同步

    2、两个线程间隔运行,添加一个标记变量进行比较以实现相互通信,加色的部分

    wait   notify   notifyAll    wait会抛出异常

    class Business

    {

          private boolean bShouleSub = true;

           public synchronized void sub()

           {

    下面的if处使用while以增加程序健壮性,因为存在虚假唤醒,有时候并没有被notify就醒了。如果该方法没有同步的话,此处就更要使用while进行判断了,避免进程不同步问题

                 if(bShouleSub)

                 {

                         for (int i=1; i<11; i++)

                         SOP(sub+i);

                 bShouldSub = false;

                 this.notify();

    }

    else

          this.wait();

    }

           public synchronized void main()

           {

                 if (!bShouldSub)

                 {

                         for (int i=1; i<101; i++)

                         SOP(main+i);

                 bShouldSub = true;

                 this.notify();

    }

    else

          this.wait();

    }

    }

    经验:要用到共同数据(包括同步锁)或相同算法的多个方法要封装在一个类中

          锁是上在代表要操作的资源类的内部方法中的,而不是上在线程代码中的。这样写出来的类就是天然同步的,只要使用的是同一个new出来的对象,那么这个对象就具有同步互斥特性

          判断唤醒等待标记时使用while增加程序健壮性,防止伪唤醒

     

     

    05.线程范围内共享变量的概念与作用

    线程范围内共享数据图解:

    代码演示:

    class ThreadScopeShareData

    {

           三个模块共享数据,主线程模块和AB模块

           private static int data = 0;     准备共享的数据

           存放各个线程对应的数据

          private Map<Thread, Integer> threadData = new HashMap<Thread, Integer>();

           public static void main(String[] args)

           {     创建两个线程

    for (int i=0; i<2; i++)

    {

           new Thread(

    new Runnable()

    {

           public void run()

           {现在当前线程中修改一下数据,给出修改信息

                  data = new Random().nextInt();

                  SOP(Thread.currentThread().getName()+将数据改为+data);

                 将线程信息和对应数据存储起来

                 threadData.put(Thread.currentThread(), data);

                  使用两个不同的模块操作这个数据,看结果

                  new A().get();

                  new B().get();

    }

    }

    ).start();

    }

    }

           static class A

           {

           public void get()

           {

                 data = threadData.get(Thread.currentThread());

           SOP(A+Thread.currentThread().getName()+拿到的数据+data);

    }

    }

           static class B

           {

           public void get()

           {

                 data = threadData.get(Thread.currentThread());

           SOP(B+Thread.currentThread().getName()+拿到的数据+data);

    }

    }

    }

    结果并没与实现线程间的数据同步,两个线程使用的是同一个线程的数据。要解决这个问题,可以将每个线程用到的数据与对应的线程号存放到一个map集合中,使用数据时从这个集合中根据线程号获取对应线程的数据。代码实现:上面红色部分

    程序中存在的问题:获取的数据与设置的数据不同步

                   Thread-1共享数据设置为:-997057737

                  Thread-1--A模块数据:-997057737

                  Thread-0共享数据设置为:11858818

                  Thread-0--A模块数据:11858818

                  Thread-0--B模块数据:-997057737

                  Thread-1--B模块数据:-997057737

    最好将Runnable中设置数据的方法也写在对应的模块中,与获取数据模块互斥,以保证数据同步

     

    06. ThreadLocal类及应用技巧

           多个模块在同一个线程中运行时要共享同一份数据,实现线程范围内的数据共享可以用上一节中所用的方法。

           JDK1.5提供了ThreadLocal类来方便实现线程范围内的数据共享,它的作用就相当于上一节中的Map

           每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map集合中增加一条记录,key就是各自的线程,value就是各自的set方法传进去的值。

           在线程结束时可以调用ThreadLocal.clear()方法用来更快释放内存,也可以不调用,因为线程结束后也可以自动释放相关的ThreadLocal变量。

           一个ThreadLocal对象只能记录一个线程内部的一个共享变量,需要记录多个共享数据,可以创建多个ThreadLocal对象,或者将这些数据进行封装,将封装后的数据对象存入ThreadLocal对象中。

           将数据对象封装成单例,同时提供线程范围内的共享数据的设置和获取方法,提供已经封装好了的线程范围内的对象实例,使用时只需获取实例对象即可实现数据的线程范围内的共享,因为该对象已经是当前线程范围内的对象了。下边给出张老师的优雅代码:

    package cn.itheima;

    import java.util.Random;

    publicclass ThreadLocalShareDataDemo

    {   /**06. ThreadLocal类及应用技巧

         * 将线程范围内共享数据进行封装,封装到一个单独的数据类中,提供设置获取方法

         * 将该类单例化,提供获取实例对象的方法,获取到的实例对象是已经封装好的当前线程范围内的对象

         */

        publicstaticvoid main(String[] args)

        {

            for (int i=0; i<2; i++)

            {

               new Thread(

                       new Runnable()

                       {                      

                           publicvoid run()

                           {

                               int data =new Random().nextInt(889);

        System.out.println(Thread.currentThread().getName()+"产生数据:"+data);

                               MyData myData = MyData.getInstance();

                               myData.setAge(data);

                               myData.setName("Name:"+data);

                               new A().get();

                               new B().get();

                           }

                       }).start();

            }

        }

       

        staticclass A

        {   //可以直接使用获取到的线程范围内的对象实例调用相应方法

            String name = MyData.getInstance().getName();

            intage = MyData.getInstance().getAge();

            publicvoid get()

            {

               System.out.println(Thread.currentThread().getName()+"-- AA name:"+name+"...age:"+age);

            }

        }  

       

        staticclass B

        {

            //可以直接使用获取到的线程范围内的对象实例调用相应方法

            String name = MyData.getInstance().getName();

            intage = MyData.getInstance().getAge();

            publicvoid get()

            {

               System.out.println(Thread.currentThread().getName()+"-- BB name:"+name+"...age:"+age);

            }

        }  

       

        staticclass MyData

        {

            private Stringname;

            privateintage;

            public String getName()

            {

               returnname;

            }

            publicvoid setName(String name)

            {

               this.name = name;

            }

            publicint getAge()

            {

               returnage;

            }

            publicvoid setAge(int age)

            {

               this.age = age;

            }

            //单例

            private MyData() {};

            //提供获取实例方法

            publicstatic MyData getInstance()

            {

               //从当前线程范围内数据集中获取实例对象

               MyData instance =threadLocal.get();

               if (instance==null)

               {

                   instance =new MyData();

                   threadLocal.set(instance);

               }

               return instance;

            }

            //将实例对象存入当前线程范围内数据集中

            static ThreadLocal<MyData>threadLocal =new ThreadLocal<MyData>();

        }

    }

     

    07.多个线程之间共享数据的方式探讨

           例子:卖票:多个窗口同时卖这100张票,票就需要多个线程共享

    a、如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个对象中有共享数据。

    卖票就可以这样做,每个窗口都在做卖票任务,卖的票都是同一个数据。

    b、如果每个线程执行的代码不同,就需要使用不同的Runnable对象,有两种方式实现

    Runnable对象之间的数据共享:

           a)将共享数据单独封装到一个对象中,同时在对象中提供操作这些共享数据的方法,可以方便实现对共享数据各项操作的互斥和通信。

           b)将各个Runnable对象作为某个类的内部类,共享数据作为外部类的成员变量,对共享数据的操作方法也在外部类中提供,以便实现互斥和通信,内部类的Runnable对象调用外部类中操作共享数据的方法即可。

           注意:要同步互斥的几段代码最好分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。

     

    08. java5原子性操作类的应用

           Java5的线程并发库

           java.util.concurrent在并发编程中很常用的实用工具类。

                         |----locks为锁和等待条件提供一个框架的接口和类,

    它不同于内置同步和监视器

                         |----atomic类的小工具包,支持在单个变量上解除锁的线程安全编程。

                               可以对基本类型、数组中的基本类型、类中的基本类型等进行操作

                                |----AtomicInteger

    构造方法摘要

    AtomicInteger()           创建具有初始值 0 的新 AtomicInteger。

    AtomicInteger(int initialValue)           创建具有给定初始值的新 AtomicInteger。

    方法摘要

     int

    addAndGet(int delta)           以原子方式将给定值与当前值相加。

     boolean

    compareAndSet(int expect, int update)           如果当前值 == 预期值,则以原子方式将该值设置为给定的更新值。

     int

    decrementAndGet()           以原子方式将当前值减 1。

     double

    doubleValue()           以 double 形式返回指定的数值。

     float

    floatValue()           以 float 形式返回指定的数值。

     int

    get()           获取当前值。

     int

    getAndAdd(int delta)           以原子方式将给定值与当前值相加。

     int

    getAndDecrement()           以原子方式将当前值减 1。

     int

    getAndIncrement()           以原子方式将当前值加 1。

     int

    getAndSet(int newValue)           以原子方式设置为给定值,并返回旧值。

     int

    incrementAndGet()           以原子方式将当前值加 1。

     int

    intValue()           以 int 形式返回指定的数值。

     void

    lazySet(int newValue)           最后设置为给定值。

     long

    longValue()           以 long 形式返回指定的数值。

     void

    set(int newValue)           设置为给定值。

     String

    toString()           返回当前值的字符串表示形式。

     boolean

    weakCompareAndSet(int expect, int update)           如果当前值 == 预期值,则以原子方式将该设置为给定的更新值。

                                |----AtomicIntegerArray

    构造方法摘要

    AtomicIntegerArray(int length)           创建给定长度的新 AtomicIntegerArray

    AtomicIntegerArray(int[] array)           创建与给定数组具有相同长度的新 AtomicIntegerArray,并从给定数组复制其所有元素。

    方法摘要

     int

    addAndGet(int i, int delta)           以原子方式将给定值与索引i的元素相加。

     boolean

    compareAndSet(int i, int expect, int update)           如果当前值==预期值,则以原子方式将位置i的元素设置为给定的更新值。

     int

    decrementAndGet(int i)           以原子方式将索引i的元素减 1

     int

    get(int i)           获取位置i的当前值。

     int

    getAndAdd(int i, int delta)           以原子方式将给定值与索引i的元素相加。

     int

    getAndDecrement(int i)           以原子方式将索引i的元素减 1

     int

    getAndIncrement(int i)           以原子方式将索引i的元素加 1

     int

    getAndSet(int i, int newValue)           将位置i的元素以原子方式设置为给定值,并返回旧值。

     int

    incrementAndGet(int i)           以原子方式将索引i的元素加 1

     void

    lazySet(int i, int newValue)           最后将位置i的元素设置为给定值。

     int

    length()           返回该数组的长度。

     void

    set(int i, int newValue)           将位置i的元素设置为给定值。

     String

    toString()           返回数组当前值的字符串表示形式。

     boolean

    weakCompareAndSet(int i, int expect, int update)           如果当前值==预期值,则以原子方式将位置i的元素设置为给定的更新值。

     

    09. java5线程并发库的应用

           如果没有线程池,需要在run方法中不停判断,还有没有任务需要执行

           线程池的通俗比喻:接待客户,为每个客户都安排一个工作人员,接待完成后该工作人员就废掉。服务器每收到一个客户请求就为其分配一个线程提供服务,服务结束后销毁线程,不断创建、销毁线程,影响性能。

           线程池:先创建多个线程放在线程池中,当有任务需要执行时,从线程池中找一个空闲线程执行任务,任务完成后,并不销毁线程,而是返回线程池,等待新的任务安排。

           线程池编程中,任务是提交给整个线程池的,并不是提交给某个具体的线程,而是由线程池从中挑选一个空闲线程来运行任务。一个线程同时只能执行一个任务,可以同时向一个线程池提交多个任务。

    线程池创建方法:

    a、创建一个拥有固定线程数的线程池

           ExecutorService threadPool = Executors.newFixedThreadPool(3);   

           b、创建一个缓存线程池    线程池中的线程数根据任务多少自动增删动态变化

           ExecutorService threadPool = Executors.newCacheThreadPool();

           c、创建一个只有一个线程的线程池 与单线程一样 但好处是保证池子里有一个线程,

    当线程意外死亡,会自动产生一个替补线程,始终有一个线程存活

           ExecutorService threadPool = Executors.newSingleThreadExector();

    往线程池中添加任务

           threadPool.executor(Runnable)

    关闭线程池:

           threadPool.shutdown()  线程全部空闲,没有任务就关闭线程池

           threadPool.shutdownNow() 不管任务有没有做完,都关掉

     

    用线程池启动定时器:

           a、创建调度线程池,提交任务        延迟指定时间后执行任务

           Executors.newScheduledThreadPool(线程数).schedule(Runnable,延迟时间,时间单位);

           b、创建调度线程池,提交任务,延迟指定时间执行任务后,间隔指定时间循环执行

           Executors.newScheduledThreadPool(线程数).schedule(Runnable,延迟时间,

    间隔时间,时间单位);

           所有的schedule方法都接受相对延迟和周期作为参数,而不是绝对的时间或日期。将以Date所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的Date运行,可以使用:schedule(task,date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)

     

    10. CallableFuture的应用:获取一个线程的运行结果

    public interface Callable<V>

    返回结果并且可能抛出异常的任务。实现者定义了一个不带任何参数的叫做 call的方法。 Callable接口类似于Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是 Runnable不会返回结果,并且无法抛出经过检查的异常。

    只有一个方法Vcall()计算结果,如果无法计算结果,则抛出一个Exception异常。

    使用方法:

           ExecutorService threadPool = Executors.newSingleThreadExccutor();

           如果不需要返回结果,就用executor方法 调用submit方法返回一个Future对象

           Future<T> future = threadPool.submit(new Callable<T>(){//接收一个Callable接口的实例对象

                        覆盖Callable接口中的call方法,抛出异常

                         publicTcall() throws Exception

                         {

                                ruturnT

    }

    });

    获取Future接收的结果

    futureget();会抛出异常

    future.get()没有拿到结果就会一直等待

           Future取得的结果类型和Callable返回的结果类型必须一致,通过泛型实现。Callable要通过ExecutorServicesubmit方法提交,返回的Future对象可以取消任务。

     

    public interface Future<V>

    Future 表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并获取计算的结果。计算完成后只能使用 get方法来获取结果,如有必要,计算完成前可以阻塞此方法。取消则由 cancel方法来执行。还提供了其他方法,以确定任务是正常完成还是被取消了。一旦计算完成,就不能再取消计算。如果为了可取消性而使用 Future但又不提供可用的结果,则可以声明 Future<?>形式类型、并返回 null作为底层任务的结果。

    方法摘要

     boolean

    cancel(boolean mayInterruptIfRunning)           试图取消对此任务的执行。

     V

    get()           如有必要,等待计算完成,然后获取其结果。

     V

    get(long timeout,TimeUnit unit)           如有必要,最多等待为使计算完成所给定的时间之后,获取其结果(如果结果可用)。

     boolean

    isCancelled()           如果在任务正常完成前将其取消,则返回 true

     boolean

    isDone()           如果任务已完成,则返回 true

     

    public interface CompletionService<V>

           CompletionService用于提交一组Callable任务,其take方法返回一个已完成的Callable任务对应的Future对象。好比同时种几块麦子等待收割,收割时哪块先熟先收哪块。

    将生产新的异步任务与使用已完成任务的结果分离开来的服务。生产者 submit执行的任务。使用者 take已完成的任务,并按照完成这些任务的顺序处理它们的结果。例如,CompletionService可以用来管理异步 IO,执行读操作的任务作为程序或系统的一部分提交,然后,当完成读操作时,会在程序的不同部分执行其他操作,执行操作的顺序可能与所请求的顺序不同。

    通常,CompletionService依赖于一个单独的Executor来实际执行任务,在这种情况下,CompletionService只管理一个内部完成队列。ExecutorCompletionService类提供了此方法的一个实现。

    CompletionService方法摘要

     Future<V>

    poll()           获取并移除表示下一个已完成任务的 Future,如果不存在这样的任务,则返回 null

     Future<V>

    poll(long timeout,TimeUnit unit)          获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则将等待指定的时间(如果有必要)。

     Future<V>

    submit(Callable<V> task)           提交要执行的值返回任务,并返回表示挂起的任务结果的 Future

     Future<V>

    submit(Runnable task,V result)           提交要执行的 Runnable任务,并返回一个表示任务完成的 Future,可以提取或轮询此任务。

     Future<V>

    take()           获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。

    ExecutorCompletionService构造方法摘要

    ExecutorCompletionService(Executor executor)
              
    使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将LinkedBlockingQueue作为完成队列。

     

    ExecutorCompletionService(Executor executor,BlockingQueue<Future<V>> completionQueue)
              
    使用为执行基本任务而提供的执行程序创建一个 ExecutorCompletionService,并将所提供的队列作为其完成队列。

     

    示例:

    ExecutorService threadPool = Executors.newFixedThreadPool(10);   //创建线程池,传递给coms

           threadPool执行任务,执行的任务返回结果都是整数

    CompletionService<Integer> coms = new ExecutorCompletionService<Integer>(threadPool);

           提交10个任务 种麦子

    for (int i=0; i<10; i++)

    {

           final int num = i+1;

    coms.submit(new Callable<Integer>(){

    public Integer call()       覆盖call方法

    {匿名内部类使用外部变量要用final修饰

           SOP(任务+num);

           Thread.sleep(new Random().nextInt(6)*1000);

           return num;

    }

    });

    }

           等待收获      割麦子

    for (int i=0; i<10; i++)

    {     take获取第一个Future对象,用get获取结果

           SOP(coms.take().get());

    }

     

    11. java5的线程锁技术

    java.util.concurrent.locks         为锁和等待条件提供一个框架的接口和类,

    接口摘要

    Condition

    Condition Object监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 setwait-set)。

    Lock

    Lock 实现提供了比使用 synchronized方法和语句可获得的更广泛的锁定操作。

    ReadWriteLock

    ReadWriteLock 维护了一对相关的,一个用于只读操作,另一个用于写入操作。

    类摘要

    AbstractOwnableSynchronizer

    可以由线程以独占方式拥有的同步器。

    AbstractQueuedLongSynchronizer

    long 形式维护同步状态的一个 AbstractQueuedSynchronizer 版本。

    AbstractQueuedSynchronizer

    为实现依赖于先进先出 (FIFO)等待队列的阻塞锁和相关同步器(信号量、事件,等等)提供一个框架。

    LockSupport

    用来创建锁和其他同步类的基本线程阻塞原语。

    ReentrantLock

    一个可重入的互斥锁Lock,它具有与使用 synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

    ReentrantReadWriteLock

    支持与 ReentrantLock 类似语义的ReadWriteLock实现。

    ReentrantReadWriteLock.ReadLock

    ReentrantReadWriteLock.readLock()方法返回的锁。

    ReentrantReadWriteLock.WriteLock

    ReentrantReadWriteLock.writeLock()方法返回的锁。

           Lock比传统线程模型中的synchronized更加面向对象,锁本身也是一个对象,两个线程执行的代码要实现同步互斥效果,就要使用同一个锁对象。锁要上在要操作的资源类的内部方法中,而不是线程代码中。

    public interface Lock

    所有已知实现类:

    ReentrantLock,ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock

    随着灵活性的增加,也带来了更多的责任。不使用块结构锁就失去了使用 synchronized 方法和语句时会出现的锁自动释放功能。在大多数情况下,应该使用以下语句:

         Lock l = ...;

         l.lock();

         try {

             // access the resource protected by this lock

         } finally {

             l.unlock();

         }

    锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally try-catch加以保护,以确保在必要时释放锁。

    方法摘要

     void

    lock()           获取锁。

     void

    lockInterruptibly()           如果当前线程未被中断,则获取锁。

     Condition

    newCondition()           返回绑定到此 Lock 实例的新 Condition 实例。

     boolean

    tryLock()           仅在调用时锁为空闲状态才获取该锁。

     boolean

    tryLock(long time,TimeUnit unit)           如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。

     void

    unlock()           释放锁。

    Locksynchronized对比,打印字符串例子

     

     

    12. java5读写锁技术的妙用

           读写锁,分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,由JVM控制。

    ReentrantReadWriteLock

    构造方法摘要

    ReentrantReadWriteLock()           使用默认(非公平)的排序属性创建一个新的 ReentrantReadWriteLock

    ReentrantReadWriteLock(boolean fair)           使用给定的公平策略创建一个新的 ReentrantReadWriteLock

    方法摘要

    protected  Thread

    getOwner()           返回当前拥有写入锁的线程,如果没有这样的线程,则返回 null

    protected  Collection<Thread>

    getQueuedReaderThreads()           返回一个 collection,它包含可能正在等待获取读取锁的线程。

    protected  Collection<Thread>

    getQueuedThreads()           返回一个 collection,它包含可能正在等待获取读取或写入锁的线程。

    protected  Collection<Thread>

    getQueuedWriterThreads()           返回一个 collection,它包含可能正在等待获取写入锁的线程。

     int

    getQueueLength()           返回等待获取读取或写入锁的线程估计数目。

     int

    getReadHoldCount()           查询当前线程在此锁上保持的重入读取锁数量。

     int

    getReadLockCount()           查询为此锁保持的读取锁数量。

    protected  Collection<Thread>

    getWaitingThreads(Condition condition)           返回一个 collection,它包含可能正在等待与写入锁相关的给定条件的那些线程。

     int

    getWaitQueueLength(Condition condition)           返回正等待与写入锁相关的给定条件的线程估计数目。

     int

    getWriteHoldCount()           查询当前线程在此锁上保持的重入写入锁数量。

     boolean

    hasQueuedThread(Thread thread)           查询是否给定线程正在等待获取读取或写入锁。

     boolean

    hasQueuedThreads()           查询是否所有的线程正在等待获取读取或写入锁。

     boolean

    hasWaiters(Condition condition)           查询是否有些线程正在等待与写入锁有关的给定条件。

     boolean

    isFair()           如果此锁将公平性设置为 ture,则返回 true

     boolean

    isWriteLocked()           查询是否某个线程保持了写入锁。

     boolean

    isWriteLockedByCurrentThread()           查询当前线程是否保持了写入锁。

     ReentrantReadWriteLock.ReadLock

    readLock()           返回用于读取操作的锁。

     String

    toString()           返回标识此锁及其锁状态的字符串。

     ReentrantReadWriteLock.WriteLock

    writeLock()           返回用于写入操作的锁。

    三个线程读数据,三个线程写数据示例:

    可以同时读,读的时候不能写,不能同时写,写的时候不能读

    读的时候上读锁,读完解锁;写的时候上写锁,写完解锁。注意finally解锁

    package cn.itheima;

     

    import java.util.Random;

    import java.util.concurrent.locks.ReadWriteLock;

    import java.util.concurrent.locks.ReentrantReadWriteLock;

     

    public class ReadWriteLockDemo

    {

           /**读写所使用

            * 三个线程读,三个线程写

            */

           public static void main(String[] args)

           {

                  //共享对象

                  final Source source = new Source();

                  //创建线程

                  for (int i=0; i<3; i++)

                  {

                         //

                         new Thread(new Runnable()

                         {

                                public void run()

                                {

                                       while (true)

                                              source.get();

                                }

                         }).start();

                         //

                         new Thread(new Runnable()

                         {

                                public void run()

                                {

                                       while (true)

                                              source.put(new Random().nextInt(999));

                                }

                         }).start();

                  }

           }

     

           static class Source

           {

                  //共享数据

                  private int data = 0;

                  //要操作同一把锁上的读或写锁

                  ReadWriteLock rwl = new ReentrantReadWriteLock();

                 

                  //读方法

                  public void get()

                  {

                         //上读锁

                        rwl.readLock().lock();

                         try

                         {

                                //获取数据并输出

                                System.out.println("读——"+Thread.currentThread().getName()+"正在获取数据。。。");

                                try

                                {

                                       Thread.sleep(new Random().nextInt(6)*1000);

                                } catch (InterruptedException e)

                                {

                                       e.printStackTrace();

                                }

                                System.out.println("读——"+Thread.currentThread().getName()+"获取到的数据:"+data);

                         }finally

                         {

                                //解锁

                               rwl.readLock().unlock();

                         }                  

                  }

                  //写方法

                  public void put(int data)

                  {

                         //上写锁

                         rwl.writeLock().lock();

                         try

                         {

                                //提示信息

                                System.out.println("写——"+Thread.currentThread().getName()+"正在改写数据。。。");

                                try

                                {

                                       Thread.sleep(new Random().nextInt(6)*1000);

                                } catch (InterruptedException e)

                                {

                                       e.printStackTrace();

                                }

                                this.data = data;

                                System.out.println("写——"+Thread.currentThread().getName()+"已将数据改写为:"+data);

                         }finally

                         {

                                //解锁

                                rwl.writeLock().unlock();

                         }                  

                  }

           }

    }

    JDK帮助文档中的示例用法。下面的代码展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理):

     class CachedData {

       Object data;
       volatile boolean cacheValid;    数据有没有标记
       ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
       void processCachedData() {处理数据
         rwl.readLock().lock();先上读锁
         if (!cacheValid) {如果数据不存在
            // Must release read lock before acquiring write lock
            rwl.readLock().unlock();准备写数据,需先解除读锁
            rwl.writeLock().lock();上写锁
            // Recheck state because another thread might have acquired
            //   write lock and changed state before we did.
            if (!cacheValid) {再次检查数据是否存在,防止其他线程已经存入数据
              data = ...
              cacheValid = true;写好数据,改变标记
            }
            // Downgrade by acquiring read lock before releasing write lock
            准备释放写锁,数据存在了,释放后就要使用数据,恢复产生数据前的读锁状态
             rwl.readLock().lock();
            rwl.writeLock().unlock(); // Unlock write, still hold read
         }
     
         use(data);存在直接使用数据
         rwl.readLock().unlock();解除读锁
       }
     }

     

    面试题:设计一个缓存系统

           缓存系统:你要取数据,需调用我的public Object getData(String key)方法,我要检查我内部有没有这个数据,如果有就直接返回,如果没有,就从数据库中查找这个数,查到后将这个数据存入我内部的存储器中,下次再有人来要这个数据,我就直接返回这个数不用再到数据库中找了。             你要取数据不要找数据库,来找我。

    class CachedSystem

    {     缓存系统的存储器

           private Map<String, Object> cache = new HashMap<String, Object>();

           取数据方法   可能有多个线程来取数据,没有数据的话又会去数据库查询,需要互斥

           public synchronized Object get(String key)

           {     先查询内部存储器中有没有要的值

                  Object value = cache.get(key);

                  if (value==null)如果没有,就去数据库中查询,并将查到的结果存入内部存储器中

                  {

                         value =aaaa;实际代码是查询后的结果 queryDB(key)

                         cache.put(key, value);

    }

    return value;

    }

    }

    上面的代码每次只能有一个线程来查询,但只有写的时候才需要互斥,修改如下

    来一个读写锁

    ReadWriteLock rwl = new ReentrantReadWriteLock();

    public Object get(String key)

    {    

           上读锁

           rwl.readLock().lock();

    先查询内部存储器中有没有要的值

           Object value = cache.get(key);

           if (value==null)如果没有,就去数据库中查询,并将查到的结果存入内部存储器中

           {

                  释放读锁  上写锁

                  rwl.readLock().unlock();

                  rwl.writeLock().lock();

                  if (value==null)再次进行判断,防止多个写线程堵在这个地方重复写

                  {

                         value =aaaa;

                         cache.put(key, value);

                  }

    设置完成释放写锁,恢复读写状态

                  rwl.readLock().lock();

                  rwl.writeLock().unlock();

    }

    释放读锁

    rwl.readLock().unlock();

    return value;                                                                 注意:try finallyunlock

    }

     

    13. java5条件阻塞Condition的应用

           Condition的功能类似在传统线程技术中的Object.wait()Object.natify()的功能,传统线程技术实现的互斥只能一个线程单独干,不能说这个线程干完了通知另一个线程来干,Condition就是解决这个问题的,实现线程间的通信。比如CPU让小弟做事,小弟说我先歇着并通知大哥,大哥就开始做事。

    public interface Condition

    Condition Object监视器方法(waitnotifynotifyAll)分解成截然不同的对象,以便通过将这些对象与任意Lock实现组合使用,为每个对象提供多个等待 setwait-set)。其中,Lock替代了 synchronized方法和语句的使用,Condition替代了 Object监视器方法的使用。

    Condition 实例实质上被绑定到一个锁上。要为特定Lock实例获得 Condition 实例,请使用其newCondition()方法。

    作为一个示例,假定有一个绑定的缓冲区,它支持 put take方法。如果试图在空的缓冲区上执行 take操作,则在某一个项变得可用之前,线程将一直阻塞;如果试图在满的缓冲区上执行 put操作,则在有空间变得可用之前,线程将一直阻塞。我们喜欢在单独的等待 set中保存 put线程和 take线程,这样就可以在缓冲区中的项或空间变得可用时利用最佳规划,一次只通知一个线程。可以使用两个Condition实例来做到这一点。

     class BoundedBuffer {阻塞队列满了不能放,空了不能取

       final Lock lock = new ReentrantLock();

       final Condition notFull  = lock.newCondition();

       final Condition notEmpty = lock.newCondition();

     

       final Object[] items = new Object[100];

       int putptr, takeptr, count;

     

       public void put(Object x) throws InterruptedException {

         lock.lock();

         try {

           while (count == items.length)

             notFull.await();

           items[putptr] = x;

           if (++putptr == items.length) putptr = 0;

           ++count;

           notEmpty.signal();

         } finally {

           lock.unlock();

         }

       }

     

       public Object take() throws InterruptedException {

         lock.lock();

         try {

           while (count == 0)

             notEmpty.await();

           Object x = items[takeptr];

           if (++takeptr == items.length) takeptr = 0;

           --count;

           notFull.signal();

           return x;

         } finally {

           lock.unlock();

         }

       }

     }

    使用方法:

    Lock lock = new ReentrantLock();

    Condition condition = lock.newCondition();

    this.wait()àcondition.await()

    this.notify()àcondition.signal()

    注意:判断条件时用while防止虚假唤醒,等待在那里,唤醒后再进行判断,确认符合要求后再执行任务。

     

    14. java5Semaphore同步工具

           Semaphore可以维护当前访问自身的线程个数,并且提供了同步机制。

           semaphore实现的功能类似于厕所里有5个坑,有10个人要上厕所,同时就只能有5个人占用,当5个人中的任何一个让开后,其中在等待的另外5个人中又有一个可以占用了。

    java.util.concurrent.Semaphore

    一个计数信号量。从概念上讲,信号量维护了一个许可集。如有必要,在许可可用前会阻塞每一个acquire(),然后再获取该许可。每个release()添加一个许可,从而可能释放一个正在阻塞的获取者。但是,不使用实际的许可对象,Semaphore只对可用许可的号码进行计数,并采取相应的行动。

    Semaphore 通常用于限制可以访问某些资源(物理或逻辑的)的线程数目。例如,下面的类使用信号量控制对内容池的访问:

     class Pool {

       private static final int MAX_AVAILABLE = 100;

       private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);

       public Object getItem() throws InterruptedException {

         available.acquire();

         return getNextAvailableItem();

       }

       public void putItem(Object x) {

         if (markAsUnused(x))

           available.release();

       }

       // Not a particularly efficient data structure; just for demo

       protected Object[] items = ... whatever kinds of items being managed

       protected boolean[] used = new boolean[MAX_AVAILABLE];

     

       protected synchronized Object getNextAvailableItem() {

         for (int i = 0; i < MAX_AVAILABLE; ++i) {

           if (!used[i]) {

              used[i] = true;

              return items[i];

           }

         }

         return null; // not reached

       }

       protected synchronized boolean markAsUnused(Object item) {

         for (int i = 0; i < MAX_AVAILABLE; ++i) {

           if (item == items[i]) {

              if (used[i]) {

                used[i] = false;

                return true;

              } else

                return false;

           }

         }

         return false;

       }

     }

    获得一项前,每个线程必须从信号量获取许可,从而保证可以使用该项。该线程结束后,将项返回到池中并将许可返回到该信号量,从而允许其他线程获取该项。注意,调用acquire()时无法保持同步锁,因为这会阻止将项返回到池中。信号量封装所需的同步,以限制对池的访问,这同维持该池本身一致性所需的同步是分开的。

    构造方法摘要

    Semaphore(int permits)           创建具有给定的许可数和非公平的公平设置的 Semaphore

    Semaphore(int permits, boolean fair)           创建具有给定的许可数和给定的公平设置的 Semaphore

    方法摘要

     void

    acquire()           从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断

     void

    acquire(int permits)           从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞,或者线程已被中断

     void

    acquireUninterruptibly()           从此信号量中获取许可,在有可用的许可前将其阻塞。

     void

    acquireUninterruptibly(int permits)           从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

     int

    availablePermits()           返回此信号量中当前可用的许可数。

     int

    drainPermits()           获取并返回立即可用的所有许可。

    protected  Collection<Thread>

    getQueuedThreads()           返回一个 collection,包含可能等待获取的线程。

     int

    getQueueLength()           返回正在等待获取的线程的估计数目。

     boolean

    hasQueuedThreads()           查询是否有线程正在等待获取。

     boolean

    isFair()           如果此信号量的公平设置为 true,则返回 true

    protected  void

    reducePermits(int reduction)           根据指定的缩减量减小可用许可的数目。

     void

    release()           释放一个许可,将其返回给信号量。

     void

    release(int permits)           释放给定数目的许可,将其返回到信号量。

     String

    toString()           返回标识此信号量的字符串,以及信号量的状态。

     boolean

    tryAcquire()          仅在调用时此信号量存在一个可用许可,才从信号量获取许可。

     boolean

    tryAcquire(int permits)           仅在调用时此信号量中有给定数目的许可时,才从此信号量中获取这些许可。

     boolean

    tryAcquire(int permits, long timeout,TimeUnit unit)           如果在给定的等待时间内此信号量有可用的所有许可,并且当前线程未被中断,则从此信号量获取给定数目的许可。

     boolean

    tryAcquire(long timeout,TimeUnit unit)           如果在给定的等待时间内,此信号量有可用的许可并且当前线程未被中断,则从此信号量获取一个许可。

    示例:3个坑 10个人

    厕所,有多少人都能装,线程数动态变化,来一个人产生一个线程

    ExecutorService service = Exccutors.newCachedThreadPool();

    final Semaphore sp =new Semaphore(3);厕所中坑的个数 指定只有3

           3个坑,来了5个人,有2个人要等,其中有一个办完事走了,等待的2个哪个先上呢?默认的构造方法不管,谁抢到了谁上。new Semaphore(3, true)就可以保证先来的先上。

    将坑的个数设置为1就可以达到互斥效果,每次只能有一个线程运行

    for (int i=0; i<10; i++)来了10个人

    {人的任务 抢坑

           Runnable runnable = new Runnable()

           {

           public void run()

           {

           sp.acquire();抢坑了会抛中断异常

    }有人占住坑了,给出提示

    SOP(currentThreadName+进入,当前已有(3-sp.availablePermits())个人了)

    Thread.sleep(5000)蹲坑办事

    办完事打声招呼

    SOPThreadName即将离开)

    释放坑的占有权

    sp.release();

    SOP(ThreadName已经走了,还有sp.availablePermits()个坑可用)

    }

           开始任务吧

           service.execute(runnable)

    }

    传统互斥只能内部释放锁this.unlock(),进去this.lock()晕倒了别人就没法进去了;用信号灯可以外部释放,其他线程可以释放再获取sp.release()  sp.acquire()

     

    15. java5CyclicBarrier同步工具

           例如:组织人员(线程)郊游,约定一个时间地点(路障),人员陆续到达地点,等所有人员全部到达,开始到公园各玩各的,再到约定时间去食堂吃饭,等所有人到齐开饭……

    java.util.concurrent.CyclicBarrier

    一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 barrier

    CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作很有用。

    构造方法摘要

    CyclicBarrier(int parties)           创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,但它不会在启动 barrier时执行预定义的操作。

    CyclicBarrier(int parties,Runnable barrierAction)           创建一个新的 CyclicBarrier,它将在给定数量的参与者(线程)处于等待状态时启动,并在启动 barrier时执行给定的屏障操作,该操作由最后一个进入 barrier 的线程执行。

    方法摘要

     int

    await()     在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。

     int

    await(long timeout,TimeUnit unit)           在所有参与者都已经在此屏障上调用 await方法之前将一直等待,或者超出了指定的等待时间。

     int

    getNumberWaiting()           返回当前在屏障处等待的参与者数目。

     int

    getParties()           返回要求启动此 barrier 的参与者数目。

     boolean

    isBroken()           查询此屏障是否处于损坏状态。

     void

    reset()           将屏障重置为其初始状态。

    例:

    ExecutorService service = Executors.newCachedThreadPool();

    final CyclicBarrier cb = new CyclicBarrier(3); 约定3个人

    for (int i=0; i<3; i++)产生3个人

    {     每个人的任务

           Runnable runnable = newRunnable()

           {

                  public void run()

                  {     开始出发到目的地

           Thread.sleep((long)Math.random()*1000);

           SOP(ThreadName即将到达集合点1

    当前已有cb.getNumberWaiting()+1

    + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"))

           cb.await();到了其他人没来就等

           人到齐了再继续进行

           Thread.sleep((long)Math.random()*1000);

           SOP(ThreadName即将到达集合点2)

           cb.await();到了其他人没来就等

     

    }

    }

    service.execute(runnable);

    }

     

    16. java5CountDownLatch同步工具

           好像倒计时计数器,调用CountDownLatch对象的countDown方法就将计数器减1,当到达0时,所有等待者就开始执行。

           举例:多个运动员等待裁判命令:   裁判等所有运动员到齐后发布结果

    代码示例:

    ExecutorService service = Executors.newCachedThreadPool();

    裁判发布命令的计数器,计数器为0,运动员就跑

    final CountDownLatch cdOrder = new CountDownLatch(1);     

    运动员跑到终点的计数器,为0裁判宣布结果

    final CountDownLatch cdAnswer = new CountDownLatch(3);

    产生3个运动员

    for (int i=0; i<3; i++)

    {     运动员的任务

           Runnable runnable = new Runnable(){

    public void run()

    {

           SOP(ThreadName准备接受命令)

           等待发布命令

           cdOrder.await();    计数器为0继续向下执行

           SOP(ThreadName已接受命令)    order计数器为0

           Thread.sleep(Random);开始跑步

           cdAnswer.countDown();跑到终点了,计数器减1

    }

    };

           service.execute(runnable);运动员开始任务

    }

    Thread.sleep(1000)裁判休息一会再发布命令

    SOP(即将发布命令)

    cdOrder.countDown();命令计数器置为0,发布命令

    SOP(命令已经发布,等待结果)

    cdAnswer.await(); 等待所有运动员,计数器为0 所有运动员到位

    SOP(宣布结果)

     

    java.util.concurrent.CountDownLatch

    一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。用给定的计数初始化 CountDownLatch。由于调用了countDown()方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,await的所有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用CyclicBarrier

    CountDownLatch 是一个通用同步工具,它有很多用途。将计数 1 初始化的 CountDownLatch用作一个简单的开/关锁存器,或入口:在通过调用countDown()的线程打开入口前,所有调用await的线程都一直在入口处等待。用N初始化的 CountDownLatch 可以使一个线程在N个线程完成某项操作之前一直等待,或者使其在某项操作完成 N次之前一直等待。

    CountDownLatch 的一个有用特性是,它不要求调用 countDown 方法的线程等到计数到达零时才继续,而在所有线程都能通过之前,它只是阻止任何线程继续通过一个await

    构造方法摘要

    CountDownLatch(int count)           构造一个用给定计数初始化的 CountDownLatch

    方法摘要

     void

    await()           使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断

     boolean

    await(long timeout,TimeUnit unit)           使当前线程在锁存器倒计数至零之前一直等待,除非线程被中断或超出了指定的等待时间。

     void

    countDown()           递减锁存器的计数,如果计数到达零,则释放所有等待的线程。

     long

    getCount()           返回当前计数。

     String

    toString()           返回标识此锁存器及其状态的字符串。

     

    17. java5Exchanger同步工具

           用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人会一直等待第二个人,直到第二个人拿着数据到来时,才能彼此交换数据。

    举例:毒品交易 双方并不是同时到达,有先有后,只有都到达了,瞬间交换数据,各自飞

    代码演示:

    ExecutorService service = Executors.newCachedThreadPool();

    final Exchanger exchanger = new Exchanger();

           毒贩:

    service.execute(new Runnable()

    {     毒贩做的事

           public void run()

           {

           String(毒品) data1 =毒品

           SOP(毒贩正在将data1换出去)

           Thread.sleep(Random)换的过程

           毒贩到位了,拿着毒品等待毒人接头,接头后就能换到钱了

           String data2 = (String)exchanger.exchange(data1);

           SOP(毒贩换到了钱:data2)

    }

    });

           毒人:

    service.execute(new Runnable()

    {     吸毒人做的事

           public void run()

           {

           String(钱) data1 =

           SOP(毒人正在将data1换出去)

           Thread.sleep(Random)换的过程

           吸毒人到位了,拿着钱等待毒贩接头,接头后就能换到毒品了

           String data2 = (String)exchanger.exchange(data1);

           SOP(毒人换到了毒品:data2)

    }

    });

    java.util.concurrent.Exchanger<V> V -可以交换的对象类型

    可以在对中对元素进行配对和交换的线程的同步点。每个线程将条目上的某个方法呈现给exchange方法,与伙伴线程进行匹配,并且在返回时接收其伙伴的对象。Exchanger可能被视为SynchronousQueue的双向形式。Exchanger可能在应用程序(比如遗传算法和管道设计)中很有用。

    用法示例:以下是重点介绍的一个类,该类使用 Exchanger在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获取一个新腾空的缓冲区,并将填满的缓冲区传递给腾空缓冲区的线程。

    class FillAndEmpty {

       Exchanger<DataBuffer> exchanger = new Exchanger<DataBuffer>();

       DataBuffer initialEmptyBuffer = ... a made-up type

       DataBuffer initialFullBuffer = ...

       class FillingLoop implements Runnable {

         public void run() {

           DataBuffer currentBuffer = initialEmptyBuffer;

           try {

             while (currentBuffer != null) {

               addToBuffer(currentBuffer);

               if (currentBuffer.isFull())

                 currentBuffer = exchanger.exchange(currentBuffer);

             }

           } catch (InterruptedException ex) { ... handle ... }

         }

       }

       class EmptyingLoop implements Runnable {

         public void run() {

           DataBuffer currentBuffer = initialFullBuffer;

           try {

             while (currentBuffer != null) {

               takeFromBuffer(currentBuffer);

               if (currentBuffer.isEmpty())

                 currentBuffer = exchanger.exchange(currentBuffer);

             }

           } catch (InterruptedException ex) { ... handle ...}

         }

       }

       void start() {

         new Thread(new FillingLoop()).start();

         new Thread(new EmptyingLoop()).start();

       }

      }

    构造方法摘要

    Exchanger()           创建一个新的 Exchanger

    方法摘要

     V

    exchange(V x)           等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。

     V

    exchange(V x, long timeout, TimeUnit unit)           等待另一个线程到达此交换点(除非当前线程被中断,或者超出了指定的等待时间),然后将给定的对象传送给该线程,同时接收该线程的对象。

     

    18. java5阻塞队列的应用

           队列包含固定长度的队列和不固定长度的队列,先进先出

    固定长度的队列往里放数据,如果放满了还要放,阻塞式队列就会等待,直到有数据取出,空出位置后才继续放;非阻塞式队列不能等待就只能报错了。

           Condition时提到了阻塞队列的原理,Java中已经实现了阻塞队列ArrayBlockingQueue

    BlockingQueue<E>       public interface BlockingQueue<E>extendsQueue<E>

    支持两个附加操作的Queue,这两个操作是:获取元素时等待队列变为非空,以及存储元素时等待空间变得可用。

    BlockingQueue 方法以四种形式出现,对于不能立即满足但可能在将来某一时刻可以满足的操作,这四种形式的处理方式不同:第一种是抛出一个异常,第二种是返回一个特殊值(null false,具体取决于操作),第三种是在操作可以成功前,无限期地阻塞当前线程,第四种是在放弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法:

     

    抛出异常

    特殊值

    阻塞

    超时

    插入

    add(e)

    offer(e)

    put(e)

    offer(e, time, unit)

    移除

    remove()

    poll()

    take()

    poll(time, unit)

    检查

    element()

    peek()

    不可用

    不可用

    BlockingQueue 不接受 null 元素。试图 addput offer一个 null元素时,某些实现会抛出 NullPointerExceptionnull被用作指示 poll操作失败的警戒值。

    BlockingQueue 可以是限定容量的。它在任意给定时间都可以有一个 remainingCapacity,超出此容量,便无法无阻塞地 put附加元素。没有任何内部容量约束的 BlockingQueue总是报告 Integer.MAX_VALUE 的剩余容量。

    BlockingQueue 实现主要用于生产者-使用者队列,但它另外还支持Collection接口。因此,举例来说,使用 remove(x) 从队列中移除任意一个元素是有可能的。然而,这种操作通常会有效执行,只能有计划地偶尔使用,比如在取消排队信息时。

    BlockingQueue 实现是线程安全的。所有排队方法都可以使用内部锁或其他形式的并发控制来自动达到它们的目的。然而,大量的 Collection操作(addAllcontainsAllretainAll removeAll没有必要自动执行,除非在实现中特别说明。因此,举例来说,在只添加了 c中的一些元素后,addAll(c)有可能失败(抛出一个异常)。

     

    java.util.concurrent.ArrayBlockingQueue<E>     E -在此 collection中保持的元素类型

    extends AbstractQueue<E>implementsBlockingQueue<E>,Serializable

    一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列的头部是在队列中存在时间最长的元素。队列的尾部是在队列中存在时间最短的元素。新元素插入到队列的尾部,队列获取操作则是从队列头部开始获得元素。

    这是一个典型的有界缓存区,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。

    此类支持对等待的生产者线程和使用者线程进行排序的可选公平策略。默认情况下,不保证是这种排序。然而,通过将公平性 (fairness)设置为 true而构造的队列允许按照 FIFO顺序访问线程。公平性通常会降低吞吐量,但也减少了可变性和避免了不平衡性

    此类及其迭代器实现了CollectionIterator接口的所有可选方法。

    此类是 Java Collections Framework 的成员。

    构造方法摘要

    ArrayBlockingQueue(int capacity)           创建一个带有给定的(固定)容量和默认访问策略的 ArrayBlockingQueue

    ArrayBlockingQueue(int capacity, boolean fair)           创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue

    ArrayBlockingQueue(int capacity, boolean fair,Collection<? extendsE> c)           创建一个具有给定的(固定)容量和指定访问策略的 ArrayBlockingQueue,它最初包含给定 collection的元素,并以 collection迭代器的遍历顺序添加元素。

    方法摘要

     boolean

    add(E e)           将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则抛出 IllegalStateException

     void

    clear()           自动移除此队列中的所有元素。

     boolean

    contains(Object o)           如果此队列包含指定的元素,则返回 true

     int

    drainTo(Collection<? super E> c)           移除此队列中所有可用的元素,并将它们添加到给定 collection 中。

     int

    drainTo(Collection<? super E> c, int maxElements)           最多从此队列中移除给定数量的可用元素,并将这些元素添加到给定 collection 中。

     Iterator<E>

    iterator()           返回在此队列中的元素上按适当顺序进行迭代的迭代器。

     boolean

    offer(E e)           将指定的元素插入到此队列的尾部(如果立即可行且不会超过该队列的容量),在成功时返回 true,如果此队列已满,则返回 false

     boolean

    offer(E e, long timeout, TimeUnit unit)           将指定的元素插入此队列的尾部,如果该队列已满,则在到达指定的等待时间之前等待可用的空间。

     E

    peek()           获取但不移除此队列的头;如果此队列为空,则返回 null

     E

    poll()           获取并移除此队列的头,如果此队列为空,则返回 null

     E

    poll(long timeout,TimeUnit unit)           获取并移除此队列的头部,在指定的等待时间前等待可用的元素(如果有必要)。

     void

    put(E e)           将指定的元素插入此队列的尾部,如果该队列已满,则等待可用的空间。

     int

    remainingCapacity()           返回在无阻塞的理想情况下(不存在内存或资源约束)此队列能接受的其他元素数量。

     boolean

    remove(Object o)           从此队列中移除指定元素的单个实例(如果存在)。

     int

    size()           返回此队列中元素的数量。

     E

    take()  获取并移除此队列的头部,在元素变得可用之前一直等待(如果有必要)。

     Object[]

    toArray()           返回一个按适当顺序包含此队列中所有元素的数组。

    <T> T[]

    toArray(T[] a)           返回一个按适当顺序包含此队列中所有元素的数组;返回数组的运行时类型是指定数组的运行时类型。

     String

    toString()           返回此 collection 的字符串表示形式。

    阻塞队列的实现原理(Condition锁中有提到await signal

     

    19. java5同步集合类的应用

           传统集合实现同步的问题

           举了一个例子:Map集合线程不同步导致的问题。

           解决办法:使用同步的Map集合     使用集合工具类中的方法将不同步的集合转为同步的Collections.synchronizedMap(new Map())这个方法返回一个同步的集合

           public static <K, V> Map<K, V> synchronizedMap(Map<K, V> m)

    {return new SynchronizedMap<K, V>(m);}

    SynchronizedMap类相当于一个代理类,通过查看源代码发现:该类中的所有方法都是直接返回:原Map集合方法调用后的结果,只是将返回结果的代码放在了同步代码块中以实现同步,构造是将同步锁默认置为当前对象。

    HashSetHashMap的关系与区别:

           HashSet是单列的,HashMap是双列的(键值对)

           关系:HashSet内部使用的是HashMap中的键,不考虑值。

    查看HashSet的源代码发现其内部就是用HashMap实现的,只是没有使用HashMapV,只使用了它的K

     

    JDK1.5中提供了并发 Collection:提供了设计用于多线程上下文中的 Collection实现:

    ConcurrentHashMapConcurrentSkipListMapConcurrentSkipListSetCopyOnWriteArrayListCopyOnWriteArraySet。当期望许多线程访问一个给定 collection时,ConcurrentHashMap 通常优于同步的 HashMapConcurrentSkipListMap通常优于同步的 TreeMap。当期望的读数和遍历远远大于列表的更新数时,CopyOnWriteArrayList优于同步的 ArrayList

    ConcurrentSkipListMap<K,V>映射可以根据键的自然顺序进行排序,也可以根据创建映射时所提供的Comparator进行排序,具体取决于使用的构造方法。

    ConcurrentSkipListSet<E>一个基于ConcurrentSkipListMap的可缩放并发NavigableSet实现。set的元素可以根据它们的自然顺序进行排序,也可以根据创建 set时所提供的Comparator进行排序,具体取决于使用的构造方法

    CopyOnWriteArrayList<E>ArrayList的一个线程安全的变体,其中所有可变操作(addset等等)都是通过对底层数组进行一次新的复制来实现的。

    这一般需要很大的开销,但是当遍历操作的数量大大超过可变操作的数量时,这种方法可能比其他替代方法有效。在不能或不想进行同步遍历,但又需要从并发线程中排除冲突时,它也很有用。

    CopyOnWriteArraySet<E>对其所有操作使用内部CopyOnWriteArrayListSet。因此,它共享以下相同的基本属性:

    它最适合于具有以下特征的应用程序:set大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。它是线程安全的。因为通常需要复制整个基础数组,所以可变操作(addset remove 等等)的开销很大。迭代器不支持可变 remove操作。使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。

     

           传统集合中存在的其它问题:对集合迭代时,不能对集合中的元素进行修改(添加、删除……),Java5中提供的并发集合就解决了这个问题。

     

    20.空中网挑选实习生的面试题1

           现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:

    public class Test1

    {

           public static void main(String[] args)

           {

           SOP(begin:+sys.currentTimeMillis()/1000);

           //模拟处理16行日志,下面的代码产生16个日志对象,需运行16秒才能打印完

           //修改程序代码,开4个线程让这16个日志在4秒钟打完

           for (iint i=0; i<16; i++) //这行代码不能改动

           {

           final String log = “”+(i+1);   //这行代码不能改动

           {

           Test1.parseLog(log);

    }

    }

    }

    //parseLog方法内部代码不能改动

    public static void parseLog(String log)

    {

           SOP(log+””+(sys.currentTimeMillis()/1000));

           try

           {

           Thread.sleep(1000);

    }

           catch(InterruptedException e)

           {

           e.printStackTrace();

    }

    }

    }

    刚看到题目还想着很简单;直接在Test.parseLog(log)的地方new4个线程,都执行打印任务即可,仔细一看不行,在这里new4个线程的话就是16*4个线程了,所以要将线程在for循环外边创建出来,for内部将产生的日志对象装在一个共享变量里,在线程内部从共享变量中取数据打印。要考虑线程同步互斥问题,这个共享变量要具备同步功能,可以使用ArrayBlockingQueue这种阻塞式队列来存储日志对象。也可以使用普通集合,但拿数据要考虑同步问题,可能会浪费时间。

    for循环外部创建线程,定义共享变量

    final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(16);

           for (int i=0; i<4; i++)   创建4个线程

           {

           new Thread(new Runnable()

    {

           public void run()

           {

           while (true)

           {     先从集合中取到日志对象,再打印

           String log = queue.take();    要处理异常

           parseLog(log);

    }

    }

    }).start();

    }

    16for循环内部将产生的日志对象装入共享变量中

    queue.put(log);     要处理异常

    这道题只要用到同步集合来共享数据就可以了  List集合的Vector也可以

     

    21.空中网挑选实习生的面试题2

           现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理。就好像生产者不断地产生数据,消费者不断地消费数据。请将程序改造成有10个线程来消费生产者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有序的。原始代码如下:

    public class Test2

    {

           public static void main(String[] args)

           {

           SOP(begin+sys.currentTimeMillis()/1000);

           for (int i=0; i<10; i++)  //这行不能改动

           {

           String input = i+””;       //这行不能改动

           String output = TeatDo.doSome(input);

           SOP(ThreadName+output);

    }

    }

    }

    //不能改动此TestDo

    class TestDo

    {

           public static String doSome(String input)

           {

           try

           {

           Thread.sleep(1000);

    }

           catch (InterruptedException e)

           {

           e.printStackTrace();

    }

    String output = input + “:” + (Sys.currentTimeMillis());

    return output;

    }

    }

           看这题和上一题差不多,也是数据共享问题了,弄一个同步集合存起来。

           用同样的方法一样解决 new ArrayBlockingQueue()

    张老师又讲了另一个同步队列:SynchronousQueue一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作,反之亦然。同步队列没有任何内部容量,甚至连一个队列的容量都没有。不能在同步队列上进行peek,因为仅在试图要移除元素时,该元素才存在;除非另一个线程试图移除某个元素,否则也不能(使用任何方法)插入元素;也不能迭代队列,因为其中没有元素可用于迭代。

    SynchronousQueue是一个特殊队列,即便是空的也不能插入元素,也读不到元素,要往里边插入的时候如果没有读取操作,插入操作就会阻塞,等到有读取操作出现时,插入操作检测到了读取操作,才能把数据插入进去,而读取操作正好可以拿到刚刚插入进去的数据。就好比毒品买卖,我拿着毒品给谁呢,只有买毒品的人来了,才能立马给他,他也拿到了。与Exchanger类似,不过Exchanger是单对单的交换,SynchronousQueue可以多个抢数据,我拿着毒品等人来买,一下来了3个人买,谁抢到了就是谁的;或者我拿3包毒品,3个人同时每人一份。

    这道题用synchronousQueue的话会一下子将10个数据全打印出来,因为10次循环一次放一个并没有人来取,所以没有放进去,后来一下10个线程来取数据,就一下放进去拿走了。我测试的时候没有这种情况,都是间隔一秒一秒的。测试后发现,将doSome处理后的结果存进去,就会有间隔,而直接存进去,取数据后再处理的话就是一下一片了。分析后知道:put时没有take,10个数据都在等待存入,如果存入的数据是doSome(input)的话,开始取数据时才开始执行doSome所以就会有间隔了。直接存数据,取出后在doSome就是一下拿到10个数据了。

    要解决这个问题,可以使用厕所抢坑的方式解决,使用Semaphore来获取许可,每取一次数据释放一次即可。

    final Semaphore x = new Semaphore(1);    一次一个

    final SynchronousQueue queue = new SynchronousQueue();

    每次取数据前都要先获取许可

    x.acquire();

    取完后释放许可

    x.release();

    这种方式与使用Lock方式一样

     

    22.空中网挑选实习生的面试题3

           现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:

                  441258199615

                  111258199615

                  331258199615

                  121258199615

           请修改代码,如果有几个线程调用TestDo.doSome(key, value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是“1”时,她们中的一个要比另外其他线程晚1秒输出结果,如下所示:

                  441258199615

                  111258199615

                  331258199615

                  121258199616

           总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:

    //不能改动此Test

    public class Test3 extends Thread

    {

           private TestDo testDo;

           private String key;

           private String value;

           public Test3(String key, String key2, String value)

           {

           this.testDo = TestDo.getInstance();

           /*常量“1”和“1”是同一个对象,下面这行代码就是要用“1+“”的

    方式产生新的对象,以实现内容没有改变,仍然相等(都还为“1”),

    但对象却不再是同一个的效果

    */

    this.key = key + key2;

    this.value = value;

    }

           public static void main(String[] args) throws InterruptedException

           {

           Test3 a = new Test3(“1”, “”, “1”);

           Test3 b = new Test3(“1”, “”, “2”);

           Test3 c = new Test3(“3”, “”, “3”);

           Test3 d = new Test3(“4”, “”, “4”);

           SOP(begin+:+sys.currentTimeMillis()/1000);

           a.start();

           b.start();

           c.start();

           d.start();

    }

           public void run()

           {

           testDo.doSome(key, value);

    }

    }

     

    class TestDo

    {

           private TestDo(){}

           private static TestDo _instance = new TestDo();

    public static TestDo getInstance()

           {

           return _instance;

    }

    public void doSome(Object key, String value)

    {

           //此大括号内的代码是需要局部同步的代码,不能改动!

           {

           try

           {

           Thread.sleep(1000);

           SOP(key+”:”+value+”:”+sys.currentTimeMillis()/1000);

    }

           catch (InterruptedException e)

           {

                  e.printStackTrace();

    }

    }

    }

    }

           看完这道题第一个想法是在标记位置加上同步代码块,但是锁不好弄了,因为每次都新建了一个key对象来接受实际key,没法获取到实际key对象。     

           想到了Lock对象,所以建一个Lock对象,判断key的值是否和指定值“1“相同,如果相同就锁上,不同不管,finally里在解锁前进行判断,避免没上锁还要解锁发生问题。

    Lock lock = new ReentrantLock();

           public void doSome(Object key, String value)

           {

                 if (key.equals("1"))

                        lock.lock();

                         //System.out.println("OKOKOOK");

                  //synchronized ("1")

                  try

                  //此大括号内的代码是需要局部同步的代码,不能改动!

                  {

                         try

                         {

                                Thread.sleep(1000);

                                System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);

                         }

                         catch (InterruptedException e)

                         {

                                e.printStackTrace();

                         }

                  }finally

                  {

                        if (key.equals("1"))

                               lock.unlock();

                  }

           }

    但上面的方式写死了,如果换2呢还要改代码,现在要不管是什么只要相同都互斥,将这些加进来的key放到一个集合ArrayList中,每次传进来一个key,先把传进来的key作为锁对象,再判断这个对象有没有存在锁集合中,如果没有,就把它存进去,同时就用这个key做锁;如果已经存在了,就是说这个key已经做过锁对象了,就需要将以前做锁的那个对象拿出来,再让它来当锁,与传进来的key对象一样,这样就产生互斥效果了。

           需要注意:拿原来的锁对象时要迭代锁集合,因为有多个线程在运行,所以迭代时有可能出现其他线程的key没有做过锁,需要将它加到锁集合中,可是这时候这个线程还在迭代过程中,迭代时不能操作集合中的数据,就会发生异常。要解决这个问题,就需要用到同步集合了。CopyOnWriteArrayList

     

           使用ArrayList时就经常出异常,换CopyOnWriteArrayList后没有异常了

    //将所有传过来的key都存起来

        //private List<Object> keys = new ArrayList<Object>();

        private CopyOnWriteArrayList<Object>keys =new CopyOnWriteArrayList<Object>();

        publicvoid doSome(Object key, String value)

        {

            //先用这个key当锁,用过一次就存到集合中

            Object o = key;

            //判断这个锁用过没有

            if (!keys.contains(o))

            {

               //如果这个key没有用过,就用它当锁,把它存到锁集合中

               keys.add(o);

            }

            else  //锁集合中已经有了这个key

            {

               //这个key已经当过锁了,就把它拿出来,还用它做锁,就和现在的key互斥了

               //因为不知道原来key的位置,所有需要进行遍历

               for (Iterator<Object> it =keys.iterator(); it.hasNext();)

               {

                   //当前遍历到的对象

                   Object oo = it.next();

                   //如果找到了,就让它做锁

                   if (oo.equals(o))

                   {

                       o = oo;

                       break;//找到了,不用再循环了

                   }                  

               }

               //o = keys.get(keys.indexOf(o));   //key和o不是同一个对象,拿不到

            }

           

            synchronized (o)

            //此大括号内的代码是需要局部同步的代码,不能改动!

            {

               try

               {

                   Thread.sleep(1000);

                   System.out.println(key+":"+value+":"+System.currentTimeMillis()/1000);

               }

               catch (InterruptedException e)

               {

                   e.printStackTrace();

               }

            }

        }

     

    a = “1”+””;

    b = “1”+””;

    ab是同一个对象,常量相加 equals为真 ==为假

     

    Object o1 = new String("1");

           Object o2 = new String("1");

           System.out.println(o1==o2);//false

           System.out.println(o1.equals(o2));//true

           System.out.println(o1); //1

           System.out.println(o2); //1

           Object o3 = "1"+"";

           Object o4 = "1"+"";

           System.out.println(o3==o4);//true

           System.out.println(o3.equals(o4));//true

           Object o5 = "2"+"";

           Object o6 = get("2","");

           System.out.println(o5==o6);//false

           System.out.println(o5.equals(o6));//true

           System.out.println(o5+"__"+o6);//2__2

          

        publicstatic Object get(String a, String b)

        {

           return a+b;

        }

     

     

     

     

     

     

     

    展开全文
  • 办公自动化习题,用于计算机等级考试公共基础知识方面的复习。主要有计算机基础知识,软件,硬件,程序设计,网络,计算机病毒等相关知识点的考核。
  • 应用场景:共享资源的争夺,例如游戏中选手进入房间的情况。 [java]   view plain   copy import  java.util.concurrent.ExecutorService;  import  java.util...

    转自:http://blog.csdn.net/xushuaic/article/category/1335611

    笔记摘要:

                   这里主要介绍了java5中线程锁技术以外的其他同步工具,首先介绍semaphore:一个计数信号量。用于控制同时访问资源的线程个数,

                   CyclicBarrier同步辅助类:从字面意思看是路障,这里用于线程之间的相互等待,到达某点后,继续向下执行,CountDownLatch同步辅

                  助类:在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。犹如倒计时计数器,然后是Exchangeer:实现两个

                 对象之间数据交换,可阻塞队列:ArrayBlockingQueue,通过阻塞队列间的通信来演示其作用,最后介绍了几个同步集合。



    一、Semaphore实现信号灯


            1.、Semaphore可以维护当前访问自身的线程个数,并提供了同步机制,使用Semaphore可以控制同时访问资源的线程个数,例如,实现一个文件允许

           的并发访问数。Semaphore 只对可用许可的号码进行计数,并采取相应的行动。 

       

            2、Semaphore实现的功能就像:银行办理业务,一共有5个窗口,但一共有10个客户,一次性最多有5个客户可以进行办理,其他的人必须等候,

         当5客户中的任何一个离开后,在等待的客户中有一个人可以进行业务办理。


            3、Semaphore提供了两种规则:

                   一种是公平的:获得资源的先后,按照排队的先后。在构造函数中设置true实现

                   一种是野蛮的:谁有本事抢到资源,谁就可以获得资源的使用权。


           4、与传统的互斥锁的异同:

                 单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了“锁“,再由另外一个线程释放”锁“,

           这可以应用于死锁恢复的一些场合。


          5、应用场景:共享资源的争夺,例如游戏中选手进入房间的情况。


    [java]  view plain  copy
    1. import java.util.concurrent.ExecutorService;  
    2. import java.util.concurrent.Executors;  
    3. import java.util.concurrent.Semaphore;  
    4.   
    5. public class SemaphoreTest {  
    6.     public static void main(String[] args) {  
    7.           
    8.   //创建一个可根据需要创建新线程的线程池  
    9.   ExecutorService service = Executors.newCachedThreadPool();  
    10.         final  Semaphore sp = new Semaphore(3);  
    11.           
    12.   //创建10个线程  
    13.   for(int i=0;i<10;i++){  
    14.             Runnable runnable = new Runnable(){  
    15.                     public void run(){  
    16.                     try {  
    17.                         sp.acquire();   //获取灯,即许可权  
    18.                     } catch (InterruptedException e1) {  
    19.                         e1.printStackTrace();  
    20.                     }  
    21.                     System.out.println("线程" + Thread.currentThread().getName() +   
    22.                             "进入,当前已有" + (3-sp.availablePermits()) + "个并发");  
    23.                     try {  
    24.                         Thread.sleep((long)(Math.random()*10000));  
    25.                     } catch (InterruptedException e) {  
    26.                         e.printStackTrace();  
    27.                     }  
    28.                     System.out.println("线程" + Thread.currentThread().getName() +   
    29.                             "即将离开");                      
    30.                     sp.release();   // 释放一个许可,将其返回给信号量  
    31.   
    32.                     //下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元  
    33.                     System.out.println("线程" + Thread.currentThread().getName() +   
    34.                             "已离开,当前已有" + (3-sp.availablePermits()) + "个并发");                      
    35.                 }  
    36.             };  
    37.             service.execute(runnable);            
    38.         }  
    39.     }  
    40.   
    41. }  


    二、其他同步工具类:


    CyclicBarrier


              1、一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,

                 这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。 


              2、CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障

                点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。 


    示例:

           3个线程到达某个集合点后再向下执行,使用await方法实现

    [java]  view plain  copy
    1. import java.util.concurrent.CyclicBarrier;  
    2. import java.util.concurrent.ExecutorService;  
    3. import java.util.concurrent.Executors;  
    4.   
    5. public class CyclicBarrierTest {  
    6.   
    7.     public static void main(String[] args) {  
    8.         ExecutorService service = Executors.newCachedThreadPool();  
    9.         final  CyclicBarrier cb = new CyclicBarrier(3);  
    10.         for(int i=0;i<3;i++){  
    11.             Runnable runnable = new Runnable(){  
    12.                     public void run(){  
    13.                     try {  
    14.                         Thread.sleep((long)(Math.random()*10000));    
    15.                         System.out.println("线程" + Thread.currentThread().getName() +   
    16.                                 "即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));                         
    17.                         cb.await();//在所有参与者都已经在此 barrier 上调用 await 方法之前,将一直等待。  
    18.                           
    19.                         Thread.sleep((long)(Math.random()*10000));    
    20.                         System.out.println("线程" + Thread.currentThread().getName() +   
    21.                                 "即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));  
    22.                         cb.await();   
    23.                         Thread.sleep((long)(Math.random()*10000));    
    24.                         System.out.println("线程" + Thread.currentThread().getName() +   
    25.                                 "即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));                       
    26.                         cb.await();                       
    27.                     } catch (Exception e) {  
    28.                         e.printStackTrace();  
    29.                     }                 
    30.                 }  
    31.             };  
    32.             service.execute(runnable);  
    33.         }  
    34.         service.shutdown();  
    35.     }  
    36. }  

    CountDownLatch


            1、一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。犹如倒计时计数器,调用CountDownLatch对象

           的countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。


            2、可以实现一个人(也可以是多个人)等待其他所有人都来通知他,也可以实现一个人通知多个人的效果,类似裁判一声口令,运动员开始奔跑

              (一对多),或者所有运送员都跑到终点后裁判才可以公布结果(多对一)。


            3、用指定的计数 初始化 CountDownLatch。在调用 countDown() 方法之前,await 方法会一直受阻塞。之后,会释放所有等待的线程,await 的所

              有后续调用都将立即返回。这种现象只出现一次——计数无法被重置。如果需要重置计数,请考虑使用 CyclicBarrier。 


    示例:

           实现运动员比赛的效果

    [java]  view plain  copy
    1. import java.util.concurrent.CountDownLatch;  
    2. import java.util.concurrent.CyclicBarrier;  
    3. import java.util.concurrent.ExecutorService;  
    4. import java.util.concurrent.Executors;  
    5.   
    6. public class CountdownLatchTest {  
    7.   
    8.     public static void main(String[] args) {  
    9.           
    10.   ExecutorService service = Executors.newCachedThreadPool();  
    11.     
    12.   //构造一个用给定计数初始化的 CountDownLatch,相当于裁判的口哨  
    13.   final CountDownLatch cdOrder = new CountDownLatch(1);  
    14.     
    15.   //相当于定义3个运行员  
    16.         final CountDownLatch cdAnswer = new CountDownLatch(3);  
    17.         for (int i = 0; i < 3; i++) {  
    18.             Runnable runnable = new Runnable() {  
    19.                 public void run() {  
    20.                     try {  
    21.                         System.out.println("线程"  
    22.                                 + Thread.currentThread().getName() + "正准备接受命令");  
    23.   
    24.                         // 等待发令枪  
    25.                         cdOrder.await();//使当前线程在锁存器倒计数至零之前一直等待  
    26.   
    27.                         System.out.println("线程"  
    28.                                 + Thread.currentThread().getName() + "已接受命令");  
    29.                         Thread.sleep((long) (Math.random() * 10000));  
    30.                         System.out  
    31.                                 .println("线程"  
    32.                                         + Thread.currentThread().getName()  
    33.                                         + "回应命令处理结果");  
    34.   
    35.                         // 各个运动员完报告成绩之后,通知裁判  
    36.                         cdAnswer.countDown();//递减锁存器的计数,如果计数到达零,则释放所有等待的线程  
    37.   
    38.                     } catch (Exception e) {  
    39.                         e.printStackTrace();  
    40.                     }  
    41.                 }  
    42.             };  
    43.             service.execute(runnable);  
    44.         }  
    45.         try {  
    46.             Thread.sleep((long) (Math.random() * 10000));  
    47.   
    48.             System.out.println("线程" + Thread.currentThread().getName()  
    49.                     + "即将发布命令");  
    50.             // 发令枪打响,比赛开始  
    51.             cdOrder.countDown();  
    52.   
    53.             System.out.println("线程" + Thread.currentThread().getName()  
    54.                     + "已发送命令,正在等待结果");  
    55.   
    56.             // 裁判等待各个运动员的结果  
    57.             cdAnswer.await();  
    58.   
    59.             // 裁判公布获得所有运动员的成绩  
    60.             System.out.println("线程" + Thread.currentThread().getName()  
    61.                     + "已收到所有响应结果");  
    62.         } catch (Exception e) {  
    63.             e.printStackTrace();  
    64.         }  
    65.         service.shutdown();  
    66.   
    67.     }  
    68. }  

    Exchanger

          

          1、用于实现两个对象之间的数据交换,每个对象在完成一定的事务后想与对方交换数据,第一个先拿出数据的对象将一直等待第二个对象拿着数据

              到来时,彼此才能交换数据。

          2、方法:exchangeV x

              等待另一个线程到达此交换点(除非当前线程被中断),然后将给定的对象传送给该线程,并接收该线程的对象。


          3、应用:使用 Exchanger 在线程间交换缓冲区


    示例:

           模拟毒品交易情景

    [java]  view plain  copy
    1. import java.util.concurrent.Exchanger;  
    2. import java.util.concurrent.ExecutorService;  
    3. import java.util.concurrent.Executors;  
    4.   
    5. public class ExchangerTest {  
    6.   
    7.     public static void main(String[] args) {  
    8.         ExecutorService service = Executors.newCachedThreadPool();  
    9.         final Exchanger exchanger = new Exchanger();  
    10.         service.execute(new Runnable(){  
    11.             public void run() {  
    12.                 try {                 
    13.   
    14.                     String data1 = "毒品";  
    15.                     System.out.println("线程" + Thread.currentThread().getName() +   
    16.                     "正在把: " + data1 +"   交易出去");  
    17.                     Thread.sleep((long)(Math.random()*10000));  
    18.                     String data2 = (String)exchanger.exchange(data1);  
    19.                     System.out.println("线程" + Thread.currentThread().getName() +   
    20.                     "换得了: " + data2);  
    21.                 }catch(Exception e){  
    22.                       
    23.                 }  
    24.             }     
    25.         });  
    26.         service.execute(new Runnable(){  
    27.             public void run() {  
    28.                 try {                 
    29.   
    30.                     String data1 = "美金";  
    31.                     System.out.println("线程" + Thread.currentThread().getName() +   
    32.                     "正在把: " + data1 +"   交易出去");  
    33.                     Thread.sleep((long)(Math.random()*10000));                    
    34.                     String data2 = (String)exchanger.exchange(data1);  
    35.                     System.out.println("线程" + Thread.currentThread().getName() +   
    36.                     "换得了: " + data2);  
    37.                 }catch(Exception e){  
    38.                       
    39.                 }                 
    40.             }     
    41.         });       
    42.     }  
    43. }  

    ArrayBlockingQueue


           1、一个由数组支持的有界阻塞队列。此队列按 FIFO(先进先出)原则对元素进行排序。队列包含固定长度的队列和不固定长度的队列。

             这是一个典型的有界缓存区,固定大小的数组在其中保持生产者插入的元素和使用者提取的元素。一旦创建了这样的缓存区,就不能

        再增加其容量。试图向已满队列中放入元素会导致操作受阻塞;试图从空队列中提取元素将导致类似阻塞。


    通俗的讲:

          当指定队列大小,如果已经放满,其他存入数据的线程就阻塞,等着该队列中有空位,才能放进去。当取的比较快,队列中没有数据,

          取数据的线程阻塞,等队列中放入了数据,才可以取。

            2、ArrayBlockingQueue中只有puttake方法才具有阻塞功能。方法类型如下


    抛出异常

    特殊值

    阻塞

    超时

    插入

    add(e)

    offer(e)

    put(e)

    offer(e, time, unit)

    移除

    remove()

    poll()

    take()

    poll(time, unit)

    检查

    element()

    peek()

    不可用

    不可用


    示例:

           用3个空间的队列来演示向阻塞队列中存取数据的效果。

    [java]  view plain  copy
    1. 通俗的将:  
    2. 当指定队列大小,如果已经放满,其他存入数据的线程就阻塞,等着该队列中有空位,才能放进去。当取的比较快,队列中没有数据,取数据的线程阻塞,等队列中放入了数据,才可以取。  
    3.   
    4.   
    5. ArrayBlockingQueue中只有put和take方法才具有阻塞功能。方法类型如下  
    6.   
    7.     抛出异常    特殊值 阻塞  超时  
    8. 插入  add(e)  offer(e)    put(e)  offer(e, time, unit)  
    9. 移除  remove()    poll()  take()  poll(time, unit)  
    10. 检查  element()   peek()  不可用 不可用  
    11.   
    12. 3个空间的队列来演示向阻塞队列中存取数据的效果。  
    13.   
    14. package cn.xushuai.thread;  
    15. import java.util.concurrent.ArrayBlockingQueue;  
    16. import java.util.concurrent.BlockingQueue;  
    17.   
    18. public class BlockingQueueTest {  
    19.     public static void main(String[] args) {  
    20.         final BlockingQueue queue = new ArrayBlockingQueue(3);  
    21.         for(int i=0;i<2;i++){  
    22.             new Thread(){  
    23.                 public void run(){  
    24.                     while(true){  
    25.                         try {  
    26.                             Thread.sleep((long)(Math.random()*1000));  
    27.                             System.out.println(Thread.currentThread().getName() + "准备放数据!");                              
    28.                             queue.put(1);   //放进去后,可能立即执行“准备取数据”  
    29.                             System.out.println(Thread.currentThread().getName() + "已经放了数据," +                             
    30.                                         "队列目前有" + queue.size() + "个数据");  
    31.                         } catch (InterruptedException e) {  
    32.                             e.printStackTrace();  
    33.                         }  
    34.   
    35.                     }  
    36.                 }  
    37.                   
    38.             }.start();  
    39.         }  
    40.           
    41.         new Thread(){  
    42.             public void run(){  
    43.                 while(true){  
    44.                     try {  
    45.                         //将此处的睡眠时间分别改为100和1000,观察运行结果  
    46.                         Thread.sleep(1000);  
    47.                         System.out.println(Thread.currentThread().getName() + "准备取数据!");  
    48.                         queue.take();   //取出后可能來不及执行下面的打印语句,就跑到了“准备放数据”,  
    49.                         System.out.println(Thread.currentThread().getName() + "已经取走数据," +                             
    50.                                 "队列目前有" + queue.size() + "个数据");                      
    51.                     } catch (InterruptedException e) {  
    52.                         e.printStackTrace();  
    53.                     }  
    54.                 }  
    55.             }  
    56.         }.start();            
    57.     }  
    58. }  

    阻塞队列间的通信

               A队列向空间中存数据,B从空间里取数据,A存入后,通知B去取,B取过之后,通知A去放,依次循环


    示例:

            子线程先循环10次,接着主线程循环100次,接着又回到子线程,循环10次,再回到主线程又循环100,如此循环50次。

            

    说明:

           这里通过使 用两个具有1个空间的队列来实现同步通知的功能(实现了锁和condition的功能),以便实现队列间的通信,其中使用到了

           构造代码块为主队列先存入一个数据,以使其先阻塞,子队列先执行。


    使用构造代码块的原因:

           成员变量在创建类的实例对象时,才分配空间,才能有值,所以创建一个构造方法来给main_quene赋值,这里不可以使用静态代码块,因为静态

           在还没创建对象就存在, 而sub_quene和main_quene是对象创建以后的成员变量,所以这里用匿名构造方法,它的运行时期在任何构造方法之前,

           创建几个对象就执行几次

    [java]  view plain  copy
    1. import java.util.concurrent.ArrayBlockingQueue;  
    2. import java.util.concurrent.BlockingQueue;  
    3.   
    4. public class BlockingQueueCommunication {  
    5.     public static void main(String[] args){  
    6.           
    7.         final Business business = new Business();  
    8.           
    9.         new Thread(new Runnable(){  
    10.             @Override  
    11.             public void run() {  
    12.                 for(int i=1;i<=50;i++){  
    13.                     business.sub(i);  
    14.                 }  
    15.             }  
    16.         }).start();  
    17.   
    18.             //主线程外部循环  
    19.             for(int i=1;i<=50;i++){  
    20.                 business.main(i);  
    21.           }  
    22.        }  
    23.   
    24.     //业务类  
    25.     static class Business{  
    26.           
    27.         BlockingQueue<Integer> sub_quene = new ArrayBlockingQueue<Integer>(1);  
    28.         BlockingQueue<Integer> main_quene = new ArrayBlockingQueue<Integer>(1);  
    29.                   
    30.         {  
    31.   //为了让子队列先走,所以在一开始就往主队列中存入一个对象,使其阻塞。  
    32.             try {  
    33.                 main_quene.put(1);    
    34.             } catch (InterruptedException e) {  
    35.                 e.printStackTrace();  
    36.             }         
    37.         }  
    38.           
    39.         //子队列先走       
    40.         public  void sub(int i){  
    41.               
    42.             try {  
    43.                 sub_quene.put(1);   //子队列第一次存入,可以执行,但由于只有1个空间,已经存满,所以只有在执行后要等到take之后才能继续下次执行  
    44.             } catch (InterruptedException e) {  
    45.                 e.printStackTrace();  
    46.             }  
    47.             //子队列循环执行  
    48.             for(int j=1;j<=10;j++){  
    49.                 System.out.println("sub thread sequence of"+i+",loop of "+j);  
    50.             }  
    51.             try {  
    52.                 main_quene.take();  //让主队列从已经填满的队列中取出数据,使其开始第一次执行  
    53.             } catch (InterruptedException e) {  
    54.                 e.printStackTrace();  
    55.             }  
    56.         }  
    57.           
    58.         public void main(int i){  
    59.               
    60.             try {  
    61.                 main_quene.put(1);  //主队列先前放过1个空间,现在处于阻塞状态,等待子队列通知,即子线程中的main_quene.take();   
    62.             } catch (InterruptedException e) {  
    63.                 e.printStackTrace();  
    64.             }  
    65.               
    66.   //主队列循环执行  
    67.             for(int j=1;j<=100;j++){  
    68.                 System.out.println("main thread sequence of"+i+", loop of "+j);  
    69.             }     
    70.             try {  
    71.                 sub_quene.take(); //让子队列从已经填满的队列中取出数据,使其执行  
    72.             } catch (InterruptedException e) {  
    73.                 e.printStackTrace();  
    74.             }  
    75.         }     
    76.     }  
    77.    }  


    三、Java5中的一些同步集合类:


    1、ConcurrentHashMap(同步的HashMap

           支持获取的完全并发和更新的所期望可调整并发的哈希表。此类遵守与 Hashtable 相同的功能规范,并且包括对应于 Hashtable 的每个方法的方法版本。

       不过,尽管所有操作都是线程安全的,但获取操作不 必锁定,并且不 支持以某种防止所有访问的方式锁定整个表。此类可以通过程序完全与 Hashtable 

       进行互操作,这取决于其线程安全,而与其同步细节无关。


    内部原理:

          其实内部使用了代理模式,你给我一个HashMap,我就给你一个同步的HashMap。同步的HashMap在调用方法时,是去分配给原始的HashMap只是在去

       调用方法的同时加上了Synchronized,以此实现同步效果


    2、ConcurrentSkipListSet类似于TreeSet

          一个基于 ConcurrentSkipListMap 的可缩放并发 NavigableSet 实现。set 的元素可以根据它们的自然顺序进行排序,也可以根据创建 set 时所提供的

       Comparator 进行排序,具体取决于使用的构造方法。


    3、CopyOnWriteArrayList 

          ArrayList 的一个线程安全的变体,可解决线程安全问题,在遍历的时候,同时进行添加操作。其中所有可变操作(add、set 等等)都是通过对底层数

          组进行一次新的复制来实现的。


    4、CopyOnWriteArraySet

        对其所有操作使用内部 CopyOnWriteArrayList 的 Set     


    展开全文
  • OFFER

    2014-05-12 12:47:05
    9个offer,12家公司,35场面试,从微软到谷歌,一个应届计算机毕业生的2012求职之路 浏览次数:4571次 2013年01月11日 _Luc_ - 博客园 字号: 大 中 小 分享到: QQ空间 新浪微博 腾讯微博 人人网 豆瓣网 ...

    PS: 颇具参考价值的文章,以及怎么准备求职,在求职中注意的问题。

    9个offer,12家公司,35场面试,从微软到谷歌,一个应届计算机毕业生的2012求职之路
    浏览次数:4571次 2013年01月11日 _Luc_ - 博客园 字号: 大 中 小
    分享到: QQ空间 新浪微博 腾讯微博 人人网 豆瓣网 开心网 更多 39

     
    1,简介

    毕业答辩搞定,总算可以闲一段时间,把这段求职经历写出来,也作为之前三个半月的求职的回顾。

    首先说说我拿到的offer情况:

    微软,3面->终面,搞定

    百度,3面->终面,口头offer

    搜狗,2面,悲剧

    腾讯,1面,悲剧

    布丁移动,3面,搞定

    涂鸦游戏,3面,搞定

    友盟,3面->CEO面,搞定

    雅虎,4面->终面,搞定

    微策略,2面,悲剧

    人民搜索,3面->终面,搞定

    人人,2面+终面+Special面,搞定

    Google,7面,搞定

    求职经历分为定位、准备、简历、笔试和面试这五个部分,大家挑感兴趣的看就成。

    我的求职经历适用但不限于码农,不适用与企事业单位(据说是完全不同的考察标准和流程)。废话比较多,大家耐心忍受,有什么问题可以跟帖提问。

    2,定位

    教育经历:本科在大连某工科院校,由于GPA比较惨烈+挂科,所以没保成研,毕业后修了一年英语双学位,然后到帝都计算机职业教育学院接受再教育。

    技术能力:属于半码农半产品的类型,代码编的过去(搞过compiler),也有一些拿的出手的产品(几十w的用户量),一句话描述:几十w代码+几十w用户的Coder。

    专业能力:非ACM出身,算法拙计但基础扎实。由于单身所以看了N多书(CS+心理+经管+历史),扯淡能力强大,碰到非专业的各种秒杀,碰到专业各种拙计。

    实习经历:大四在一家ds公司实习过一年,攒了不少代码量;后来在MS断断续续的待了一年多,虽说是打酱油,但在众大神的光环笼罩下,水平至少提了三个档。

    目标公司:由于百度给我的印象实在很差,而MS给我的印象又实在很好,所有就有了下面的排名:

    外企(Google、MS、Yahoo等)>国内互联网(阿里、腾讯、百度、网易等)>企事业单位(基本不考虑)

    3,准备

    经常在论坛里看到各种求职抱怨贴,其实在抱怨前应该仔细想一想,为了求职,你付出了多少?看到人家找工作找的顺找的爽,有没有想过人家背地里付出了多少努力和心血?别拿官二代和富二代啥的说事,真ds只会拿一堆自身以外的理由掩饰自己的懒惰。

    不要认为求职就是发个简历等面试通知,对于大神来说不用发简历牛逼公司也会围着你转,对于ds来说就是预则立不预则废,中国缺什么就是不缺人,不下功夫准备很有可能连个P都没有。

    其实很多ds就是怕预也废所以干脆不准备直接上,这样搞不定的话,就有借口说不是自己蠢而是自己没准备,可以捍卫自己的智商高地不被侵犯。身边有不少这样的实例,典型的死要面子活受罪,活该你找不到工作。

    我的微软mentor曾提到过,我的实习面试表现一般,但后来表现出的动手能力大大超出之前面试的预估,而有些面试表现很出色,问题对答如流的选手,入职之后反而不是很理想,至少没有达到面试时发挥出的水准。

    这说明一个问题,就是笔试面试,准备和不准备会差异很大。如果你的简历不是那么NB,那就只能靠笔试和面试的加分撑场面。身边经常有同学纳闷这样代码都编不利索的傻屌都能进MS为什么我不能进,答案往往很简单:人家比你多准备了一个月。平时电脑上写程序可能很利索,笔试面试时在纸上写写试试你就知道什么叫拙计。

    IT公司的笔试和面试的题量都不大(相对于企事业单位和银行动辄上百道选择题的题量,算是很少),一般十几道选择题,三四道大题就算题量很大。但计算机的东西实在又是太多,程序设计、数据结构、算法设计、操作系统、体系结构、编译原理、数据库、软件工程等分支,编译的话太难(一千个码农里也没几个人能在纸上写一个最基础的递归下降LLParser),软件工程、体系结构、数据库这些太水(不是说这些分支没用,而是它们很难考察,尤其对应届生来说这些都是些文字游戏,比如说面向对象的三要素五原则,有个鸟用),这么一排除,再把数据结构和算法设计一合并,就剩下程序设计、算法和操作系统。没错,这三项搞定,国内外IT公司通杀。

    因此我的笔试和面试准备很简单,就是重温+突击程序设计、算法和操作系统。下面是我的笔试+面试准备内容:

    程序设计:

    1,把基础的数据结构的C语言实现在纸上写三遍以上,用我能想到的最精简最优化的方法

    2,阅读CARM和TCPL,确保不会遗漏C语言的每个细节

    3,重温之前自己做过的靠谱项目,并总结里面的关键难题和解决思路

    4,重读Writing Solid Code、Elements of Programming、Practice of programming

    5,阅读Science of Programming,做到可以证明自己的程序的正确性(前条件+后条件+不变式)

    算法:

    1,重读Algorithm Design Manual,重点阅读Dynamic Programming和Backtraverse

    2,重读Programming Pearls和More Programming Pearls,并完成所有课后题

    3,独立解决编程之美里面的题目(国内不少企业选题用的这本书)

    4,完成Careercup里Amazon、Google和Microsoft这三个分类下面的前20页面试题

    5,完成TopCoder的数十道D1L2~D2L1难度区间的算法题目

    操作系统:

    1,重读Modern Operating System,重温OS的核心概念

    2,重读Computer Systems a Programmer's Perspective的关键章节,回顾里面的关键点

    从七月底开始一直到十一月,花了接近四个月,很多东西都是一边面试一边准备:面试->发现盲点->修复盲点。

    此外列出一些面试笔试题的资源,此外感谢基友@codewarrior之前的推荐:

    1,Crack over the code interview

    很靠谱的笔试面试指导手册

    2,CareerCup

    集齐了大量的真实笔试面试题,去外企的一定得看

    3,TopCoder

    如果不是ACM,练这个就够,其实面试也不会问太难的算法,哪怕是google

    4,编程之美

    尽管题目有些过时,但依然很实用,三星题目适合一个人仔细想

    此外也说下一些不靠谱的资源:

    1,IT公司面试100题

    这个恐怕是国内传的最多的IT面试题

    题目本身还可以,但那个出题人本身代码功底一般,给出的答案包含大量错误和缺陷,导致参考价值骤降

    2,程序员面试宝典

    翔一样的书,各种错误概念的堆积,如果一个错误给我一块钱,我能从这本书搞成万元户。如果去正规公司拿这本书准备,包你被黑出翔。

    4,简历

    在MS时,老大曾让我帮忙招几个靠谱的实习生,因此我收到了几百封简历,过了一把HR的瘾。这里说说自己在看简历时发现的几点:

    1,可读性。不要用Word或压缩包,用PDF。此外在邮件里面用纯文本加上自己的简介,简化对方阅读的操作。要记住HR一天看的简历海的去了,压缩包是HR最痛恨的格式,因为解压了就不知道扔哪去了,有时干脆就不看;Word有版本问题,10的docx到了07往往被黑出翔。还有就是对方有可能不在PC上读邮件,因此纯文本的简介非常有必要。

    2,群发。不要给人群发的嫌疑,看清楚目标职位和目标公司,我发的工程院招聘贴,收到的几百封简历里面有十余封是投到微软亚洲研究院,有几个干脆写“敬爱的某领导”,尼玛这不找抽么。

    3,设计。特别提一下设计,很多电工的简历就是翔,丑的一逼,对齐没有,字体拙计,要点不明。再放到几百份几千份简历里面,活该你被忽略。建议所有电工投简历前阅读《给大家看的设计书》,至少搞明白里面的C.R.A.P四原则。不要小看设计的威力,在简历内容接近的情况下,良好的设计会大大加分。

    4,篇幅。控制在一页以内。倒不是说不能写两页,而是HR没时间看两页这么多。而且就我看过的几百封简历而言,凡是超过两页的没一个靠谱,有这么高的先验概率,HR才没工夫一个个筛,反正中国有的是人。

    5,重点。一定要有重点,做到让HR通过简历在20秒内确定你靠不靠谱。可以用加黑字体进行视觉引导。

    6,措辞。甭搞“渴望得到这份工作”、“期待在xxx的工作机会”这样的句式,除了显得你低端,其它毛用没有。

    7,别字。千万不要出现错别字,别字简历一般直接干掉。一页的简历都能出问题,一般不会靠谱。

    因为看了很多不靠谱的简历,我对简历重要性的理解要比其他人深刻很多。首先花了一周把原来两页半的纸缩到一页内,然后找UI朋友帮忙调整了版式、缩进和字体,并找UX朋友帮忙进行重点调整以进行视觉引导,然后在PC和手机上进行了简历可读性测试。从而获得了100%的简历通过率。

    此外,优先走内部推荐,这样最有效率,所以结识各个公司的朋友是非常有必要的。

    海投简历既没必要也没效率,有这个时间不如改改简历来的实在。

    相关书目:

    1,给大家看的设计书,让你的简历看起来不像一坨翔。

    2,Google Resume,如何写出靠谱的简历,进行靠谱的求职。

    5,笔试

    如之前所说,IT公司的笔试相对单纯(程序设计、算法和操作系统),而且范围较窄,有不少题目被出了一遍又一遍。因此市面上存在大量面试/笔试宝典之类的书籍和题集。

    准备国内的小公司,这些面试/笔试题集还靠的住,因为小公司往往不会在招人环节上下太大的成本,因此他们的试卷一般就是东拼西凑的网上题目;对于大型公司来说,这些题库或是宝典就显的不够用了,尤其是外企。

    所以不要把希望放在运气或是临阵磨枪上。就我自己而言,笔试准备了一个多月,尽管这个时间并不算多,但由于自己平时一直在阅读CS的基础书籍,并做了大量的纸上代码练习,因此笔试通过率也达到了100%(实际上是由于我笔试的公司题目都略简单,据说EMC和网易游戏的笔试难度很高,但当时由于时间冲突没有去成,因此搞出了这个数据)。

    多说无益,这里拿搜狗、腾讯、微软和雅虎这四家公司的笔试试卷为例,简单的介绍下IT公司的笔试题型和题目组成。

    搜狗:

    题型由十余道不定项选择题和三道算法题目组成,要求在两个小时完成。选择题难度一般,比较杂,也有一些原题;三道算法题目有点意思,至少网上是很难找到,需要一定的算法设计能力(主要是动态规划)才能给出解决。

    搜狗的笔试试题按职位分的很细,从C++开发工程师到iOS开发工程师再到数据挖掘研究工程师十余个职位,每个职位的题目都有一套独立的试卷。但是研发的算法题是一样的,最后一道算法题很有意思,我花了一个多小时才想到利用组合数学里面的知识(多元一次方程非负解)给出设计方案,后来和面试官聊这道题时他们也挺吃惊,因为我的方案比他们的答案还要优化。

    腾讯:

    题型由二十道单项选择题、六道填空题和两道算法题组成,其中两道算法题是二选一。难度比较简单,题型很广,既有程序设计语言的细节也有概率统计的基本知识,甚至还有C语言的创始人是谁这样的搞笑问题,从选择题上能看得出腾讯在笔试题上还是下了点功夫的,但算法题就太简单了点,至少我认为考不出什么区分度。

    腾讯的笔试试题也是按职位划分的,但就没有搜狗那么细了,研发类笔试题目是统一的,要求一个半小时完成,印象里自己四十分钟就搞定收工,很多人都提前交了试卷,因为确实很简单。

    微软:

    题型只有二十道不定项选择题,难度较难,要求在一小时四十分钟完成。难度较难,覆盖面非常广,从设计模式,算法分析,代码阅读到C++语言特性,甚至连冷门的函数式程序设计语言都有涉及。

    微软的笔试题目BT之处在于其独特的积分机制:答对了加分,不答无分,答错了倒扣。这就使得很多ds答完试卷感觉自我良好但实际已经被倒扣出翔。以最后一道题为例,答对了加7分,答错倒扣13分,相当于一下子损失20分。所以微软的笔试题会做就得做对,不会做就别蒙,要不更惨。

    此外,微软的笔试题是英文的,加上时间比较短,有些人题都读不完,有些ds连functional language是什么都不知道,自然败的很惨。

    雅虎:

    题型由十余道单项选择题,一道设计题目和六道算法题目组成,其中六道算法题目是六选二,要求在两个小时完成。难度尚可,主要考察编程能力和算法设计能力。由于时间充裕,尽管是六选二,为了炫技,我直接答了里面的五道题目。然而面试时发现面试官判卷子时并没有把多答的题目考虑在内,囧tz。

    相对于微软,雅虎的题目覆盖面就窄了许多,没有一道题目跳出程序设计、算法和操作系统这个圈的,只要勤加准备,很容易通过。

    雅虎的笔试题也是英文,因此英语作答较为合适,此外,由于算法题目简单,给出optimal solution是必须的,比如说在logN的时间内算fibonacci number是必备的知识,能写binary search就不要写sequential search等等。

    从笔试题可以明显看出,国外的大型IT公司(比如雅虎,微软和谷歌等)并不在意你现在的skill set,而更看重你的potential,因此题目大多很基础,并具备相当的深度,以确保你对CS有深刻的理解并能够走的很远;而国内的IT公司(比如百度、搜狗和人人等)更看重你现在的skill set,因此会出现不少语言特性,OS操作之类的具体题目,以确保你能够以尽快的速度上手干活,至于能发展到啥程度他们就不care了。

    因此,准备笔试题的时候要确定自己的目标公司:主攻国内公司的话,C++的语言特性、linux基本命令操作这些细节也得准备,因为会有大量此类题目;主攻国外公司的话,良好的英文阅读能力必不可少,此外优秀的代码理解和代码编写能力也不可或缺。

    此外,不要在笔试题目里犯低级错误,不要抄袭(面试时经常会问到笔试题),保持书写的工整(尤其是代码题目和问答题目)。

    考虑到几乎所有的公司都有编程题目,也就是在纸上写代码,这里推荐几本相关书籍:

    1,Elements of programming style 2nd,写出良好风格的代码。纸上代码一般不长,但短短几行代码往往可以看出这个人的水准,风格很差的代码往往会被pass掉。

    2,Algorithm design manual 2nd,作为非ACM出身的码农,这本书比算导实用很多,课后题也很实在,对回溯,动态规划这些编程技巧讲的非常清楚。

    3,C interfaces and implementation,无论是面试还是笔试,一般都会用C写程序,这本书包含大量的工业级C代码,绝佳的参考和模仿素材。

    最后推荐下Elements of programming和Structure and interpretation of computer programs,这两本书难度很搞,需要大量的时间阅读,不适合临场阅读准备,但读过后,写出的代码绝逼会上两个层次,这里我就不多介绍了。

    6,面试

    之前有人PM我关于海投简历的问题。我个人不建议海投简历。因为对能力弱的人来说,海投简历只会让他信心更差,没有任何效果,有投简历的时间还不如精心准备少数几个好公司;而对能力强的人来说,海投简历之后会有大量的笔试和面试,笔试很耗体力,面试更很耗体力,不但需要打车或坐地铁在各个公司间穿梭,而且需要在面试时保持精神的高度集中,一般面下来都会精疲力尽,导致接下来的发挥不好。所以还是之前所说,优先内部推荐,然后再根据自己的情况和职业发展路线选择公司,选择职位,不用选太多,集中精力攻破领域内的TOP3即可。就我自己而言,求职期间,我一共投了12封简历,参加了12家公司的面试,一共面了35轮。说多不多,说少不少,因为自己投简历时也是本着互联网公司为主,小公司和企事业单位压根没有投,精准投放的好处在于可以集中精力准备同一类型的公司,从而达到不错的效果。

    关于简历海投的问题就说到这里,接下来讲讲IT公司的面试。需要注意的是我这里聊的都是应届生面试,社会招聘面试可能会有所区别,但整体流程不会有太大差异。

    尽管笔试题会有所差别,但IT公司面试的流程大同小异:标准的技术面试一般有45分钟到60分钟,大约分为三个阶段:

    1,自我介绍(5~10分钟):

    这个环节的主要目的在于建立面试官和求职者之间的沟通,面试官已经扫过你的简历,但需要对你有进一步的了解,以便建立一个初步印象,并便于进行接下来的技术提问,所以这个环节最常见的问题无外乎“进行一下自我介绍”,有时会加上3分钟或是5分钟的时限,有时会询问“说说你最得意的项目/作品”之类的变体问题。

    由于这个环节相对固定,因此准备起来相对容易,但即便如此,面试初期时我在自我介绍环节也犯过不少错误。这里以我的经历简单的总结下这个环节的要点:

    1,言简意赅,突出亮点

    面试初期时,我犯的一个很大的问题就是自我介绍废话太多,诸如“出生自xx省xx市”,“自我评价xxx”之类的屁话连篇。要知道技术面试不是相亲,这里的自我介绍不是相亲里面的查户口本,而是要了解你这个人靠不靠谱,牛逼不牛逼。如何在3分钟内让别人觉得你牛逼呢?很简单,说且仅说你最牛逼的事迹,让对方留下深刻印象。就技术面试而言,牛逼的事迹包含三方面:做过的项目,读过的书,认识的人。

    如果实在想不出来有啥牛逼事迹,那就比较难办。说实话,换做你是面试官,招一个履历毫无亮点的人进来有何用?

    2,紧贴简历

    面试官了解你的另外一个途径就是简历,然而短短一两页的简历很难说明白你简历项目中的亮点和难点。就算你不提及,面试官也会在简历中挑他感兴趣的点进行提问,因此自我介绍的内容应该是简历的补充。这样既能留给面试官不错的印象,也能有效节省时间,留出更多的时间进行技术提问环节和问答环节。

    此外,千万不要搞出自相矛盾,比如说简历讲的做了A你在自我介绍中又说是B,这就不是拙计的问题了。

    3,了解公司需求

    不要试图用一套自我介绍来搞定所有公司,除非你的简历只有一句话("Exhausted graphic programming"或是"I wrote python"等)。不同的公司有不同的需求,在Google面前大谈.net技术显然不是什么好的选择(我在google一面中就做过这种挫事)。面试之前要进行详细的调研,了解公司和职位的需求,然后根据他们的需求定制自己的自我介绍和简历,效果会更好。

    2,技术提问(35~45分钟):

    通过自我介绍环节,面试官会对你有一个大概的评估,接下来会通过一系列深入的问题考察你的项目经历和技术能力。所以自我介绍环节不要吹牛逼,技术面试是很实在的东西,你有几斤几两问几下就出来,根本忽悠不过去。

    按照MS的分类,技术面试问题分为三类:

    1,Behavior questions:此类问题针对面试者的过往经历,一方面考察面试者的表达能力和实际经验,一方面也可以排除掉一堆在简历上吹牛逼的真ds:

    “说说你最牛逼的项目?”

    2,Hypothetical questions:此类问题会假设出一些场景,让面试者进行作答,主要考察面试者的应变能力和实际经验:

    “给你三天,你会如何把xx项目做得很牛逼?”

    3,Probing questions:如果前两类问题答的不错,面试官往往会追加一些问题,以探测面试者能够走多远,此类问题的出现也是一个标志,面试官对你之前的表现感觉还不错:

    “你会如何改进你做过的最牛逼的项目?”

    就具体技术问题而言,考察题目视你的应聘职位和你的过往经历而定,开发岗会侧重代码编写和系统设计,测试岗会更注重测试用例的编写等细节,产品岗要对线框图,交互设计有了解。不过算法设计和代码编写这两块是肯定有的,毕竟这是程序员的看家功夫,这个搞不定就没有然后了。

    算法设计这块我就不多说了,不搞ACM的码农没啥发言权。代码编写的话这里多说两句:和平时的开发不同,面试时的代码往往是在纸上搞的,而非IDE。很多代码写的还不错的选手往往会在纸上代码这个环节被虐的翔尿齐飞,限于篇幅原因我就不多介绍纸上代码的技巧了,还是那句话,多练习。我自己把Software Tools、Elements of programming和C interface and implementation中的代码在纸上写过几遍,又把常见的面试题目练习了三遍,因此纸上代码环节从来没出过问题。

    3,问答环节(5~10分钟):

    如果前两个环节进展顺利,就会进入最后的问答环节。这个环节面试官一般会让面试者提几个感兴趣的问题,以增进相互的了解。

    相对于前两个环节,这个环节会轻松很多。不过依然要注意,关于面试表现的问题最好别问,因为问也问不出来什么,至于待遇,那是HR的事情,技术面试官也无能为力。

    最后根据自己的面试经历说说几个细节的问题

    态度:记住你是去求职,证明自己的能力达到职位需求是你的首要任务。没有必要和面试官抬杠,把面试官搞不爽对你一点好处都没有。我在面试搜狗时就出过这问题(直接表示对面试官的问题的不屑),直接一面被砍掉。

    着装:尽管IT公司大多不需要西装革履,但也别太拖沓,穿整齐些,至少给人很精神的感觉。我面试腾讯时直接搞了一件套头衫+迷彩裤+机车帽,结果是面试官从头到尾就没正眼看过我,直接一面被砍掉。

    交流:面试是一个交流的过程,不明白的一定要主动询问,面试的大忌就是面试官给了一个问题,你一声不吭的搞了一个小时,最后发现搞的不是面试官问的问题,这时就算你很牛逼,面试结果往往也是悲剧。

    7,国企

    国内的IT公司一共去了6家,拿到6个offer,面试19场。

    百度(2轮面试+1轮终面)

    百度的应届生面试分为3轮,2轮技术面和1轮终面,3轮面试连在一起进行,如果搞到第1轮或第2轮就叫你回去,基本上不是悲剧就是备胎。如果进了终面,只要不出岔子,一般问题不大,因为百度每年招的人非常多,印象里有1500人之多。

    可能是招的人特别多从而导致面试官人数不够,百度的面试在所有大公司里面几乎是最随意的,面试官往往都没有经过系统的面试培训,出的题目也只是从网上东拼西凑,比如像C++的虚函数的实现机制此类SB题目层出不穷。而且有些面试官缺乏对面试者最基本的尊重,我有几个同学在百度面试时差点被面哭。

    就我个人而言,我先后参加了百度的实习生面试和正式员工面试。正式员工面试给我感觉相当不错,流程很规范,面试官很nice,问的问题也说的过去;然而实习生面试那两个人就是翔,不但问的问题很二,而且不给我任何交流的空间,同时在面试过程中表现出一副非常不屑的神情,令人极度不爽。

    此外,百度的员工(包括面试官)给我一种工作过度的感觉,说惊悚些就是印堂发黑。结合艳红哥提到的狼性精神,我这号酱油男说啥也不敢去。

    搜狗(2轮面试)

    搜狗的应届生面试分为4轮,2轮技术+1轮HR+一轮Manager,4轮面试是分开的,所以会比较麻烦,毕竟来回跑来跑去的很费时费力。

    我的搜狗面试经历比较诙谐,尽管面了2轮,但2轮都是一面,第一个一面是朋友帮忙推荐过去的面试,第二个一面是参加搜狗笔试获得的面试机会,从这里多少能看出搜狗招人是有点混乱的。

    第一个一面非常囧,当时面试官问我C++,我表示很少用,接下来问了若干智力题,由于被问的有些拙计,于是反问“这种智力题有什么考察度,会做的人一下子做出来,不会做的一天也搞不定”。估计这句话把面试官搞毛了,接下来的气氛变的很紧张,后来面试官反问我“那你觉得应该怎么招人”,我回答“你们应该学学微软”。然后就没有然后了,囧tz。

    第二个一面感觉还成,因为我笔试的题目答得比较出彩。接下来是一个strcpy的纸上代码和一个简单的OS生产者消费者问题,答得还算顺利,可惜依然没有然后,我怀疑可能是没有Hire Count了。

    搜狗面试给我一个很大的教训,就是别装逼,求职就是求职,别和面试官抬杠。在接下来的面试中,我收敛了很多。

    创新工场(2轮面试+4轮终面)

    创新工场本身是一个孵化公司,它的招聘流程是这样的:工场进行笔试和面试初选,然后由工场下面的子公司进行复选,由于子公司众多,因此工场有一个双选会,每个通过初选的求职者可以选择3家工场的子公司,在这个双选会上开复哥很是鼓吹创业,不过效果似乎不太理想 :-D

    工场的初选面试有两轮,然后是3个子公司的复选面试。和其它公司不一样,工场的面试时间非常短,只有25分钟:一个自我介绍,两个无需写代码的题目。有点拼人品,因为这么短的时间很难考察全面。复选面试大多是电面,比较简单,算法题目说下思路就可以。顺便推荐下友盟,感觉这些子公司里面这家的发展潜力最大。

    腾讯(1轮面试)

    腾讯的应届生面试的组成我不太清楚,因为1轮就GG了。

    腾讯的面试也让人很火大,面试官直接拿一个laptop在那里给你放ppt,一个slide一个题目,答完下一个slide,结果是我说的口干舌燥面试官还没说两句话。题目五花八门,从简历到智力题再到为人处事,印象里答了不下七八道题目,累的一逼还没通过,尼玛。

    不过腾讯的面试中见到很多PLMM,目测有很多非技术岗。

    人人(2轮面试+2轮终面)

    人人的应届生面试一般是2轮面试+1轮终面,由于我的笔试和面试发挥不错,因此又得到了一个加面的机会,拿到了人人special offer,待遇非常给力。

    人人的一面面试官非常nice,我当时迟到了30分钟,由于没吃饭因此直接蹭了几个面包,一边吃一边回答问题一边写代码。由于一面主要考察纸上代码,这个是我的强项,因此很轻松的通过了。二面相对杂一些,数据结构,算法,设计模式,多线程等都有涉及,不过问的都不深,也比较容易。

    终面第一面由自我介绍+读过的书+写一段代码组成,正好刚刚读过Sicence of programming,于是就海侃了一顿程序正确性证明的东西,并用这个证明了下自己的代码的正确性,目测效果还不错。

    终面第二面是一个大manager面试,这一轮主要是自己的职业发展路线等其它非技术问题,聊的也比较顺利,然后他直接告诉我我拿到了special offer。

    人民搜索(2轮面试+1轮终面)

    人民搜索的应届生面试由1轮算法面+1轮设计面+1轮终面组成,每一轮面试都是45分钟,时间控制很严。

    和其它公司的面试不同,人搜的算法面试没有自我介绍环节,直接就是搞算法题目,至少要做两道(难度一般,肯定会有一道动态规划),并在纸上写出完整的代码。由于缺乏沟通,这轮面试略感生硬,不过还是比较顺利的通过了。

    设计面试多了一些沟通环节,接下来大部分时间会讨论一个系统的设计,你需要给出这个系统的架构,接下来面试官会不断的追问如何改进该系统以应对大用户量大数据量等极端情况。我这方面的知识不多,只会很土鳖的hash+cache,磕磕绊绊的把这轮过去了。

    终面面试官是一个前google工程师,正巧当时我在google面了好几轮,比较了解google面试的套路,因此很顺利的就通过了。

    总之,人民搜索的待遇比较给力,而且能搞定户口,如果想在北京长待而且视户口很重,那么人搜值得一试。

    8,外企

    国外的IT公司一共去了4家,拿到3个offer,面试16场。

    微软(2轮面试+1轮终面)

    微软从去年开始大规模扩招,印象里以前应届生招几十人,现在一个STC(互联网工程院)就能招二百余人,因此面试难度也有所下降,面试轮数由以前的5轮左右下降到现在的3轮左右。

    这里多提一句,很多人把微软和MSRA(微软亚洲研究院)划等号,甚至有人认为进了微软就等于进了MSRA,其实微软有很多部门,包括STC、STB、MOD等部门,MSRA只是其中一个研究性质很浓的部门。不过MSRA要求极高,和其它部门不同,一般MSRA的FTE只招博士,很少招硕士,招聘需要进行七轮甚至以上的严格面试,难度丝毫不亚于谷歌。而MSRA的实习生则容易很多,名校学生一般有内部推荐就可以搞定。

    我之前在微软进行过实习,因此直接参加了实习生转正面试,三轮面试分别由SDET,SDE和一名高级部门经理进行面试,面试的流程可以参考我之前提到的常规面试流程。与国内IT公司不同,微软不会问语言细节或是OS细节之类的人品问题(就是那种上网搜一下就明白的题目,C++的虚函数实现机制是此类非常典型的人品题),而会集中在算法设计和程序设计上,其中应届生面试又以纸上代码最为严格,即使到了终面依然会有纸上代码编写环节,尽管不会考察特别复杂的算法,但对细节要求的非常严格。好在我之前有微软几位SDE的指导,纸上代码功夫还是不错的,并且实习期间获得了不错的review,因此比较顺利的通过了微软实习转正面试。

    微策略(2轮面试)

    微策略是一家进入中国没多久的外企,规模不大,工作内容主要是大数据分析+数据可视化,面试一般由四轮到六轮面试组成。我比较悲催的直接挂在第二轮面试。

    微策略是我面试的外企中唯一全程使用英语面试的公司,面试官给人的感觉是很smart,但不nice,具体原因我后面说。

    第一轮面试主要问了些面向对象相关的内容,接下来是一些智力题目,我有一道题目(高楼扔鸡蛋)没有说清楚,尽管答案是正确的,但我的推导思路比较繁琐,因此浪费了大量的时间和面试官进行沟通。事后回想下面试官还是很nice的,主要还是自己平时想问题浅尝辄止,才会败在这道题上。

    第二轮面试就让我感到不爽了,我在自我介绍时直接被面试官打断,以至于自己的亮点经历说都说不出来,而且在后续的编程环节中,面试官拒绝和我进行交流,我写完了题目他又说这个和他要求的不太一样,当时我心里就开始暗骂wtf了。估计是前一轮被评为weak hire以至于这一轮的面试就是走走形式吧。

    雅虎(3轮面试+1轮终面)

    雅虎的面试分为3轮技术面和1轮终面,在同一天完成。尽管雅虎公司一直给我一种摇摇欲坠的感觉(经常有传言雅虎可能会被收购),但雅虎公司的员工给我感觉都很nice且很smart,而且比微软的员工要有活力许多。

    三轮技术面试有两轮是典型的技术面,自我介绍+技术提问,由于我在简历上提到“阅读了120本以上的计算机经典书籍”,因此被问到了“看过最经典的计算机书”这样的问题,我拿Brian Kernighan和Rob Pike的The practice of programming吹了一阵,算法题目相对微软要难一些,除了动态规划,也涉及到了后缀数组等不太常见的数据结构,还好之前有所准备,所以回答的还不错。

    终面的面试官是一名移动部门的老大,问了一些数据结构设计和职业规划的问题,并用英文进行了一小段交流,由于这些问题准备的很充分,因此跟他聊的比较high,终面也很顺利的通过了。

    Google(7轮面试)

    Google的面试轮数不定,如果表现良好,4轮面试就可以拿到offer,但如果有某轮面试表现一般,可能会进行加面来进行确认面试者是否合格,拿我自己来说,进行了7轮面试(据说有进行到10轮以上的,不过无法确认可信性)。Google是典型的工程师文化工程师面试,没有终面这个说法,每个面试官都有一票否决权,加上每个面试官考察的点都不一样,因此Google面试是我经历过的难度最高的面试。

    其实Google的面试我本来没抱多大希望,因为Google一直给我可望不可及的感觉,因此面试时也很放松,这种“自暴自弃”的心态反而让我发挥的不错,一步一步走到最后,并拿到offer。

    Google的面试每一轮大约45分钟,时间卡的比较严格。面试题目肯定会包含算法和程序设计(一般体现为纸上代码),同时包含其它各种各样的问题,我经历了策略题(设计一种策略从而在某个游戏中达到优胜)、数据结构设计、系统设计、白盒/黑盒测试、项目介绍等五花八门的题目,题目的类型视面试官的类型而定:学术型的面试官(比如说名校PHD)问的题目偏重算法,工程型的面试官(大多是社招的Googler)问的题目偏重项目经历。面试题目并不像网上传说的那么困难,但是面试官会抛出很多Probing question,让你给出一个optimal solution,这着实让我拙计了几次,不过即便一时间想不到最优解也不要紧,一边保持和面试官的交流一边试探各种可能的思路,这里再次推荐下Polya的How to solve it。

    由于面试轮数很多,因此这里就不依次说每轮面试的细节,可以参考下面的Google面试经历链接。


    《面试体验:Google 篇》

    9,总结

    笔试难度:微策略>人搜>人人>雅虎>搜狗>创新工场>腾讯

    面试难度:Google>雅虎>人搜>微策略>微软>人人>百度>腾讯=搜狗=创新工场

    待遇:Google>人人>人搜>雅虎>微软>百度>创新工场旗下子公司

    毫不犹豫的选择了Google,尽管我自己是.Net流,天天折腾VS和C#,linux和unix啥的都没碰过,但就前景来看,不得不承认Google比微软强太多了。

    老实说我自己进Google感觉像做梦,毕竟不是搞ACM的,大学成绩一般,什么奖学金都没拿过。


     
    但回想一下,这也不全是靠运气:从07年(那时我大三,一行代码没写过)挂科开始,决心开始搞计算机这行,编写自己的第一行靠谱代码,独立完成第一个编程作业,阅读书籍,不懂的就来D版询问各路大神(这里谢过FloridDong,UGLee等大神),然后一边实习一边读书学习,花了半年考研考到帝都,在考研结束的那段时间(四个月假期)精读了数据结构,计算机组成等基础经典书籍,补习自己的基础。到帝都之后,在一场即兴技术口译之后,获得去微软实习的机会,然后在实习中学习编译器知识,创作了自己的编程语言和编译器,加入朋友的创业团队并合作完成了AppStore TOP1的应用,离开团队独立搞定Windows Phone 7上最火的拨号应用、阅读应用和AV应用并在移动互联网中赚到自己的第一桶金,通宵一周完成毕业小论文发表并推荐到核心期刊,为了求职写了三本纸上代码,阅读the Science of programming学会如何证明自己代码的正确性,100%的简历通过率+100%的笔试通过率,最后进入Google。

    现在回想,感慨万千。


    展开全文
  • Java多线程与并发库高级应用

    千次阅读 2015-04-13 22:46:56
    定时器嵌套应用 需求:两秒启动一个炸弹,然后四秒启动一个炸弹,交替运行 方法一:静态全局变量 package ThreadDemo; import java.util.*; public class TimerDemo { //定义一个全局静态变量(不能...

    传统线程技术回顾

    线程就是程序的一条执行线索

    创建线程的两种传统方式

    1. 在Thread子类覆盖的run方法中编写运行代码
    希望代码长期运行下去就编写在一个循环里面
    涉及一个以往知识点:能否在run方法声明上抛出InterruptedException异常,以便省略run方法内部对Thread.sleep()语句的try…catch处理?
    不行,子类不能抛出比父类更多的异常
    2. 在传递给Thread对象的Runnable对象的run方法中编写代码

    总结:查看Thread类的run()方法的源代码,可以看到其实这两种方式都是在调用Thread对象的run方法,如果Thread类的run方法没有被覆盖,并且为该Thread对象设置了一个Runnable对象,该run方法会调用Runnable对象的run方法。

    问题:如果在Thread子类覆盖的run方法中编写了运行代码,也为Thread子类对象传递了一个Runnable对象,那么,线程运行时的执行代码是子类的run方法的代码?还是Runnable对象的run方法的代码?
    子类的,因为子类已经把父类的run方法覆盖了,会以子类的run方法为准。为什么没有执行runnable对象?因为执行该对象的代码在父类中,现在父类Thread的代码被子类覆盖已经没有了,所以不可能执行父类中找runnable对象的方法

    涉及到的一个以往知识点:匿名内部类对象的构造方法如何调用父类的非默认构造方法。

    多线程机制会提高程序的运行效率吗?
    不会,会更慢,因为CPU资源有限

    为什么会有多线程下载呢?

    是为了抢夺服务器带宽

    传统定时器技术回顾

    Timer类与TimerTask类

    void schedule(TimerTask task, long delay, long period); //使用相对时间,delay表示多长时间后执行任务
    void schedule(TimerTask task, Date firstTime, long period); //使用绝对时间
    启动定时器的代码,过10秒钟后启动定时器,然后每过1秒定时器执行一次:
                              new Timer().schedule(new TimerTask(){
    					public void run() {
    						System.out.println(Thread.currentThread().getName());
    					}
    				}, 
    				10000, //过十秒启动定时器
    				1000); //执行周期
    
    代码实例:
    package ThreadDemo;
    import java.util.*;
    public class TimerDemo {
    	public static void main(String[] args) {
    		//5秒之后启动定时器并执行代码
    		new Timer().schedule(new TimerTask(){
    			@Override
    			public void run() {
    				System.out.println("bombing!");
    			}
    			
    		}, 5000);
    		
    		//while循环按打印当前秒数,每过一秒打印一次。
    		while(true){
    			System.out.println(new Date().getSeconds());
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }

    定时器嵌套应用

    需求:两秒启动一个炸弹,然后四秒启动一个炸弹,交替运行
    方法一:静态全局变量
    package ThreadDemo;
    import java.util.*;
    public class TimerDemo {
    	//定义一个全局静态变量(不能定义在内部类里)
    	private static int count = 0;
    	public static void main(String[] args) {
    		//自定义一个内部类继承TimerTask
    		class MyTimerTask extends TimerTask{
    			@Override
    			public void run() {
    				count = (count+1)%2;//让count在0和1之间交替
    				System.out.println("bombing!");
    				new Timer().schedule(new MyTimerTask(),2000+2000*count);//多长时间后执行是一个动态值
    			}
    		}
    		new Timer().schedule(new MyTimerTask(),2000);
    		
    		while(true){
    			System.out.println(new Date().getSeconds());
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}		
    	}
    }
    方法二:两个自定义定时器交替运行
    package ThreadDemo;
    import java.util.*;
    //自定义一个类继承TimerTask,内部调用另一个自定义的TimerTask对象
    class MyTimerTask extends TimerTask{
    	@Override
    	public void run() {
    		System.out.println("bombing!");
    		new Timer().schedule(new MyTimerTask2(),2000);//多长时间后执行是一个动态值
    	}
    }
    //自定义一个类继承TimerTask,内部调用另一个自定义的TimerTask对象
    class MyTimerTask2 extends TimerTask{
    	@Override
    	public void run() {
    		System.out.println("bombing!");
    		new Timer().schedule(new MyTimerTask(),4000);
    	}	
    }
    public class TimerDemo {
    	public static void main(String[] args) {
    		new Timer().schedule(new MyTimerTask(),4000);
    		while(true){
    			System.out.println(new Date().getSeconds());
    			try {
    				Thread.sleep(1000);
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
     
     
     需求:每天凌晨三点发邮件(需要使用绝对时间)
    
    需求:工作日周一至周五发邮件,周末不发(使用开源工具Quartz)

    传统线程互斥技术

    静态方法中不能直接创建内部类的实例对象,因为内部类可以访问外部类的成员变量,而静态方法存在时还没有外部类的对象存在。
    线程安全问题可以用银行转账来解释使用synchronized代码块及其原理使用synchronized方法分析静态方法所使用的同步监视器对象是什么?是所属类的字节码对象
    不论是同步代码块还是同步方法(包括静态同步方法),只要他们使用的锁是同一个对象就可以实现互斥,即同步

    传统线程同步通信技术

    wait与notify实现线程间的通信
    面试题:子线程循环10次,接着主线程循环100,接着又回到子线程循环10次,接着再回到主线程又循环100,如此循环50次,请写出程序。
    最初写出来的代码如下:
    package ThreadDemo;
    public class ThreadTest {
    	private static boolean bShouldMain = false;//这里相当于定义了控制该谁执行的一个信号灯
    	public static void main(String[] args) {
    		new Thread(
    				new Runnable(){
    					public void run() {
    						for(int i=1;i<=50;i++){
    							//这里使用类的字节码对象作为锁进行同步,但是当需要对同步进行分组时就不科学了
    							synchronized(ThreadTest.class){
    								if(bShouldMain){
    									try {
    										ThreadTest.class.wait();
    									} catch (InterruptedException e) {
    										e.printStackTrace();
    									}
    								}
    								for(int j=1;j<=10;j++){
    									System.out.println("sub thread sequence of "+j+", loop of "+i);
    								}
    								bShouldMain = true;
    								ThreadTest.class.notify();
    							}
    						}
    					}
    				}
    		).start();
    
    		//main方法本身是一个线程,这里是主线程的运行代码
    		for(int i=1;i<=50;i++){
    			synchronized(ThreadTest.class){
    				if(!bShouldMain){
    					try {
    						ThreadTest.class.wait();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    				for(int j=1;j<=100;j++){
    					System.out.println("main thread sequence of "+j+", loop of "+i);
    				}
    				bShouldMain = false;
    				ThreadTest.class.notify();
    			}
    		}
    	}
    }
    问题在于两个线程的代码要参照同一个变量,即bShouldMain,即这两个线程的代码要共享数据,所以,把这两个线程的执行代码搬到同一个类中去(注意循环50次不属于各自的业务执行代码):
    package ThreadDemo;
    public class ThreadTest {
    	private static boolean bShouldMain = false;//这里相当于定义了控制该谁执行的一个信号灯
    	public static void main(String[] args) {
    		//因为用到了匿名内部类,内部类访问的局部变量需要用final修饰
    		final Business business = new Business();
    		new Thread(
    				new Runnable(){
    					public void run() {
    						for(int i=1;i<=50;i++){
    							//这里使用类的字节码对象作为锁进行同步,但是当需要对同步进行分组时就不科学了
    							business.sub(i);
    						}
    					}
    				}
    		).start();
    
    		//main方法本身是一个线程,这里是主线程的运行代码
    		for(int i=1;i<=50;i++){
    			business.main(i);
    		}
    	}
    }
    
    class Business {
    	private boolean bShouldSub = true;//最开始该子线程走
    	public synchronized void sub(int i){
    		while(!bShouldSub){//用while而不是if线程醒来还会再次进行判断,防止代码被伪唤醒,代码更健壮。还可以防止生产者消费者问题
    			try {
    				this.wait();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		for(int j=1;j<=10;j++){
    			System.out.println("sub thread sequence of "+j+", loop of "+i);
    		}
    		bShouldSub = false;
    		this.notify();
    	}
    	public synchronized void main(int i){
    		while(bShouldSub){
    			try {
    				this.wait();//这里的锁是this
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    		for(int j=1;j<=100;j++){
    			System.out.println("main thread sequence of "+j+", loop of "+i);
    		}
    		bShouldSub = true;
    		this.notify();
    	}
    }
    经验:
    1. 要用到共同数据(包括同步锁)或共同算法的若干方法应该归在同一个类身上,这种设计正好体现了高类聚和程序的健壮性
    2. 锁是上在要操作的资源的类的内部方法中,而不是线程代码中!好处是以后该类交给任何线程自然就同步了,而不需要考虑互斥同步的问题。
    3. Eclipse中将运行结果保存至文件的操作:Run as-->Run Configuration-->Common-->File处打钩然后选择一个文件

    线程范围内的共享数据

    线程范围内共享变量的概念与作用

    线程范围内共享数据的示意图


    全局变量会被所有的线程都共享,现在需要实现同一个线程内不同模块间变量的共享
    关于线程范围内的变量共享的举例,直接用程序代码进行时说明,创建两个线程,它们都访问了两个模块,两个模块都取值,同一个线程设置的值,只能被相同的线程获取。
    package cn.itcast.heima;
    
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Random;
    
    public class ThreadScopeShareData {
    	private static Map<Thread,Integer> threadData = new HashMap<Thread,Integer>();//定义一个Map,为每一个线程存放独立数据
    	public static void main(String[] args) {
    		//使用循环创建两个线程
    		for(int i=0;i<2;i++){
    			new Thread(new Runnable(){
    				public void run() {
    					int data = new Random().nextInt();//线程产生一个数据
    					System.out.println(Thread.currentThread().getName()+" has put data: "+data);
    					threadData.put(Thread.currentThread(),data);
    					new A().get();//A模块取出数据
    					new B().get();//B模块取出数据
    				}
    			}).start();
    		}
    	}
    
    	//模块A
    	static class A{
    		public void get(){
    			int data = threadData.get(Thread.currentThread());//取出当前线程中变量的值
    			System.out.println("A from "+Thread.currentThread().getName()+" has put data: "+data);
    		}
    	}
    
    	//模块B
    	static class B{
    		public void get(){
    			int data = threadData.get(Thread.currentThread());
    			System.out.println("B from "+Thread.currentThread().getName()+" has put data: "+data);
    		}
    	}
    
    }
    运行结果:

    应用:账户的转入转出。在同一个线程中有一个转入模块和一个转出模块,如果刚刚把钱转入时程序崩溃,转出模块还没有执行,就需要撤销之前的转入操作。并且只能提交己方线程的转出请求,而不能提交其他线程的转出请求。

    ThreadLocal实现线程范围的共享变量

    ThreadLocal类就相当于一个Map,用于实现线程内的数据共享,即对于相同的程序代码,多个模块在同一个线程中运行时要共享一份数据,而在另外线程中运行时又共享另外一份数据。
    每个线程调用全局ThreadLocal对象的set方法,就相当于往其内部的map中增加一条记录,key分别是各自的线程,value是各自的set方法传进去的值。在线程结束时可以调用ThreadLocal.clear()方法,这样会更快释放内存,不调用也可以,因为线程结束后也可以自动释放相关的ThreadLocal变量。
    怎样得到线程结束的通知呢,或者是监听线程死亡的事件?比如监听虚拟机退出,Runtime类代表虚拟机,其addShutdownHook(Thread hook)方法会在虚拟机停止前运行传入线程的代码。那么要得到线程结束的通知,也会用到同样的思想。
    注:api中包名以com.sun打头的是属于底层不被程序员调用的
    ThreadLocal的应用场景:
    • 订单处理包含一系列操作:减少库存量、增加一条流水台账、修改总账,这几个操作要在同一个事务中完成,通常也即同一个线程中进行处理,如果累加公司应收款的操作失败了,则应该把前面的操作回滚,否则,提交所有操作,这要求这些操作使用相同的数据库连接对象,而这些操作的代码分别位于不同的模块类中。
    •  银行转账包含一系列操作: 把转出帐户的余额减少,把转入帐户的余额增加,这两个操作要在同一个事务中完成,它们必须使用相同的数据库连接对象,转入和转出操作的代码分别是两个不同的帐户对象的方法。
    • 例如Strut2的ActionContext,同一段代码被不同的线程调用运行时,该代码操作的数据是每个线程各自的状态和数据,对于不同的线程来说,getContext方法拿到的对象都不相同,对同一个线程来说,不管调用getContext方法多少次和在哪个模块中getContext方法,拿到的都是同一个。
    实验案例:定义一个全局共享的ThreadLocal变量,然后启动多个线程向该ThreadLocal变量中存储一个随机值,接着各个线程调用另外其他多个类的方法,这多个类的方法中读取这个ThreadLocal变量的值,就可以看到多个类在同一个线程中共享同一份数据。
    实现对ThreadLocal变量的封装,让外界不要直接操作ThreadLocal变量。
    • 对基本类型的数据的封装,这种应用相对很少见。
    • 对对象类型的数据的封装,比较常见,即让某个类针对不同线程分别创建一个独立的实例对象。
    总结:一个ThreadLocal代表一个变量,故其中里只能放一个数据,你有两个变量都要线程范围内共享,则要定义两个ThreadLocal对象。如果有一个百个变量要线程共享呢?那请先定义一个实体对象来装这一百个变量,然后在ThreadLocal中存储这一个对象。比如定义一个学生实体,存放姓名,年龄等变量。
    如何设计线程范围内的共享对象?
    • 第一种不优雅的实现方式:
      package cn.itcast.heima;
      import java.util.Random;
      public class ThreadLocalTest {
      	
      	//定义一个ThreadLocal变量存储线程内共享变量
      	private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
      	//定义一个ThreadLocal变量存储线程内的实体对象
      	private static ThreadLocal<MyThreadScopeData> myThreadScopeData = new ThreadLocal<MyThreadScopeData>();
      	public static void main(String[] args) {
      		for(int i=0;i<2;i++){
      			new Thread(new Runnable(){
      				public void run() {
      					int data = new Random().nextInt();
      					System.out.println(Thread.currentThread().getName()+" has put data: "+data);
      					x.set(data);//存入数据,并且数据已与当前线程关联
      					
      					//创建实体对象,并向实体对象中存入数据
      					MyThreadScopeData myData = new MyThreadScopeData();
      					myData.setName("name"+data);
      					myData.setAge(data);
      					//将实体存入ThreadLocal变量中
      					myThreadScopeData.set(myData);
      					
      					new A().get();
      					new B().get();
      				}
      			}).start();
      		}
      	}
      
      	//模块A
      	static class A{
      		public void get(){
      			int data = x.get();//不用指定线程号就取出当前线程中变量的值
      			System.out.println("A from "+Thread.currentThread().getName()+" has put data: "+data);
      			
      			//获取线程内存入的实体对象并获取对象中存储的数据
      			MyThreadScopeData myData = myThreadScopeData.get();
      			System.out.println("A from "+Thread.currentThread().getName()+" getMyData: "+myData.getName()+","+myData.getAge());
      		}
      	}
      
      	//模块B
      	static class B{
      		public void get(){
      			int data = x.get();
      			System.out.println("B from "+Thread.currentThread().getName()+" has put data: "+data);
      		}
      	}
      
      }
      
      //定义一个实体
      class MyThreadScopeData{	
      	private String name;
      	private int age;
      	public String getName() {
      		return name;
      	}
      	public void setName(String name) {
      		this.name = name;
      	}
      	public int getAge() {
      		return age;
      	}
      	public void setAge(int age) {
      		this.age = age;
      	}
      }
    • 第二种优雅的实现方式:
      package cn.itcast.heima;
      import java.util.Random;
      public class ThreadLocalTest {
      	
      	//定义一个ThreadLocal变量存储线程内共享变量
      	private static ThreadLocal<Integer> x = new ThreadLocal<Integer>();
      	
      	public static void main(String[] args) {
      		for(int i=0;i<2;i++){
      			new Thread(new Runnable(){
      				public void run() {
      					int data = new Random().nextInt();
      					System.out.println(Thread.currentThread().getName()+" has put data: "+data);
      					x.set(data);//存入数据,并且数据已与当前线程关联
      					
      					/*
      					//创建实体对象,并向实体对象中存入数据
      					MyThreadScopeData myData = new MyThreadScopeData();
      					myData.setName("name"+data);
      					myData.setAge(data);
      					//将实体存入ThreadLocal变量中
      					myThreadScopeData.set(myData);*/
      					
      					//拿到与本线程相关的实例对象,然后向实体对象中存入数据
      					MyThreadScopeData.getThreadInstance().setName("name"+data);;
      					MyThreadScopeData.getThreadInstance().setAge(data);;
      					new A().get();
      					new B().get();
      				}
      			}).start();
      		}
      	}
      
      	//模块A
      	static class A{
      		public void get(){
      			int data = x.get();//不用指定线程号就取出当前线程中变量的值
      			System.out.println("A from "+Thread.currentThread().getName()+" has put data: "+data);
      			
      			/*//获取线程内存入的实体对象并获取对象中存储的数据
      			MyThreadScopeData myData = myThreadScopeData.get();
      			System.out.println("A from "+Thread.currentThread().getName()+" getMyData: "+myData.getName()+","+myData.getAge());
      		*/
      			//获取本线程相关的实体对象并获取对象中存储的数据
      			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
      			System.out.println("A from "+Thread.currentThread().getName()+" getMyData: "+myData.getName()+","+myData.getAge());
      
      		}
      	}
      
      	//模块B
      	static class B{
      		public void get(){
      			int data = x.get();
      			System.out.println("B from "+Thread.currentThread().getName()+" has put data: "+data);
      			 
      			MyThreadScopeData myData = MyThreadScopeData.getThreadInstance();
      			System.out.println("B from "+Thread.currentThread().getName()+" getMyData: "+myData.getName()+","+myData.getAge());
      
      		}
      	}
      
      }
      
      //定义一个实体
      class MyThreadScopeData{
      	
      	//将该类设计成类似单例模式
      	private MyThreadScopeData(){}
      	private static ThreadLocal<MyThreadScopeData> map = new ThreadLocal<MyThreadScopeData>();
      	public static MyThreadScopeData getThreadInstance(){//这里不需要同步,因为ThreadLocal变量与当前线程相关,各个线程会获取各自的实例
      		MyThreadScopeData instance = map.get();
      		if(instance==null){//如果实例没有,就创建实例并保存
      			instance = new MyThreadScopeData();//保证返回的变量不为null
      			map.set(instance);//存入的实体与当前线程相关,所以该方法不需要加同步
      		}
      		return instance;
      	}
      	
      	private String name;
      	private int age;
      	public String getName() {
      		return name;
      	}
      	public void setName(String name) {
      		this.name = name;
      	}
      	public int getAge() {
      		return age;
      	}
      	public void setAge(int age) {
      		this.age = age;
      	}
      }
      • 优点:
        • 与前一种实现方式相比,这种方式向调用者隐藏ThreadLocal变量
        • 调用者只需要调用实体类中的方法就可以获得与当前线程相关的实例对象了
        • 单例是在任意地方都只能获取同一个对象,这里是在线程内调用获取的是同一个对象

    多个线程访问共享对象和数据的方式

    如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做。
    如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:
    • 将共享数据传递给Runnable实现类:将共享数据封装在另外一个对象中,然后将这个对象通过Runnable接口实现类的构造方法逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,这样容易实现针对该数据进行的各个操作的互斥和通信。
    • 让Runnable实现类去访问共享数据:将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。也可以将成员变量作为局部变量加上final。
    • 上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量,每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。
    • 总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。
    极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。
    面试题示例:
    设计4个线程,其中两个线程每次对j增加1,另外两个线程对j每次减少1。写出程序。
    方式一(上述第三种方式):
    public class MultiThreadShareData {
    	private static ShareData1 data1 = new ShareData1();
    	public static void main(String[] args) {
    		new Thread(new Runnable(){
    			public void run() {
    				while(true){
    					data1.decrement();
    				}
    			}
    
    		}).start();
    		new Thread(new Runnable(){
    			public void run() {
    				while(true){
    					data1.increment();
    				}
    			}
    
    		}).start();
    	}
    }
    
    //定义共享数据类,谁拥有数据,就提供操作该数据的方法
    class ShareData1 implements Runnable{
    	private int j=0;
    	public synchronized void increment(){
    		j++;
    	}
    	public synchronized void decrement(){
    		j--;
    	}
    	public void run() {
    
    	}
    }
    方式二(上述第二种方式):
    public class ThreadTest1 
    { 
    	private int j; 
    	public static void main(String args[]){ 
    		ThreadTest1 tt=new ThreadTest1(); 
    		Inc inc=tt.new Inc(); 
    		Dec dec=tt.new Dec(); 
    		for(int i=0;i<2;i++){ 
    			Thread t=new Thread(inc); 
    			t.start(); 
    			t=new Thread(dec); 
    			t.start(); 
    		} 
    	} 
    	
    	private synchronized void inc(){ 
    		j++; 
    		System.out.println(Thread.currentThread().getName()+"-inc:"+j); 
    	} 
    	private synchronized void dec(){ 
    		j--; 
    		System.out.println(Thread.currentThread().getName()+"-dec:"+j); 
    	} 
    
    	class Inc implements Runnable{ 
    		public void run(){ 
    			for(int i=0;i<100;i++){ 
    				inc(); 
    			} 
    		} 
    	} 
    
    	class Dec implements Runnable{ 
    		public void run(){ 
    			for(int i=0;i<100;i++){ 
    				dec(); 
    			} 
    		} 
    	} 
    } 

    Java5中的线程并发库

    看java.util.concurrent包及子包的API帮助文档
    • 看concurrent包的帮助文档页面,对并发库中涉及的内容有一个总体上的介绍:在并发编程中很常用的实用工具类。
    • 如何看包的API帮助文档:可以先找到该包下的某个类的帮助页面,然后在该页面的顶部单击package超链接。
    java.util.concurrent.atomic包
    • 查看atomic包文档页下面的介绍:类的小工具包,支持在单个变量上解除锁的线程安全编程。
    • 可以对基本数据,对数组中的基本数据,对类中的基本数据等进行操作
    • 通过如下两个方法快速理解atomic包的意义:
      • AtomicInteger类的boolean compareAndSet(expectedValue, updateValue); 
      • AtomicIntegerArray类的int addAndGet(int i, int delta);
    • 顺带解释volatile类型的作用,需要查看java语言规范。Volatile的意思是说:在jvm中,一个线程更新了共享变量i,另外一个线程立即去读取共享区中的i时,读到的可能不是刚才另外那个线程更新过的结果,这就类似数据库中的事务隔离级别中的read uncommited,volatile就是解决这个问题的。
    了解java.util.concurrent.lock包
    • 在下面通过案例详细讲解

    原子性操作类的应用

    AtomicInteger类

    java.util.concurrent.atomic包下的AtomicInteger类可以解决 多线程访问同一个整数的问题
    首先通过构造函数AtomicInteger(int initialValue)创建一个给定值的原子整数
    然后比如调用对象上的addAndGet(int delta)方法返回对象中的数和delta的和(传入负数就是相减),该方法的调用过程中别的线程无法参与进来,因此自动实现了线程同步。还有其他的方法比如decrementAndGet()和incrementAndGet()
    一般用于定义被多线程访问的成员变量而不是局部变量,因为局部变量会在每个线程中都有一份
    类似的有AtomicBoolean、AtomicLong类操作不同的数据

    AtomicIntegerArray类

    java.util.concurrent.atomic包下的AtomicIntegerArray类用于解决 多线程操作数组中的一个整数的问题
    addAndGet(int i, int delta) 以原子方式将给定值与索引 i 的元素相加。

    AtomicIntegerFieldUpdater类

    解决 多线程操作类的对象中存储的整数的问题
    static <U> AtomicIntegerFieldUpdater<U>  newUpdater(Class<U> tclass, String fieldName) 使用给定字段为对象创建和返回一个更新器 
    int addAndGet(T obj, int delta) 以原子方式将给定值添加到此更新器管理的给定对象的字段当前值

    线程池 

    线程池的概念
    • 首先介绍在Tcp服务器编程模型的原理,每一个客户端连接用一个单独的线程为之服务,当与客户端的会话结束时,线程也就结束了,即每来一个客户端连接,服务器端就要创建一个新线程。
    • 线程池首先创建一些线程,它们的集合称为线程池,当服务器接受到一个客户请求后,就从线程池中取出一个空闲的线程为之服务,服务完后不关闭该线程,而是将该线程还回到线程池中。
    • 任务是提交给整个线程池,一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务,池中的线程会各自同时执行一个任务,其他任务会排队等待
    • 所有任务执行完后线程还存在,程序不会结束,需要手动关闭线程池
    • 不需要和传统方式一样把任务交给特定的线程执行,而是把任务交给线程池,如果池中有空闲线程,就执行该任务,否则任务就等待被执行
    Executors类的应用
    • 创建固定大小的线程池
    • 创建缓存线程池//线程数可随需求变化
    • 创建单一线程池(实现线程死掉之后重新启动)
    关闭线程池
    • shutdown与shutdownNow的比较
    用线程池启动定时器
    • 调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
    • 支持间隔重复任务的定时方式(scheduleAtFixedRate)。
    • 所有的 schedule 方法都接受相对(相对现在) 延迟和周期作为参数,而不是绝对的时间或日期。将以 Date 所表示的绝对时间转换成要求的形式很容易。例如,要安排在某个以后的 Date 运行,可以使用:schedule(task, date.getTime() - System.currentTimeMillis(), TimeUnit.MILLISECONDS)
    package cn.itcast.heima;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.TimeUnit;
    
    public class ThreadPool {
    	public static void main(String[] args) {
    		//创建一个固定大小的线程池,里面有3个线程
    		ExecutorService threadPool = Executors.newFixedThreadPool(3);
    		//创建一个缓存的线程池,池中线程不够,会自动创建新的线程
    		//ExecutorService threadPool = Executors.newCachedThreadPool();
    		//创建单一线程池,好处是始终保证池中有一个线程存在,如果线程死了,会再创建一个
    		//ExecutorService threadPool = Executors.newSingleThreadExecutor();
    		
    		//通过循环向池中添加10个任务,但是同时只有3个任务被池中的3个线程执行,只有这3个任务都执行结束了才会执行其余任务 
    		for(int i=1;i<=10;i++){
    			final int task = i;
    			threadPool.execute(new Runnable(){
    				public void run() {
    					//任务循环10遍,线程池中的某一个线程就会执行该任务
    					for(int j=1;j<=10;j++){
    						try {
    							Thread.sleep(20);
    						} catch (InterruptedException e) {
    							// TODO Auto-generated catch block
    							e.printStackTrace();
    						}
    						System.out.println(Thread.currentThread().getName()+" is looping of "+j+" for task "+task);
    					}
    				}
    			});
    		}
    		System.out.println("all of 10 tasks has committed!");
    		threadPool.shutdown();//关闭线程池(当所有的任务执行完毕,所有的线程都空闲下来时,结束池中的所有线程)
    		//threadPool.shutdownNow();//关闭线程池(立即结束池中的所有线程,即使还有任务没有执行完毕)
    		
    		//创建一个调度线程池
    		Executors.newScheduledThreadPool(3).schedule(
    				new Runnable(){
    					public void run() {
    						System.out.println("bombing!");
    					}
    				}, 
    				3,//delay 
    				TimeUnit.SECONDS);
    	}
    }

    Callable&Future

    使用Callable<T>接口编写的线程中的任务会在线程运行结束会返回一个结果,该结果类型为Future<V>
    Future通过get()方法取得返回结果,线程没有结束get方法会一直等待。
    返回的结果类型和Callable返回的结果类型必须一致,这是通过泛型来实现的。
    Callable要采用ExecutorSevice的submit方法提交,而不是execute方法,因为execute方法没有返回值,返回的future对象可以取消任务。
    CompletionService接口用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。
    • 好比我同时种了几块地的麦子,然后就等待收割。收割时,则是哪块先成熟了,则先去收割哪块麦子。
    应用:既然苦苦等待线程运行完毕返回的结果,还不如直接调用一个方法,运行完成就返回一个结果。
    package cn.itcast.heima;
    
    import java.util.Random;
    import java.util.concurrent.Callable;
    import java.util.concurrent.CompletionService;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorCompletionService;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class CallableAndFuture {
    	public static void main(String[] args) {
    		ExecutorService threadPool1 = Executors.newSingleThreadExecutor();
    		//使用Callable编写任务,Future接受返回结果
    		Future<String> future = threadPool1.submit(
    				new Callable<String>(){
    					public String call() throws Exception {
    						Thread.sleep(2000);
    						return "hello";
    					}
    				});
    		System.out.println("等待结果");
    		try {
    			System.out.println("拿到结果: "+future.get());
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} catch (ExecutionException e) {
    			e.printStackTrace();
    		}
    		
    		ExecutorService threadPool2 = Executors.newFixedThreadPool(10);
    		
    		CompletionService<Integer> completionService = new ExecutorCompletionService<Integer>(threadPool2);
    		//向CompletionService中提交10个任务
    		for(int i=1;i<=10;i++){
    			final int sequence = i;//记录任务序号
    			completionService.submit(
    					new Callable<Integer>(){
    						public Integer call() throws Exception {
    							Thread.sleep(new Random().nextInt(5000));
    							return sequence;//返回的是当前任务的序号
    						}
    					});
    		}
    		//获取结果
    		for (int i = 0; i < 10; i++) {
    			try {
    				System.out.println(completionService.take().get());
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			} catch (ExecutionException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    

    java5的线程锁技术Lock&Condition实现线程同步通信

    Lock

    Lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现 同步互斥的效果,它们必须用同一个Lock对象。锁是上在要操作的资源的类的内部方法中,而不是线程代码中!
    package cn.itcast.heima;
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class LockTest {
    	public static void main(String[] args) {
    		new LockTest().init();
    	}
    	
    	private void init(){
    		final Outputer outputer = new Outputer();
    		new Thread(new Runnable(){
    			public void run() {
    				while(true){
    					try {
    						Thread.sleep(10);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					outputer.output("zhangxiaoxiang");//线程如果不上锁,输出就会被打乱
    				}
    			}
    		}).start();
    		
    		new Thread(new Runnable(){
    			public void run() {
    				while(true){
    					try {
    						Thread.sleep(10);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					outputer.output("lihuoming");
    				}
    			}
    		}).start();
    	}
    
    	static class Outputer{
    		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();//一定要释放锁,所以用finally
    			}
    		}
    		//传统的使用synchronized关键字进行同步互斥的方法
    		public synchronized void output2(String name){
    			int len = name.length();
    			for(int i=0;i<len;i++){
    					System.out.print(name.charAt(i));
    			}
    			System.out.println();
    		}
    	}
    }

    读写锁ReadWriteLock

    分为读锁和写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由jvm自己控制的,你只要上好相应的锁即可。如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁;如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。读与读不互斥,读与写互斥,写与写也互斥。总之,读的时候上读锁,写的时候上写锁!
    读写锁技能提高性能,又能实现互斥。如果只是使用Lock,那么读和读的线程之前也会互斥
    代码示例:创建3个读线程,3个写线程:
    package cn.itcast.heima;
    
    import java.util.Random;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    
    public class ReadWriteLockTest {
    	public static void main(String[] args) {
    		final Queue3 q3 = new Queue3();
    		//同时创建3个读取线程,3个写入线程
    		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();
    		}
    	}
    }

    缓存对象和缓存系统

    在Hibernate中,获取数据库数据实体有两种方式:
    User user = session.get(id,User.class);//如果数据对象不存在,返回null
    User user = session.load(id,User.class);//如果数据对象不存在,则加载的是一个缓存代理对象
    User$Proxy extends User{
    	private Integer id = id;
    	User realUser = null;
    	getName(){
    		if(realUser==null){
    			realUser = session.get(id);
    			if(realUser==null)
    				throw new Exception();
    		}
    		return realUser.getName();
    	}
    }
    API文档中对ReentrantReadWriteLock示例代码:
    /*
     * 示例用法。下面的代码展示了如何利用重入来执行升级缓存后的锁降级(为简单起见,省略了异常处理):
     * 当有许多读的线程访问一个数据实体时,如果有一个线程发现该数据实体为空,该线程就需要将读锁释放,上写锁,写入数据
     * 然后在释放写锁前重新上读锁
     */
    class CachedData {
    	Object data;
    	volatile boolean cacheValid;//判断实体中是否有值
    	ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
    	void processCachedData() {
    		rwl.readLock().lock();
    		if (!cacheValid) {
    			// Must release read lock before acquiring write lock
    			rwl.readLock().unlock();
    			rwl.writeLock().lock();
    			// Recheck state because another thread might have acquired
    			//   write lock and changed state before we did.
    			if (!cacheValid) {
    				data = ...//加载数据
    						cacheValid = true;
    			}
    			// Downgrade by acquiring read lock before releasing write lock
    			rwl.readLock().lock();
    			rwl.writeLock().unlock(); // Unlock write, still hold read
    		}
    		use(data);
    		rwl.readLock().unlock();
    	}
    }
    面试题:请设计一个 缓存系统
    以上是对单个对象进行缓存,缓存系统就是可以装很多个对象。
    要拿对象别直接找数据库,而是访问缓存系统。当缓存系统的数据被访问时,就应该检查内部是否有该数据,如果有,就直接返回,如果没有就查询数据库,然后把数据存入内存中,下次就直接返回该数据不需要再查数据库了。
    package cn.itcast.heima;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.concurrent.locks.ReadWriteLock;
    import java.util.concurrent.locks.ReentrantReadWriteLock;
    public class CacheDemo {
    	//定义一个Map,存储多个对象
    	private Map<String, Object> cache = new HashMap<String, Object>();
    	public static void main(String[] args) {}
    	private ReadWriteLock rwl = new ReentrantReadWriteLock();
    	//获取对象的方法,当多个线程来读(即获取对象时)不需要互斥,只有写的时候才需要互斥,因此需要使用读写锁
    	public  Object getData(String key){
    		rwl.readLock().lock();//先上读锁,这样多个线程都可以来读
    		Object value = null;
    		try{
    			value = cache.get(key);
    			//如果线程读的时候发现数据为空,那么就把读锁释放,禁止数据的读取,然后上写锁
    			if(value == null){
    				rwl.readLock().unlock();
    				rwl.writeLock().lock();
    				try{
    					//再次判断数据是否存在,因为当一个线程获取写锁完成了数据填充并释放了写锁,另外一个线程也可能获取了写锁,会重复填充数据。
    					if(value==null){
    						value = "aaaa";//实际是去queryDB();即查询数据库
    					}
    				}finally{
    					rwl.writeLock().unlock();//数据填充完,释放写锁
    				}
    				rwl.readLock().lock();//恢复为读锁
    			}
    		}finally{
    			rwl.readLock().unlock();
    		}
    		return value;
    	}
    }

    Condition

    Lock和Condition的关系:Lock只能实现 互斥(一个线程持有锁,另外的线程不能访问该锁),但是不能实现 通信(即使获取了CPU的执行权,但是也可以让出执行权通知另外的线程执行),一个是哥们你不能干,一个是哥们你可以干。
    Condition的功能类似于在传统线程计数中的Object.wait和Object.notify的功能。在等待 Condition 时,允许发生“虚假唤醒”,这通常作为对基础平台语义的让步。对于大多数应用程序,这带来的实际影响很小,因为 Condition 应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序程序员总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。
    package cn.itcast.heima;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ConditionCommunication {
    	public static void main(String[] args) {
    
    		final Business business = new Business();
    		new Thread(
    				new Runnable() {
    					public void run() {
    						for(int i=1;i<=50;i++){
    							business.sub(i);
    						}
    					}
    				}
    				).start();
    
    		for(int i=1;i<=50;i++){
    			business.main(i);
    		}
    	}
    
    	static class Business {//解决两个java文件中都有该类的问题:这里的完整类名是ConditionCommunicaiton.Business,另一个是Business
    		Lock lock = new ReentrantLock();
    		Condition condition = lock.newCondition();//获取该锁的condition对象
    		private boolean bShouldSub = true;
    		public void sub(int i){
    			lock.lock();
    			try{
    				while(!bShouldSub){//while循环防止虚假唤醒(线程被唤醒但是运行条件并不满足)
    					try {
    						condition.await();//调用condition对象特有的await方法,调用wait方法不报错,因为condition也是一个object,但是不是预期的方法。
    					} catch (Exception e) {
    						e.printStackTrace();
    					}
    				}
    				for(int j=1;j<=10;j++){
    					System.out.println("sub thread sequence of " + j + ",loop of " + i);
    				}
    				bShouldSub = false;
    				condition.signal();//调用condition对象特有的signal方法
    			}finally{
    				lock.unlock();
    			}
    		}
    
    		public void main(int i){
    			lock.lock();
    			try{
    				while(bShouldSub){
    					try {
    						condition.await();
    					} catch (Exception e) {
    						e.printStackTrace();
    					}
    				}
    				for(int j=1;j<=100;j++){
    					System.out.println("main thread sequence of " + j + ",loop of " + i);
    				}
    				bShouldSub = true;
    				condition.signal();
    			}finally{
    				lock.unlock();
    			}
    		}
    	}
    }
    一个锁内部可以有多个Condition,即有 多路等待和通知,可以参看jdk1.5提供的Lock与Condition实现的 可阻塞队列的应用案例(见下面代码),从中除了要体味算法,还要体味面向对象的封装。在传统的线程机制中一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象。(如果只用一个Condition,两个存放的线程都在等,一旦一个放的线程进去了,那么它通知 可能会导致另一个放的线程接着往下走。)
    可阻塞队列起到缓冲的效果,应用:使用该队列实现寻呼信息的存放和取出,当队列满时,存放线程等待,当队列空时,取出线程等待。
    class BoundedBuffer {
    	final Lock lock = new ReentrantLock();
    	final Condition notFull  = lock.newCondition(); 
    	final Condition notEmpty = lock.newCondition(); 
    
    	final Object[] items = new Object[100];
    	int putptr, takeptr, count;
    
    	public void put(Object x) throws InterruptedException {
    		lock.lock();
    		try {
    			while (count == items.length) //如果缓冲区中存入数据的数量等于缓冲区的长度,即缓冲区满了,那么存入线程就进入等待
    				notFull.await();
    			items[putptr] = x; 
    			if (++putptr == items.length) putptr = 0;//将存的指针指向缓冲区第一个坐标
    			++count;
    			notEmpty.signal();//只换醒取出线程
    		} finally {
    			lock.unlock();
    		}
    	}
    
    	public Object take() throws InterruptedException {
    		lock.lock();
    		try {
    			while (count == 0) //缓冲区中没有数据可取了,取出线程就进入等待
    				notEmpty.await();
    			Object x = items[takeptr]; 
    			if (++takeptr == items.length) takeptr = 0;//将取的指针指向第一个坐标
    			--count;
    			notFull.signal();//只换醒存入线程
    			return x;
    		} finally {
    			lock.unlock();
    		}
    	} 
    }
    并法库包中的ArrayBlockingQueue 类提供了这项功能,因此没有理由去实现这个示例类
    可以实现多个线程按顺序执行。下面代码实现主线程,子线程1,子线程2轮流交替运行:
    package cn.itcast.heima;
    
    import java.util.concurrent.locks.Condition;
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class ThreeConditionCommunication {
    	public static void main(String[] args) {
    		final Business business = new Business();
    		new Thread(
    				new Runnable() {
    					public void run() {	
    						for(int i=1;i<=50;i++){
    							business.sub2(i);
    						}					
    					}
    				}
    				).start();
    
    		new Thread(
    				new Runnable() {
    					public void run() {				
    						for(int i=1;i<=50;i++){
    							business.sub3(i);
    						}					
    					}
    				}
    				).start();		
    
    		for(int i=1;i<=50;i++){
    			business.main(i);
    		}	
    	}
    
    	static class Business {
    		Lock lock = new ReentrantLock();
    		//几个线程轮流执行就定义几个condition
    		Condition condition1 = lock.newCondition();
    		Condition condition2 = lock.newCondition();
    		Condition condition3 = lock.newCondition();
    		private int shouldSub = 1;//条件,该第几个线程运行
    		public  void sub2(int i){
    			lock.lock();
    			try{
    				while(shouldSub != 2){
    					try {
    						condition2.await();
    					} catch (Exception e) {
    						e.printStackTrace();
    					}
    				}
    				for(int j=1;j<=10;j++){
    					System.out.println("sub2 thread sequence of " + j + ",loop of " + i);
    				}
    				shouldSub = 3;//该第三个线程运行了
    				condition3.signal();
    			}finally{
    				lock.unlock();
    			}
    		}
    
    		public  void sub3(int i){
    			lock.lock();
    			try{
    				while(shouldSub != 3){
    					try {
    						condition3.await();
    					} catch (Exception e) {
    						e.printStackTrace();
    					}
    				}
    				for(int j=1;j<=20;j++){
    					System.out.println("sub3 thread sequence of " + j + ",loop of " + i);
    				}
    				shouldSub = 1;
    				condition1.signal();
    			}finally{
    				lock.unlock();
    			}
    		}		  
    
    		public  void main(int i){
    			lock.lock();
    			try{
    				while(shouldSub != 1){
    					try {
    						condition1.await();
    					} catch (Exception e) {
    						e.printStackTrace();
    					}
    				}
    				for(int j=1;j<=100;j++){
    					System.out.println("main thread sequence of " + j + ",loop of " + i);
    				}
    				shouldSub = 2;
    				condition2.signal();
    			}finally{
    				lock.unlock();
    			}
    		}
    	}
    }

    Semaphore实现信号灯

    Semaphore(信号)可以控制当前访问资源的线程个数,并提供了同步机制。例如,实现一个文件允许的并发访问数。
    • Semaphore实现的功能就类似厕所有5个坑,假如有十个人要上厕所,那么同时能有多少个人去上厕所呢?同时只能有5个人能够占用,当5个人中的任何一个人让开后,其中在等待的另外5个人中又有一个可以占用了。
    • 同步锁是一个坑,现在是多个坑一起参与资源的管理
    • 另外等待的5个人中可以是随机获得优先机会,也可以是按照先来后到的顺序获得机会,这取决于构造Semaphore对象时传入的参数选项:public Semaphore(int permits, boolean fair),fair为 true则保证此信号量在争用时按先进先出的顺序授予许可
    单个信号量的Semaphore对象可以实现互斥锁的功能,这时这个信号量就类似一个锁,并且可以是由一个线程获得了“锁”,再由另一个线程释放“锁”,这可应用于死锁恢复的一些场合(锁只能被本线程释放,不能被其他线程释放)。
    package cn.itcast.heima;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Semaphore;
    
    public class SemaphoreTest {
    	public static void main(String[] args) {
    		ExecutorService service = Executors.newCachedThreadPool();
    		final  Semaphore sp = new Semaphore(3);
    		//循环10遍相当于创建了10个线程
    		for(int i=0;i<10;i++){
    			Runnable runnable = new Runnable(){
    					public void run(){
    					try {
    						sp.acquire();//获得一盏信号灯,该线程就可以运行了
    					} catch (InterruptedException e1) {
    						e1.printStackTrace();
    					}
    					System.out.println("线程" + Thread.currentThread().getName() + 
    							"进入,当前已有" + (3-sp.availablePermits()) + "个并发");//获取了可获得许可数
    					try {
    						Thread.sleep((long)(Math.random()*10000));
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println("线程" + Thread.currentThread().getName() + 
    							"即将离开");					
    					sp.release();//线程离开,就释放灯
    					//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
    					System.out.println("线程" + Thread.currentThread().getName() + 
    							"已离开,当前已有" + (3-sp.availablePermits()) + "个并发");					
    				}
    			};
    			service.execute(runnable);			
    		}
    	}
    }
    
    管理停车位,一个小的电子设备,实时性强就要semaphore。

    其他同步工具类

    CyclicBarrier

    一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。
    表示大家彼此等待,大家集合好后才开始出发,分散活动后又在指定地点集合碰面,这就好比整个公司的人员利用周末时间集体郊游一样,先各自从家出发到公司集合后,再同时出发到公园游玩,在指定地点集合后再同时开始就餐,…。
    Cyclic:循环的,有周期性的,Barrier:障碍物,屏障。多个线程干完各自的任务,在不同的时刻到达集合点后,就可以接着忙各自的工作去了,再到达新的集合点,再去忙各自的工作,到达集合点了用CyclicBarrier对象的await方法表示。想在什么地方集合,就在什么地方调用await方法
    package cn.itcast.heima;
    import java.util.concurrent.CyclicBarrier;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class CyclicBarrierTest {
    
    	public static void main(String[] args) {
    		ExecutorService service = Executors.newCachedThreadPool();
    		final  CyclicBarrier cb = new CyclicBarrier(3);//约定需要3个同时到达的线程
    		for(int i=0;i<3;i++){
    			Runnable runnable = new Runnable(){
    					public void run(){
    					try {
    						Thread.sleep((long)(Math.random()*10000));	
    						//getNumberWaiting返回当前在屏障处等待的参与者数目。
    						System.out.println("线程" + Thread.currentThread().getName() + 
    								"即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));						
    						cb.await();//在这里到达集合点。都到达之后才会向下执行
    						
    						Thread.sleep((long)(Math.random()*10000));	
    						System.out.println("线程" + Thread.currentThread().getName() + 
    								"即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
    						cb.await();	
    						Thread.sleep((long)(Math.random()*10000));	
    						System.out.println("线程" + Thread.currentThread().getName() + 
    								"即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));						
    						cb.await();						
    					} catch (Exception e) {
    						e.printStackTrace();
    					}				
    				}
    			};
    			service.execute(runnable);
    		}
    		service.shutdown();
    	}
    }

    CountDownLatch

    Latch:门闩,闩锁
    犹如倒计时计数器,调用CountDownLatch对象的await方法让当前线程等待计数器到0,调用countDown方法就将计数器减1,当计数到达0时,则所有等待者或单个等待者开始执行。
    可以实现一个人(也可以是多个人)等待其他所有人都来通知他,可以实现一个人通知多个人的效果,类似裁判一声口令,运动员同时开始奔跑,或者所有运动员都跑到终点后裁判才可以公布结果,用这个功能做百米赛跑的游戏程序不错哦!还可以实现一个计划需要多个领导都签字后才能继续向下实施的情况。
    package cn.itcast.heima;
    import java.util.concurrent.CountDownLatch;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    public class CountdownLatchTest {
    	public static void main(String[] args) {
    		ExecutorService service = Executors.newCachedThreadPool();
    		final CountDownLatch cdOrder = new CountDownLatch(1);//计数器的初始值是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();//让当前线程等待该计数器到0
    						System.out.println("线程" + Thread.currentThread().getName() + 
    						"已接受命令");								
    						Thread.sleep((long)(Math.random()*10000));	
    						System.out.println("线程" + Thread.currentThread().getName() + 
    								"回应命令处理结果");						
    						cdAnswer.countDown();//在当前线程中让计数器减1					
    					} catch (Exception e) {
    						e.printStackTrace();
    					}				
    				}
    			};
    			service.execute(runnable);
    		}		
    		try {
    			Thread.sleep((long)(Math.random()*10000));
    			System.out.println("线程" + Thread.currentThread().getName() + 
    					"即将发布命令");						
    			cdOrder.countDown();//在主线程中将计数器的计数减1,类似于裁判比赛倒计时
    			System.out.println("线程" + Thread.currentThread().getName() + 
    			"已发送命令,正在等待结果");	
    			cdAnswer.await();//让主线程等待该计数器为0。当上面的3个线程都运行完计数器就为0了。类似于裁判等待运动员到达公布成绩
    			System.out.println("线程" + Thread.currentThread().getName() + 
    			"已收到所有响应结果");	
    		} catch (Exception e) {
    			e.printStackTrace();
    		}				
    		service.shutdown();
    	}
    }

    Exchanger

    用于实现两个人之间的数据交换,每个人在完成一定的事务后想与对方交换数据,第一个先拿出数据的人将一直等待第二个人拿着数据到来时,才能彼此交换数据。
    好比两个毒贩要进行交易,一手交钱、一手交货,不管谁先来到接头地点后,就处于等待状态了,当另外一方也到达了接头地点(所谓到达接头地点,也就是到到达了准备接头的状态)时,两者的数据就立即交换了,然后就又可以各忙各的了。
    exchange方法就相当于两手高高举着待交换物,等待人家前来交换,一旦人家到来(即人家也执行到exchange方法),则两者立马完成数据的交换
    package cn.itcast.heima;
    import java.util.concurrent.Exchanger;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ExchangerTest {
    
    	public static void main(String[] args) {
    		ExecutorService service = Executors.newCachedThreadPool();
    		final Exchanger<String> exchanger = new Exchanger<String>();
    		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){	}				
    			}	
    		});		
    	}
    }

    可阻塞的队列BlockingQueue

    队列:先进先出。包含固定长度队列和不固定长度队列
    什么是可阻塞队列:队列满了添加线程会阻塞等待,队列空了获取线程也会阻塞等待。非阻塞队列则会直接报错
    阻塞队列的作用与实际应用,阻塞队列的实现原理。
    阻塞队列与Semaphore有些相似,但也不同,阻塞队列是一方存放数据,另一方释放数据,Semaphore通常则是由同一方设置和释放信号量。
    BlockingQueue接口的子类提供了可阻塞队列功能,ArrayBlockingQueue类是固定大小的,LinkedBlockingQueue类可以是不固定大小的(数组是连续的一片内存,链表是不连续的内存)
    在其接口类BlockingQueue中提供了3个插入方法。add抛异常,offer返回真假值,put方法阻塞。3个取的方法:remove抛出异常,poll返回null,take可阻塞
    只有put方法和take方法才具有阻塞功能
    用3个空间的队列来演示阻塞队列的功能和效果。
    package cn.itcast.heima;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    public class BlockingQueueTest {
    	public static void main(String[] args) {
    		final BlockingQueue queue = new ArrayBlockingQueue(3);//创建一个可阻塞数组队列,允许放3个数据
    		for(int i=0;i<2;i++){
    			new Thread(){
    				public void run(){
    					while(true){
    						try {
    							Thread.sleep((long)(Math.random()*1000));
    							System.out.println(Thread.currentThread().getName() + "准备放数据!");							
    							queue.put(1);//向队列中添加数据
    							System.out.println(Thread.currentThread().getName() + "已经放了数据," + 							
    										"队列目前有" + queue.size() + "个数据");
    						} catch (InterruptedException e) {
    							e.printStackTrace();
    						}
    					}
    				}
    			}.start();
    		}
    		
    		new Thread(){
    			public void run(){
    				while(true){
    					try {
    						//将此处的睡眠时间分别改为100和1000,观察运行结果
    						Thread.sleep(1000);//可以调整取的快慢
    						System.out.println(Thread.currentThread().getName() + "准备取数据!");
    						//下面两句不能保证是原子性的,一取就马上打印
    						queue.take();
    						System.out.println(Thread.currentThread().getName() + "已经取走数据," + 							
    								"队列目前有" + queue.size() + "个数据");					
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    			}
    		}.start();			
    	}
    }
    用两个具有1个空间的队列来实现同步通知的功能(你一下,我一下,轮流执行)。
    在前面用Condition实现的同步通知的例子的基础上,改为用阻塞队列来实现。
    第一个线程:A.take()……..B.put()
    第二个线程:B.take()……..A.put()
    package cn.itcast.heima;
    
    import java.util.Collections;
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class BlockingQueueCommunication {
    	public static void main(String[] args) {
    		final Business business = new Business();
    		new Thread(
    				new Runnable() {
    					public void run() {
    						for(int i=1;i<=50;i++){
    							business.sub(i);
    						}
    					}
    				}
    				).start();
    
    		for(int i=1;i<=50;i++){
    			business.main(i);
    		}
    	}
    
    	static class Business {
    		//产生两个阻塞队列
    		BlockingQueue<Integer> queue1 = new ArrayBlockingQueue<Integer>(1);//控制子线程
    		BlockingQueue<Integer> queue2 = new ArrayBlockingQueue<Integer>(1);//控制主线程
    
    		//构造代码块(或匿名构造方法),对所有对象进行初始化。不能用静态代码块,因为是对成员变量进行初始化
    		{
    			Collections.synchronizedMap(null);
    			try {
    				System.out.println("xxxxxdfsdsafdsa");
    				queue2.put(1);//一开始主线程不运行,就通过构造代码块在程序运行之前在队列2中放入数据
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    
    		public  void sub(int i){//这里如果加synchronized关键字那么会出现死锁
    			try {
    				queue1.put(1);//程序一执行让子线程先运行,就先在队列1中放入元素
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    			for(int j=1;j<=10;j++){
    				System.out.println("sub thread sequece of " + j + ",loop of " + i);
    			}
    			try {
    				queue2.take();//子线程运行完毕取走队列2的元素让主线程运行
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    
    		public  void main(int i){
    			try {
    				queue2.put(1);//由于程序开始执行时队列2是满的所以会阻塞在这里
    			} catch (InterruptedException e1) {
    				e1.printStackTrace();
    			}
    			for(int j=1;j<=100;j++){
    				System.out.println("main thread sequece of " + j + ",loop of " + i);
    			}
    			try {
    				queue1.take();
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }

    同步集合Concurrent Collections

    传统集合类在并发访问(多个线程访问)时会出现问题。比如死循环。
    • 此时就需要在读的时候不能写,写的时候不能读,但可以并发地读。
    • 如果不被多个线程并发访问,就使用传统集合
    • 		/*
      		 * 在进行元素读取的时候不要进行其他操作,会出现死循环。
      		 * 比如hasNext正好读到最后一个元素时,其他线程进来执行remove操作,hasNext内部cursor!=count,始终返回true,成为死循环
      		 */
      		count=4;
      		while(hasNext()){
      			next(){cursor++}//next方法内部原理
      		}
      		//hasNext方法内部原理
      		hasNext(){
      			if(cursor==count)
      				return false;
      			return true;
      		}
      		remove(){
      			count--;count=3;
      		}
    传统方式下用Collections工具类提供的synchronizedCollection方法来获得同步集合
    • 比如Collections.synchronizedMap(new HashMap()),分析该方法的实现源码,可以到使用了代理类返回了一个同步集合,在该代理类中对被代理集合的方法都加上了synchronized关键字实现方法间的互斥
    • 面试题---HashSet和HashMap的关系:HashSet的内部实现使用HashMap,只使用了HashMap的key部分,value部分不考虑(随便填写但是不使用),因为key不能重复,所以是一个Set
    Java5中提供了如下一些同步集合类(通过看java.util.concurrent包下的介绍可以知道有哪些并发集合):
    • ConcurrentHashMap
    • ConcurrentSkipListMap/Set:可以排序的同步集合,需要指定比较器
    • CopyOnWriteArrayList
    • CopyOnWriteArraySet
    传统方式下的Collection在迭代集合时,不允许对集合进行修改。
    • 用空中网面试的同步级线程题进行演示(见下)
    • 根据AbstractList的checkForComodification方法的源码,分析产生ConcurrentModificationException异常的原因。
    使用同步集合实现迭代时对集合进行修改:
    package cn.itcast.heima;
    import java.util.ArrayList;
    import java.util.Collection;
    import java.util.Iterator;
    import java.util.concurrent.CopyOnWriteArrayList;
    public class CollectionModifyExceptionTest {
    	public static void main(String[] args) {
    		Collection users = new CopyOnWriteArrayList();//在写的时候有一分拷贝
    		users.add(new User("张三",28));	
    		users.add(new User("李四",25));			
    		users.add(new User("王五",31));	
    		Iterator itrUsers = users.iterator();
    		while(itrUsers.hasNext()){
    			System.out.println("aaaa");
    			User user = (User)itrUsers.next();
    			if("李四".equals(user.getName())){
    				users.remove(user);
    				//itrUsers.remove();
    			} else {
    				System.out.println(user);				
    			}
    		}
    	}
    }	 
    

    空中网挑选实习生的面试题

    第一题:

    现有的程序代码模拟产生了16个日志对象,并且需要运行16秒才能打印完这些日志,请在程序中增加4个线程去调用parseLog()方法来分头打印这16个日志对象,程序只需要运行4秒即可打印完这些日志对象。原始代码如下:
    package cn.itcast.heima;
    public class Test {
    	public static void main(String[] args){
    		System.out.println("begin:"+(System.currentTimeMillis()/1000));
    		/*模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。
    			修改程序代码,开四个线程让这16个对象在4秒钟打完。
    		 */
    		for(int i=0;i<16;i++){  //这行代码不能改动
    			final String log = ""+(i+1);//这行代码不能改动
    			{
    				Test.parseLog(log);
    			}
    		}
    	}
    
    	//parseLog方法内部的代码不能改动
    	public static void parseLog(String log){
    		System.out.println(log+":"+(System.currentTimeMillis()/1000));
    		try {
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}		
    	}
    }
    分析:线程如何得到产生的数据?将数据存放在队列中,然后从队列中取
    步骤:创建四个线程,将产生的数据存入集合,线程在集合中取数据
    package cn.itcast.heima;
    
    import java.util.concurrent.ArrayBlockingQueue;
    import java.util.concurrent.BlockingQueue;
    
    public class Test {
    	public static void main(String[] args){
            final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(1);//1个数据也可以,
            //final BlockingQueue<String> queue = new ArrayBlockingQueue<String>(16);//可以装16个数据
            //创建四个线程
    		for(int i=0;i<4;i++){
    			new Thread(new Runnable(){
    				@Override
    				public void run() {
    					while(true){
    						try {
    							String log = queue.take();//从阻塞队列中拿数据
    							parseLog(log);
    						} catch (InterruptedException e) {
    							e.printStackTrace();
    						}
    					}
    				}
    			}).start();
    		}
    		
    		System.out.println("begin:"+(System.currentTimeMillis()/1000));
    		/*模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。
    		修改程序代码,开四个线程让这16个对象在4秒钟打完。
    		*/
    		for(int i=0;i<16;i++){  //这行代码不能改动
    			final String log = ""+(i+1);//这行代码不能改动
    			{
    					try {
    						queue.put(log);//将数据放入队列中
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    			}
    		}
    	}
    	
    	//parseLog方法内部的代码不能改动
    	public static void parseLog(String log){
    		System.out.println(log+":"+(System.currentTimeMillis()/1000));
    		
    		try {
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}		
    	}
    }
    考察了阻塞队列的使用

    第二题:

    现成程序中的Test类中的代码在不断地产生数据,然后交给TestDo.doSome()方法去处理,就好像生产者在不断地产生数据,消费者在不断消费数据。请将程序改造成有10个线程来消费生成者产生的数据,这些消费者都调用TestDo.doSome()方法去进行处理,故每个消费者都需要一秒才能处理完,程序应保证这些消费者线程依次有序地消费数据,只有上一个消费者消费完后,下一个消费者才能消费数据,下一个消费者是谁都可以,但要保证这些消费者线程拿到的数据是有顺序的。原始代码如下:
    package queue;
    public class Test {
    	public static void main(String[] args) {
    		System.out.println("begin:"+(System.currentTimeMillis()/1000));
    		for(int i=0;i<10;i++){  //这行不能改动
    			String input = i+"";  //这行不能改动
    			String output = TestDo.doSome(input);
    			System.out.println(Thread.currentThread().getName()+ ":" + output);
    		}
    	}
    }
    
    //不能改动此TestDo类
    class TestDo {
    	public static String doSome(String input){
    		try {
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		String output = input + ":"+ (System.currentTimeMillis() / 1000);
    		return output;
    	}
    }
    分析:
    线程如何得到产生的数据?通过队列
    这里使用同步队列SynchronousQueue,它是一个阻塞队列,每一个插入操作对应一个取出操作,只有线程一旦来取了,才会插入数据。所以它不指定大小
    使用lock或者semaphore实现线程互斥,一个一个执行
    package queue;
    
    import java.util.concurrent.Semaphore;
    import java.util.concurrent.SynchronousQueue;
    
    public class Test {
    	public static void main(String[] args) {
    		final Semaphore semaphore = new Semaphore(1);
    		final SynchronousQueue<String> queue = new SynchronousQueue<String>();
    		for(int i=0;i<10;i++){
    			new Thread(new Runnable(){
    				public void run() {	
    					try {
    						semaphore.acquire();//当前线程获取一个信号灯,保证线程一个一个取,而不是10个线程同时取数据(这里用锁也可以)
    						String input = queue.take();
    						String output = TestDo.doSome(input);
    						System.out.println(Thread.currentThread().getName()+ ":" + output);
    						semaphore.release();
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}	
    				}
    			}).start();
    		}
    		
    		System.out.println("begin:"+(System.currentTimeMillis()/1000));
    		for(int i=0;i<10;i++){  //这行不能改动
    			String input = i+"";  //这行不能改动
    			try {
    				queue.put(input);//将数据放在队列中
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    
    //不能改动此TestDo类
    class TestDo {
    	public static String doSome(String input){
    		try {
    			Thread.sleep(1000);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		String output = input + ":"+ (System.currentTimeMillis() / 1000);
    		return output;
    	}
    }
    

    第三题:

    现有程序同时启动了4个线程去调用TestDo.doSome(key, value)方法,由于TestDo.doSome(key, value)方法内的代码是先暂停1秒,然后再输出以秒为单位的当前时间值,所以,会打印出4个相同的时间值,如下所示:
    4:4:1258199615
    1:1:1258199615
    3:3:1258199615
    1:2:1258199615
    请修改代码,如果有几个线程调用TestDo.doSome(key, value)方法时,传递进去的key相等(equals比较为true),则这几个线程应互斥排队输出结果,即当有两个线程的key都是"1"时,它们中的一个要比另外其他线程晚1秒输出结果,如下所示:
    4:4:1258199615
    1:1:1258199615
    3:3:1258199615
    1:2:1258199616
    总之,当每个线程中指定的key相等时,这些相等key的线程应每隔一秒依次输出时间值(要用互斥),如果key不同,则并行执行(相互之间不互斥)。原始代码如下:
    	package syn;
    
    	//不能改动此Test类	
    	public class Test extends Thread{
    		
    		private TestDo testDo;
    		private String key;
    		private String value;
    		
    		public Test(String key,String key2,String value){
    			this.testDo = TestDo.getInstance();
    			/*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,
    			以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/
    			this.key = key+key2; 
    			this.value = value;
    		}
    
    		public static void main(String[] args) throws InterruptedException{
    			Test a = new Test("1","","1");
    			Test b = new Test("1","","2");
    			Test c = new Test("3","","3");
    			Test d = new Test("4","","4");
    			System.out.println("begin:"+(System.currentTimeMillis()/1000));
    			a.start();
    			b.start();
    			c.start();
    			d.start();
    
    		}
    		
    		public void run(){
    			testDo.doSome(key, value);
    		}
    	}
    
    	class TestDo {
    
    		private TestDo() {}
    		private static TestDo _instance = new TestDo();	
    		public static TestDo getInstance() {
    			return _instance;
    		}
    
    		public void doSome(Object key, String value) {
    	
    			// 以大括号内的是需要局部同步的代码,不能改动!
    			{
    				try {
    					Thread.sleep(1000);
    					System.out.println(key+":"+value + ":"
    							+ (System.currentTimeMillis() / 1000));
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    	}
    分析:
    需要同步,同步的锁就是传入的key,但是锁字符串内容相同,但不一定是同一对象,所以需要进行判断。
    需要用到同步集合。
    package syn;
    
    import java.util.ArrayList;
    import java.util.Iterator;
    import java.util.concurrent.CopyOnWriteArrayList;
    
    //不能改动此Test类	
    public class Test extends Thread{
    	
    	private TestDo testDo;
    	private String key;
    	private String value;
    	
    	public Test(String key,String key2,String value){
    		this.testDo = TestDo.getInstance();
    		/*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,
    		以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/
    		this.key = key+key2; 
    /*		a = "1"+"";
    		b = "1"+"";//这里a和b都是常量相加,编译器会自动优化为同一对象
    */
    		this.value = value;
    	}
    
    	public static void main(String[] args) throws InterruptedException{
    		Test a = new Test("1","","1");
    		Test b = new Test("1","","2");
    		Test c = new Test("3","","3");
    		Test d = new Test("4","","4");
    		System.out.println("begin:"+(System.currentTimeMillis()/1000));
    		a.start();
    		b.start();
    		c.start();
    		d.start();
    
    	}
    	
    	public void run(){
    		testDo.doSome(key, value);
    	}
    }
    
    class TestDo {
    
    	private TestDo() {}
    	private static TestDo _instance = new TestDo();	
    	public static TestDo getInstance() {
    		return _instance;
    	}
    
    //	private ArrayList keys = new ArrayList();//这里不能使用ArrayList集合。因为会发生ConcurrentModificationException的问题,此例中会出现一个线程迭代集合的同时另一个线程在往集合中添加元素。比如此例中a线程进入往集合中添加了一个元素,b线程进入因为元素相同就进行迭代操作,这时c线程也是同时执行的,进入后会执行添加操作。出现该错误必须是这种情况出现,否则程序运行不会报错!!
    	private CopyOnWriteArrayList keys = new CopyOnWriteArrayList();
    	public void doSome(Object key, String value) {
    		Object o = key;
    		if(!keys.contains(o)){
    			keys.add(o);//如果集合中没有该元素(实际上判断有无元素与之相等),就存入该对象
    		}else{
    			//如果有该元素,就直接遍历集合,找到该元素,并用该元素作为同步的锁
    			for(Iterator iter=keys.iterator();iter.hasNext();){
    				try {
    					Thread.sleep(20);//减慢迭代的速度(等着别的线程进行加入元素操作),增加出现并发修改问题的概率
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				Object oo = iter.next();
    				if(oo.equals(o)){
    					o = oo;
    					break;
    				}
    			}
    		}
    		synchronized(o)
    		// 以大括号内的是需要局部同步的代码,不能改动!
    		{
    			try {
    				Thread.sleep(1000);
    				System.out.println(key+":"+value + ":"
    						+ (System.currentTimeMillis()/1000));//这里将毫秒数除以1000,所以看到的时间会一样
    			} catch (InterruptedException e) {
    				e.printStackTrace();
    			}
    		}
    	}
    }
    展开全文
  • 目前从事开发工作也有5年时间了,从刚开始大学毕业来到帝都,开启了自己的开发之旅,呆过的都是互联网公司,一二线的大公司(百度、美团、探探、滴滴等)都有去面试过,也顺利拿到了3个offer,最终我还是选择了百度...
  • 首先,与许多其他框架(比如仅限于Web应用程序的Apache Strut)不同,可以使用Spring构建Java中的任何应用程序(例如,独立的应用程序、Web 应用程序或JEE应用程序)。 其次,该描述中的轻量级-词真正指的并不是类的...
  • Exchanger 可能在应用程序(比如遗传算法和管道设计)中很有用。 用法示例:以下是重点介绍的一个类,该类使用Exchanger 在线程间交换缓冲区,因此,在需要时,填充缓冲区的线程获取一个新腾空的缓冲区,并将填...
  • 面试题总结 —— JAVA高级工程师

    万次阅读 多人点赞 2016-03-03 12:03:31
    近期考虑换工作的问题,于是投简历面试,面试5家公司的高级Java工程师,有4家给了我offer,想着总结一下面试经验,方便最近正在寻求机会的你们 一、无笔试题 不知道是不是职位原因还是没遇到,面试时,都不...
  • 应用场景:共享资源的争夺,例如游戏中选手进入房间的情况。 import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Semaphore; public ...
  • 近期考虑换工作的问题,于是投简历面试,面试5家公司的高级Java工程师,有4家给了我offer,想着总结一下面试经验,方便最近正在寻求机会的你们 关于面试问题的解答,我文末附上了链接,大家自行参考。 一、无笔试...
  • 博主14年毕业后从事Android移动应用开发,至今已是第六年了。在深圳摸爬滚打了好几年,也都是在小厂的圈子里跳来跳去。在我拿到这份新offer钱,15K是我拿过的最高工资。已经有快一年没有涨过工资了。疫情在家隔离了...
  • JavaEE企业级分布式高级架构师 课程招生 全新改版,深度可达阿里 P6+ 多名一线互联网名师倾力指导 (前腾讯、美团、百度) 就业班拿不到 offer 退学费 课程由廖雪峰团队倾情打造,围绕大厂用人标准和市场需求,...
  • 剑指offer典型面试题

    2019-01-08 16:33:41
     考虑异常安全性的解法,高级程序员必备  2.2.2 C#  面试题2:实现Singleton模式  不好的解法一:只适用于单线程  不好的解法二:可用于多线程但效率不高  可行的解法:同步锁前后两次判断  推荐的解法一:...
  • 一个毕业两年的双非本科的小小程序员的冲击历程,拿到了500强的offer。 链接:https://blog.csdn.net/asd1358355022/article/details/119707811 前提背景 ????????当前公司岗位已不足以满足我个人未来规划以及当前...
  • offer选择分析

    千次阅读 2012-07-16 21:57:04
    大纲: 一、缘由、概述 二、创新工场的模式 ...三、职业发展道路的影响因素 ... 前两周,有个师弟咨询我个offer选择——360产品经理offer和创新工场某团队产品经理。他说面试的时候,创新工场某面试官说在360
  • Java 工程师如何得到一个好 Offer

    万次阅读 多人点赞 2017-12-21 00:00:00
    本文来自作者 张振华 在 GitChat 上分享 「Java 工程师如何得到一个好 Offer」,「阅读原文」查看交流实录。「文末高能」编辑 | 哈比大多数情况下,通过跳槽,可以实现自我价值的升值。 作者从业 10 几年,经历...
  • 答案,助你拿到心仪 Offer 更多:作者提 Bug 标签:   一份 Android 面试题集,适应于实习 & 初级工程师 & 中级工程师,高级工程师勉强吧。笔者不提供答案,但是会提供学习链...
  • 源码精品专栏 原创 | Java 2020 超神之路,很肝~ 中文详细注释的开源项目 RPC 框架 Dubbo 源码解析 网络应用框架 Netty 源码解析 消息中间件 RocketMQ 源码解析 数据库中间件 Sharding-JDBC 和 MyCAT 源码解析 ...
  • 倒排索引与正排索引的区别 ElasticSearch的原理(倒排索引+TF/IDF) Zookeeper用过吗,介绍一下 Zookeeper一般用在什么场景 除了ZAB协议,在介绍几个分布式一致性协议(Paxos、Raft) 线程池的几种拒绝策略及其应用场景 ...
  • 考虑异常安全性的解法,高级程序员必备 26 2.2.2 C# 27 面试题2:实现Singleton模式 31 不好的解法一:只适用于单线程 31 不好的解法二:可用于多线程但效率不高 32 可行的解法:同步锁前后两次判断 33 推荐的解法一...
  • 有时就一书--剑指offer

    2017-05-15 19:53:36
    不经意间已经把4月份买来的《剑指Offer》看到八十多页啦,啦啦啦,1/3啦~其实同步在看的还有《http权威指南》这些的,但是想着说有空的话就归纳一下,但是我连《JavaScript高级程序设计》都...还没有更完第二章?(我...
  • 分享一篇面经,作者拿到了 PingCAP,今日头条的 offer 以及蚂蚁金服的口头 offer。下面是该作者的经验分享: 准备过程 我自己是本科毕业后在老东家干了两年多,老东家算是一家”小公司”(毕竟这年头没有 BAT 或 ...
  • Spring Boot 是由 Pivotal 团队提供的一个全新框架,其设计目的是用来简化 Spring 应用的初始搭建以及开发过程。该框架使用了特定的方式来进行配置,从而使开发人员不再需要定义样板化的配置。说句人话就是,Spring...
  • 剑指Offer——知识点储备-J2EE基础

    万次阅读 多人点赞 2016-10-27 09:00:36
    剑指Offer——知识点储备-J2EE基础9.2 jdk 1.8的新特性(核心是Lambda 表达式)参考链接:http://www.bubuko.com/infodetail-690646.html (1)接口的默认方法 (给接口添加一个非抽象的方法实现,只需default关键字...
  • 剑指Offer——求职必备神器

    万次阅读 2016-09-26 17:41:58
    求职必备神器前言  不管是公司网申、银行招聘、面试等等,“谈谈你的职业规划”、“以往工作中遇到了哪些棘手问题?...  进入公司一年之内,我会努力钻研Java相关技术,学习并应用主流技术架构,从一
  • 虽然算法与数据结构、编程语言很有深度,但当时认为真正厉害的是能干项目,写网站的,懂框架写实际应用程序。 那时候觉得语言、算法,现学会用就行。那些编译原理、操作系统课程、数学、英语,听课就行,自以为企业...
  • 剑指Offer--排序算法小结

    万次阅读 多人点赞 2016-07-29 08:31:47
    Offer来了(Java版)——排序算法小结前言 毕业季转眼即到,工作成为毕业季的头等大事,必须得认认真真进行知识储备,迎战笔试、电面、面试。 许久未接触排序算法了。平时偶尔接触到时自己会不假思索的百度,然后就是...
  • 2015年9月20号下午,接到腾讯总部的电话,确定了offer相关信息,算是正式get了鹅场的offer,坐等下个周一周二的签约会。 心路篇  2015年2月:已经2月份了,自己在大学的时光已经来到了比较关键的阶段性,大学的很多...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 18,764
精华内容 7,505
关键字:

offer高级应用