java面试总结(多线程)

  1.什么是线程池?

        线程池是一种多线程处理形式,处理过程中将任务提交到线程池,任务的执行交由线程池来管理。

        如果每个请求都创建一个线程去处理,那么服务器的资源很快就会被耗尽,使用线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

        如果用生活中的列子来说明,我们可以把线程池当做一个客服团队,如果同时有1000个人打电话进行咨询,按照正常的逻辑那就是需要1000个客服接听电话,服务客户。现实往往需要考虑到很多层面的东西,比如:资源够不够,招这么多人需要费用比较多。正常的做法就是招100个人成立一个客服中心,当有电话进来后分配没有接听的客服进行服务,如果超出了100个人同时咨询的话,提示客户等待,稍后处理,等有客服空出来就可以继续服务下一个客户,这样才能达到一个资源的合理利用,实现效益的最大化。

         execute 和 submit的区别?

         execute实用于不需要关注返回值的场景,只需要将线程丢到线程池中去执行就可以了,而submit方法实用于需要关注返回值的场景。

         五种线程池的使用场景  

  • newSingleThreadExecutor:一个单线程的线程池,可以用于需要保证顺序执行的场景,并且只有一个线程在执行。

  • newFixedThreadPool:一个固定大小的线程池,可以用于已知并发压力的情况下,对线程数做限制。

  • newCachedThreadPool:一个可以无限扩大的线程池,比较适合处理执行时间比较小的任务。

  • newScheduledThreadPool:可以延时启动,定时启动的线程池,适用于需要多个后台线程执行周期任务的场景。

  • newWorkStealingPool:一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用cpu数量的线程来并行执行。

        线程池的关闭

       关闭线程池可以调用shutdownNow和shutdown两个方法来实现

       shutdownNow:对正在执行的任务全部发出interrupt(),停止执行,对还未开开始执行的任务全部取消,并且返回还没开始的任务列表

       shutdown:当我们使用shutdown后,线程池将不再接受新的任务,但也不会强制终止已经提交或者正在执行的任务

     2.实现多线程有哪几种方式?

        1)实现Runnable接口,并且实现该接口的run()方法。

         自定义类并实现Runnable接口,实现run()方法

         创建Thread对象,用实现Runnable接口的对象作为参数实例化该Thread对象

         调用Thread的start()方法

         

class MyThread implement Runnable{

        public void run(){//重写run方法

                    System.out.println("Thread body");

        }

}

 

public class Test{

        public static void main(String[] args){

              MyThread thread = new MyThread();

              Thread t = new Thread(thread);

              t.start();//开启线程

        }

}

        2)继承Thread类,重写run方法

         继承Thread,重写run()方法

         创建一个MyThread实例

         执行start()方法

         

class MyTread extends Thread{

      public void run(){

            System.out.println("Thread body");

      }

}

 

public class Test{

      public static void main(String[] args){

            MyThread thread = new MyThread();

            thread.start();

      }

}

    3)实现Callable接口,重写call()方法

      Callable对象实际是属于Executor框架中的功能类,Callable接口与Runnable接口类似,但是提供了比Runnable更强大的功能,主要表现以下三点:

      Callable可以在任务结束后提供一个返回值,Runnable无法提供这个功能。

      Callable中的call()方法可以抛出异常,而Runnable的run()方法不能抛出异常。

      运行Callable可以拿到一个Future对象,Future对象表示异步计算的结果。它提供了检查计算是否完成的方法。由于线程属于异步计算模型,所以无法从其他线程中得到方法的返回值,在这种情况下,就可以使用Futrue来监视目标线程调用call()方法的情况,当调用Futrue的get()方法以获取结果时,当前线程就会阻塞,直到call()方法结束返回结果。

       总结:在以上三种方式中,前两种方式线程执行完之后都没有返回值,只有最后一种是带返回值的。但需要实现多线程时,一般推荐实现Runnable接口的方式,原因如下:首先,Thread类定义了多种方法可以被派生类使用或重新,但是只有run()方法是必须被重写的,在run方法中实现这个线程的主要功能。这当然是实现Runnable接口所需的同样的方法。而且,很多java开发人员认为,一个类仅在他们需要被加强或修改时才会被继承。因此,如果没有必要重写Thread类中的其他方法,那么通过继承Thread的实现方式与实现Runnable接口的效果相同,在这种情况下最好通过实现Runnable接口方式来创建线程。

3. sleep() 和 wait() 的区别

       sleep() 方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。
      因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象。

      wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程

       综上所述:

       sleep() 和 wait() 的区别就是调用sleep方法的线程不会释放对象锁,而调用wait()方法会释放对象锁

   4.要想使i++线程安全,应该怎么做(不仅仅限于synchronized修饰)

      volatile解决了线程间共享变量的可见性问题

      使用volatile会增加性能开销

      volatile并不能解决线程同步问题

      解决i++或者++i这样的线程同步问题需要使用synchronized或者AtomicXX系列的包装类,同时也会增加性能开销。

5.单例模式下的多线程会有什么后果?

    (1)单例模式的懒汉式[线程不安全,不可用]

        public class Singleton {  
      
        private static Singleton instance=null;  
          
        private Singleton() {};  
          
        public static Singleton getInstance(){  
              
            if(instance==null){  
                instance=new Singleton();  
            }  
            return instance;  
        }  
    }   

      这种方式是在调用getInstance方法的时候才创建对象的,所以它就被称为懒汉模式。

      这是存在线程安全问题的,那具体是存在怎样的线程安全问题?怎样导致这种问题的?好,我们来说一下什么情况下这种写法会有问题。在运行过程中可能存在这么一种情况:多个线程去调用getInstance方法来获取Singleton的实例,那么就有可能发生这样一种情况,当第一个线程在执行if(instance==null)时,此时instance是为null的进入语句。在还没有执行instance=new Singleton()时(此时instance是为null的)第二个线程也进入了if(instance==null)这个语句,因为之前进入这个语句的线程中还没有执行instance=new Singleton(),所以它会执行instance = new Singleton()来实例化Singleton对象,因为第二个线程也进入了if语句所以它会实例化Singleton对象。这样就导致了实例化了两个Singleton对象。所以单例模式的懒汉式是存在线程安全的,既然它存在问题,那么可能有解决办法,于是就有下面加锁这种写法。

(2)懒汉式线程安全的[线程安全,效率低不推荐使用]

   public class Singleton {  
  
    private static Singleton instance=null;  
      
    private Singleton() {};  
      
    public static synchronized Singleton getInstance(){  
          
        if(instance==null){  
            instance=new Singleton();  
        }  
        return instance;  
    }  

缺点:效率太低了,每个线程在想获得类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面想获得该实例,直接return就行了。方法进行同步效率太低要改进。

(3)单例模式懒汉式[线程不安全,不可用]

 public class Singleton7 {  
  
    private static Singleton instance=null;  
      
    public static Singleton getInstance() {  
        if (instance == null) {  
            synchronized (Singleton.class) {  
                instance = new Singleton();  
            }  
        }  
        return instance;  
    }  
}

这种写法也是不安全的,当一个线程还没有实例化Singleton时另一个线程执行到if(instance == null)这个判断时语句机会进入if语句,虽然加了锁,但是等到第一个线程执行完instance=new Singleton()跳出这个锁时,另一个进入if语句的线程同样会实例化另外一个SIngleton对象。因为这种改进方法不可行。 

6.servlet是线程安全吗?

   servlet不是线程安全的。

   要解释为什么Servlet不是线程安全的,需要了解一下Servlet容器(即Tomcat)如何响应HTTP请求的。

   当Tomcat接受到Client的HTTP请求时,Tomcat从线程池取出一个线程,之后找到该请求对应的Servlet对象并且进行初始化,之后调用service()方法。要注意的是每一个Servlet对象再Tomcat容器中只有一个实力对象,即是单例模式。如果多个HTTP请求的是同一个Servlet,那么这两个HTTP请求对应的线程将并发调用Servlet的service()方法。

    如果Thread1和Thread2调用了同一个Servlet1,此时如果Servlet1中定义了实例变量或静态变量,那么可能会发生线程安全问题(因为所有的线程都可能使用这些变量)。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值