Java面试题1_多线程并发


 

1.

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

package read;
    
    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();
            }        
        }
        
    }

解决方式一:采用线程池
     

public class Interview1 {
    
    public static void main(String[] args){
        
        System.out.println("begin:"+(System.currentTimeMillis()/1000));
        /*模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。
        修改程序代码,开四个线程让这16个对象在4秒钟打完。
        */
        ExecutorService threadPool=Executors.newFixedThreadPool(4);
        
            for(int i=0;i<16;i++){  //这行代码不能改动
                final String log = ""+(i+1);//这行代码不能改动
                {    
                      threadPool.execute(new Runnable(){//提交16个任务给线程池
                         public void run(){
                          Interview1.parseLog(log);
                         }
                      });
                         
                }
              
            
            }
           threadPool.shutdown();
        
    }
    
    //parseLog方法内部的代码不能改动
    public static void parseLog(String log){
        //为了输出明确添加线程名称
        System.out.println(Thread.currentThread().getName()+"..."+log+":"+(System.currentTimeMillis()/1000));
        
      try {
            Thread.sleep(1000);//可以增加每个线程执行Interview1.parseLog(log)的概率
                               //不加可能该线程很快执行完空闲,又去执行下一个任务,其它线程机会较少
        } catch (InterruptedException e) {
            e.printStackTrace();
        }    
    }
    
}

多线程并发面试题1

解决方式二:采用阻塞队列:

class Interview1_2 {
    
    public static void main(String[] args){
        
        System.out.println("begin:"+(System.currentTimeMillis()/1000));
        /*模拟处理16行日志,下面的代码产生了16个日志对象,当前代码需要运行16秒才能打印完这些日志。
        修改程序代码,开四个线程让这16个对象在4秒钟打完。
        */
        final ArrayBlockingQueue<String> blockQueue=new ArrayBlockingQueue<String> (1);//可以是任意值,下面有循环控制最好1~16
        for(int i=0;i<4;++i)
            new Thread(new Runnable(){

                @Override
                public void run() {
                   while(true){
                           try {
                            Interview1_2.parseLog(blockQueue.take());
                        } catch (InterruptedException e) {
                            // TODO 自动生成的 catch 块
                            e.printStackTrace();
                        }
                   }
                }
                
                
            }).start();
        
            for(int i=0;i<16;i++){  //这行代码不能改动
                final String log = ""+(i+1);//这行代码不能改动
                {    
                    
                     try {
                        blockQueue.put(log);
                    } catch (InterruptedException e) {
                        // TODO 自动生成的 catch 块
                        e.printStackTrace();
                    }    
                    //Interview1_2.parseLog(log);
                    
                         
                }
              
            
            }
    
        
    }
    
    //parseLog方法内部的代码不能改动
    public static void parseLog(String log){
        //为了输出明确添加线程名称
        System.out.println(Thread.currentThread().getName()+"..."+log+":"+(System.currentTimeMillis()/1000));
        
        try {
            Thread.sleep(1000);//采用阻塞队列,这里如果不让每个线程sleep下,每个线程可能多次take
                               
        } catch (InterruptedException e) {
            e.printStackTrace();
        }    
    }
    
}

多线程并发面试题1

2.    

第二题:现成程序中的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;
        }
    }

为了控制取出的顺序,依然可以采用阻塞队列,每个消费者都需要一秒才能处理完:采用同步

package com.itheima.thread.current.interview;

import java.util.concurrent.ArrayBlockingQueue;

    public class Interview2 {
        private static ArrayBlockingQueue<String>  blockQueue=
                                      new ArrayBlockingQueue<String> (10);
        public static void main(String[] args) {
            
            System.out.println("begin:"+(System.currentTimeMillis()/1000));
           for(int i=0;i<10;i++){  //这行不能改动
                String input = i+"";  //这行不能改改动
                try {
                    blockQueue.put(input);
                } catch (InterruptedException e1) {
                    // TODO 自动生成的 catch 块
                    e1.printStackTrace();
                }
                
                
                System.out.println(blockQueue);//可以看到put过程,运行结果主线程,一直在打印,这是因为子线程中doSome的sleep导致
                 
                
                
                new Thread(new Runnable(){
                       @Override
                       public void run(){
                                String output=null;
                               synchronized(Interview2.class){
                                    try {
                                        output = TestDo.doSome(blockQueue.take());
                                    } catch (InterruptedException e) {
                                        // TODO 自动生成的 catch 块
                                        e.printStackTrace();
                                    }
                                      System.out.println(Thread.currentThread().getName()+ ":" + output);
                                     
                                   }
                          }
                       }).start();
             }
          }
    }
    
    //不能改动此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:一种阻塞队列,其中每个插入操作必须等待另一个线程的对应移除操作 ,反之亦然。
 */

Interview2

3.

     第三题:现有程序同时启动了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.equals(anotherKey)==true互斥,不满足的不互斥

思想:将doSome中的代码放到同步代码块中,通过改变锁来控制线程间是否同步

在解决这个问题前,先看本题涉及的其它问题:

代码中:

/*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象, 以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/

this.key = key+key2;

this.value = value;

导致a和b的key不是同一个对象,如果没有这个直接是this.key=key;那么直接将doSome中的代码放到synchronized(key)中就可以完成需求.

为什么不是同一个对象?看一个小例子:

package com.itheima.thread.current.interview;

public class StringTest {

    /**
     * @param args
     */
    public static void main(String[] args) {
        // TODO 自动生成的方法存根
        String str1="1"+"a";
        String str2="1"+"a";
        System.out.println(str1==str2);//true
        compare(str1,str2);
    }
    public static void compare(String str1,String str2){
        String k1=str1+str2;
        String k2=str1+str2;
        System.out.println(k1==k2);//false
    }

}

反编译:

public com.itheima.thread.current.interview.StringTest();
Code:
   0: aload_0       
   1: invokespecial #8                  // Method java/lang/Object."<init>":()V
   4: return        

public static void main(java.lang.String[]);
Code:
   0: ldc           #16                 // String 1a
   2: astore_1      
   3: ldc           #16                 // String 1a
   5: astore_2      
   6: getstatic     #18                 // Field java/lang/System.out:Ljava/io/PrintStream;
   9: aload_1       
  10: aload_2       
  11: if_acmpne     18
  14: iconst_1      
  15: goto          19
  18: iconst_0      
  19: invokevirtual #24                 // Method java/io/PrintStream.println:(Z)V
  22: aload_1       
  23: aload_2       
  24: invokestatic  #30                 // Method compare:(Ljava/lang/String;Ljava/lang/String;)
V
  27: return        

public static void compare(java.lang.String, java.lang.String);
Code:
   
   //采用StringBuilder完成+动作,然后StringBuilder.toString()会new一个新的String对象
   0: new           #43                 // class java/lang/StringBuilder
   3: dup           
   4: aload_0       
   5: invokestatic  #45                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)L
java/lang/String;
   8: invokespecial #49                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/S
tring;)V
  11: aload_1       
  12: invokevirtual #52                 // Method java/lang/StringBuilder.append:(Ljava/lang/Str
ing;)Ljava/lang/StringBuilder;
  15: invokevirtual #56                 // Method java/lang/StringBuilder.toString:()Ljava/lang/
String;
  18: astore_2      
  
  
  19: new           #43                 // class java/lang/StringBuilder
  22: dup           
  23: aload_0       
  24: invokestatic  #45                 // Method java/lang/String.valueOf:(Ljava/lang/Object;)L
java/lang/String;
  27: invokespecial #49                 // Method java/lang/StringBuilder."<init>":(Ljava/lang/S
tring;)V
  30: aload_1       
  31: invokevirtual #52                 // Method java/lang/StringBuilder.append:(Ljava/lang/Str
ing;)Ljava/lang/StringBuilder;
  34: invokevirtual #56                 // Method java/lang/StringBuilder.toString:()Ljava/lang/
String;
  37: astore_3      
  38: getstatic     #18                 // Field java/lang/System.out:Ljava/io/PrintStream;
  41: aload_2       
  42: aload_3       
  43: if_acmpne     50
  46: iconst_1      
  47: goto          51
  50: iconst_0      
  51: invokevirtual #24                 // Method java/io/PrintStream.println:(Z)V
  54: return        
}

再来解决上面的问题:

public class Interview3 extends Thread{
        
        private TestDo2 testDo;
        private String key;
        private String value;
        
        public Interview3(String key,String key2,String value){
            this.testDo = TestDo2.getInstance();
            /*常量"1"和"1"是同一个对象,下面这行代码就是要用"1"+""的方式产生新的对象,
            以实现内容没有改变,仍然相等(都还为"1"),但对象却不再是同一个的效果*/
            this.key = key+key2; 
            this.value = value;
        }


        public static void main(String[] args) throws InterruptedException{
            Interview3 a = new Interview3("1","","1");
            Interview3 b = new Interview3("1","","2");
            Interview3 c = new Interview3("3","","3");
            Interview3 d = new Interview3("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 TestDo2 {

        private TestDo2() {}
        private ArrayList al=new ArrayList ();//定义一个集合
         //private CopyOnWriteArrayList al=new CopyOnWriteArrayList();
        private static TestDo2 _instance = new TestDo2();
        
        public static TestDo2 getInstance() {
            return _instance;
        }

        public void doSome(Object key, String value) {
         
            Object obj=key;
           synchronized(TestDo2.class){//这里必须加同步,即使用一个同步的集合(CopyOnArrayList或Collections返回的等)
                                   //可以使用同步的集合,然后在add前使用sleep来测试,每个线程可能均执行不到else
               if(!al.contains(key)){  //同步集合只能保证并发修改数据不会出错(add,remove...)其实也就是只在add上加了同步
                    al.add(key);       //如果不加同步,很可能所有线程都执行al.add(key);
               
               }
                else
                  //obj=al.get(al.indexOf(key));//index内部使用equals进行查找
                
                  //一般使用迭代器:
                   for(Iterator it=al.iterator();it.hasNext();){//如果不加同步,可能出现一个线程add,切换到另一个线程it.next
                       Object getObj=it.next();                 //并发修改异常,同步的集合可以避免这个异常,保证在add时,
                                                            //其它线程不能it.next
                           if(getObj.equals(obj))               
                              obj=getObj;
                   }
                  
           }
            // 以大括号内的是需要局部同步的代码,不能改动!
            synchronized(obj)
            {
                try {
                    Thread.sleep(1000);
                    System.out.println(key+":"+value + ":"
                            + (System.currentTimeMillis()/1000 ));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }

    }

interview3

posted @ 2013-08-07 19:08  伊秋  阅读(476)  评论(0编辑  收藏  举报