2020-多线程面试题整理

  1. 队列 
    主要遵循先进先出、后进后出的原则
  2. 进程与线程的区别?
    :进程是所有线程的集合,每一个线程是进程中的一条执行路径,线程只是一条执行路径。 
  3. 为什么要用多线程?
    :提高程序效率
  4. 多线程的3大特性 
     1. 原子性
          一个、多个操作 要么全部执行且执行的过程不会被任何因素打断,要么就都不执行。
      2. 可见性
          多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
      3. 有序性
          在并发时,程序的执行可能会出现乱序。给人的直观感觉就是:写在前面的代码,会在后面执行。有序性问题的原因是       因为程序在执行时,可能会进行指令重排,重排后的指令与原指令的顺序未必一致
  5. 多线程创建方式?
    :继承Thread或实现Runnable 接口
  6. 是继承Thread类好还是实现Runnable接口好?
     :Runnable接口好,因为实现了接口还可以继续继承。继承Thread类不能再继承。
  7. 你在哪里用到了多线程?
     :主要能体现到多线程提高程序效率。  举例:分批发送短信
  8. 什么是多线程安全?
    答:当多个线程同时共享,同一个全局变量或静态变量,做写的操作时,可能会发生数据冲突问题,也就是线程安全问题。做读操作是不会发生数据冲突问题。
  9. 如何解决多线程之间线程安全问题?
     答:使用多线程之间同步或使用锁(lock)。
  10. 为什么使用线程同步或使用锁能解决线程安全问题呢?
    答:将可能会发生数据冲突问题(线程不安全问题),只能让当前一个线程进行执行。被包裹的代码执行完成后释放锁,让后才能让其他线程进行执行。这样的话就可以解决线程不安全问题。
  11. 什么是多线程之间同步?
    :当多个线程共享同一个资源,不会受到其他线程的干扰。 
  12. 什么是同步代码块?
    :就是将可能会发生线程安全问题的代码,给包括起来。只能让当前一个线程进行执行,被包裹的代码执行完成之后才能释放所,让后才能让其他线程进行执行。
  13. 多线程同步的分类?
     使用同步代码块
     
    private Object mutex = new Object();// 自定义多线程同步锁
    	public void sale() {
    		synchronized (mutex) {
    			if (trainCount > 0) {
    try {
    					Thread.sleep(10);
    				} catch (Exception e) {
    				}
    				System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
    				trainCount--;			}
    		}
    

     使用同步函数,在方法上修饰synchronized 称为同步函数
     
    public synchronized void sale() {
    			if (trainCount > 0) { 
    try {
    					Thread.sleep(40);
    				} catch (Exception e) {
    				}
    				System.out.println(Thread.currentThread().getName() + ",出售 第" + (100 - trainCount + 1) + "张票.");
    				trainCount--;
    			}
    	}
    
    静态同步函数
           方法上加上static关键字,使用synchronized 关键字修饰 为静态同步函数 。静态的同步函数使用的锁是  该函数所属字节码文件对象 
         
  14. 同步代码块与同步函数区别?
    答:同步代码使用自定锁(明锁)

           同步函数使用this锁

  15. 同步函数与静态同步函数区别?   例如: 一个静态方法和一个非静态静态怎么实现同步?
    同步函数使用this锁
    静态同步函数使用字节码文件,也就是类.class
  16. 什么是多线程死锁?
    答:  同步中嵌套同步,无法释放锁的资源 
           解决办法:同步中尽量不要嵌套同步
  17. Wait()与Notify ()区别?
    Wait让当前线程有运行状态变为等待状态,和同步一起使用

    Notify 唤醒现在正在等待的状态,和同步一起使用

  18. Wait()与sleep()区别?
    sleep()不会释放锁的资源、wait()会释放锁的资源
    sleep()是时间到了就会被唤醒
    Wati()可以用notify()唤醒
  19. Lock与Synchronized区别?

    Lock锁属于手动控制                      灵活性好
    Synchronized同步锁属于自动控制  不利于扩展

  20. Condition用法
    Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能
     Lock lock = new ReentrantLock();  
     Condition condition = lock.newCondition();
    
     lock.lock();  
     condition.await();   // 类似wait
     lock.unlock();   
    
     lock.lock(); 
     condition.signalAll();   // 类似notifyAll
     lock.unlock();  

     

  21. 如何停止线程?
    使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
    使用stop方法强行终止线程(这个方法不推荐使用,因为stop和suspend、resume一样,也可能发生不可预料的结果)。
    使用interrupt方法中断线程。 线程在阻塞状态
  22. 什么是守护线程
    Java中有两种线程,一种是用户线程,另一种是守护线程。
    当进程不存在或主线程停止,守护线程也会被停止。
    使用setDaemon(true)方法设置为守护线程
  23. join()方法作用
    join作用是让其他线程变为等待,只有当前线程执行完毕后,等待的线程才会被释放。  
    如: A线程调用了B线程的join()方法时,表示:A等待B方法先执行
  24.  有T1、T2、T3三个线程,你怎样保证T2在T1执行完后执行,T3在T2执行完后执行  
    public class Test {
     
        public static void main(String[] args) throws Exception{
           Thread t1 = new Thread(new Runnable() {
               @Override
               public void run() {
                   for( int i =0 ; i<10; i++) {
                       System.out.println(Thread.currentThread().getName()+"打印的值--------"+i);
                   }
               }
           });
     
            Thread t2 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        t1.join();   //  表示t2线程会等待t1线程先执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for( int i =0 ; i<10; i++) {
                        System.out.println(Thread.currentThread().getName()+"打印的值--------"+i);
                    }
                }
            });
     
            Thread t3 = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        t2.join();  //  表示t3线程会等待t2线程先执行
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    for( int i =0 ; i<10; i++) {
                        System.out.println(Thread.currentThread().getName()+"打印的值--------"+i);
                    }
                }
            });
     
     
           t1.start();
           t2.start();
           t3.start();
     
        }
    }
  25. 线程生命周期

    线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。

    新建状态:调用Thread t1 = new Thread(new Thread01());
    就绪状态:调用t1.start();方法  的等待cpu执行资源
    运行状态:抢到cpu执行权,执行run()方法体
    阻塞状态:调用wait()、sleep()方法。
    死亡状态:方法执行完毕后

  26. 线程三大特性
    多线程有三大特性,原子性、可见性、有序性
    原子性:保证数据一致性,线程安全。
    可见性:对另一个线程是否课件
    有序性:线程之间执行有顺序 
  27. 说说Java内存模型
    简称(JMM), JMM决定了一个线程对共享变量写入的时候,是否对另一个线程可见。
    JMM定义了线程和主内存之间的抽象关系,线程之间的共享变量存储在主内存,
    每个线程都有一个私有内存,私有内存存的是共享变量的副本。

      
     上图:线程程A与线程B之间如要通信的话,必须要经历下面2个步骤:
    a、首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
    b、然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
  28. 使用volatile关键字
     能够解决A线程修改的共享变量(刷新主内存)对另一个线程可见
    
    class Thread01 implements  Runnable{
     
        public volatile boolean flag = true;  // volatile可以将子线程修改的共享变量即使刷新主内存
        @Override
        public void run() {
            System.out.println("子线程开始......"+flag);
            while (flag){
            }
            System.out.println("子线程结束....."+flag);
        }
     
        public void setFlag(boolean flag){
            this.flag = flag;
        }
    }
    public class Test {
     
        public static void main(String[] args) throws Exception {
            Thread01 th = new Thread01();
            Thread t1 = new Thread(th);
            t1.start();
            Thread.sleep(3000);
            th.setFlag(false);
            System.out.println("主线程获取flag---"+th.flag);
        }
    }

     

  29. volatile和synchronized的区别
    volatile能保证线程的可见性,不能保证原子性
    synchronized能保证线程的可见性,也能保证原子性
    volatile的性能优于synchronized
  30. AtomicInteger原子类
    public class Sample2 {
    
        private static AtomicInteger count = new AtomicInteger(0);
    
        public static void increment() {
            count.getAndIncrement();
        }
    
    }

    AtomicIntege不需要添加synchronized,就可以使用线程安全的方式操作加减,适合高并发情况下的使用

  31. 什么是ThreadLocal
    ThreadLocal的实例代表了一个线程局部的变量,每条线程都只能看到自己的值,并不会意识到其它的线程中也存在该变量。它采用采用空间来换取时间的方式,解决多线程中相同变量的访问冲突问题。
  32. 什么是线程池?
    因为创建和销毁线程都是非常消耗系统资源的,线程池就是提前创建若干个线程,如果有任务需要处理,线程池里的线程就会处理任务,处理完之后线程并不会被销毁,而是等待下一个任务。
  33. 线程池的作用
    降低资源消耗。  通过重复利用已创建的线程,降低线程创建和销毁造成的资源消耗
    提交响应速度。当有任务时,不需要等到线程创建。
    方便线程的管理。使用线程池可以进行统一分配、调优和监控
  34. 线程池的使用场景
    线程池是为突然大量爆发的线程设计的,使用线程池创建好的线程,减少了创建和销毁线程所需的时间,从而提高效率。
    如果一个线程的时间非常长,就没必要用线程池了(不是不能作长时间操作,而是不宜。),况且我们还不能控制线程池中线程的开始、挂起、和中止。
  35. 线程池配置
    主要判断你的业务场景,IO密集和CPU密集
    IO密集        阻塞: 即请求、数据库操作、循环等属于IO密集
    意思是该任务需要大量的IO,即大量的阻塞。在单线程上运行IO密集型的任务会导致浪费大量的
    最大线程数一般设置:cpu核数乘以2(线程的等待时间和cpu运行时间都是1秒的话)
    如果你的线程等待时间很大则最大线程数的个数一般设置:(线程等待时间与线程CPU时间之比 + 1)* CPU数目
    CPU密集
    意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
    最大线程数一般设置:cpu核数
    线程等待时间所占比例越高,需要越多线程。 
  36. 线程池的四种创建方式 
     1 、创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程
            ExecutorService executorService = Executors.newCachedThreadPool(); 
     2、创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
           ExecutorService executorService = Executors.newFixedThreadPool(3);  
     3、创建一个定长线程池,支持定时及周期性任务执行
          ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(3);
     4、创建一个单线程化的线程池,唯一的工作线程来执行任务,保证任务按照指定顺序(FIFO, LIFO, 优先级)执行。
           ExecutorService executorService = Executors.newSingleThreadExecutor();
  37. 说说JDK1.5并发包
     

    名称

    作用

    Lock

    Executors

    线程池

    ReentrantLock

    一个可重入的互斥锁定 Lock,功能类似synchronized,但要强大的多。

    Condition

     Condition的功能类似于在传统的线程技术中的,Object.wait()和Object.notify()的功能,

     

    ConcurrentHashMap

    分段HasMap

    AtomicInteger

    原子类

    BlockingQueue

     

    BlockingQueue 通常用于一个线程生产对象,而另外一个线程消费这些对象的场景

    ExecutorService

     

    执行器服务

     
  38. 什么是死锁 
    同步中嵌套同步,导致无法释放
  39. 锁的种类
    自旋锁
    互斥锁 
    可重入锁
    悲观锁
    乐观锁
  40. 乐观锁
    本质没有锁,效率高,无阻赛,无等待
    经常用于数据数库的update和delete操作,   在设计表的时候加个version字段,用于更新、删除时的判断条件
    select number, version from table;   // 先拿到数据和版本号
    update table set number = 123, version = version+1 where id = #{id} and version = #{version};  // 更新时加上判断
    上述问题 :若果有2个线程都拿到了同样的版本号,来做update时,就看谁走的看谁先执行。先执行的同时修改version
  41. 悲观锁
    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,
    所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁。
    传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,
    都是在做操作之前先上锁。再比如Java里面的同步原语synchronized关键字的实现也是悲观锁。
  42. cas无锁机制 
     cas本身使用的还是乐观锁机制,每次修改时都会将期望值与更新值作比较,如果相等表示没被其他线程该过。
     缺点: 如果一个线程将它的值修改后又改回来原来的值,它也认为没被改过。
  43. SpringBoot默认线程配置 
     默认核心线程数:8
     最大线程数:Integet.MAX_VALUE
     队列使用LinkedBlockingQueue
     容量是:Integet.MAX_VALUE
     空闲线程保留时间:60s
  44. 未完待续......
     
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值