Java-多线程

多线程

  • 什么是线程和进程?

进程是系统运行程序的基本单位,在Java中启动一个main函数就是启动一个JVM进程,main函数所在的线程叫主线程.

线程是一个比进程还小的执行单位,一个进程可以产生多个线程.

总结 : 进程间是互相独立的,线程间是共享进程的空间,所以线程会有并发问题.

JVM

  • 运行时数据区域

线程私有 : 程序计数器,虚拟机栈,本地方法栈

线程共享 : 堆,方法区,直接内存

  • 程序计数器

主要有2个作用:
1. 字节码解释器通过改变程序计数器来决定下一条指令,从而控制代码流程.
2. 多线程情况下,程序计数器用来记录当前线程执行的位置,当线程切换回来的时候可以继续之前的流程,所以程序计数器是私有的.

  • Java 虚拟机栈

线程私有,Java内存大致可以分为堆区和栈区,栈区指的就是虚拟机栈,一般用来局部变量.

虚拟机栈可以出现的异常:
1. StackOverFlowError 当前线程请求栈的深度超过了虚拟机栈的最大深度.
2. OutOfMemoryError 当请求栈时,内存用完了且无法拓展时.

  • 本地方法栈

线程私有,和虚拟机栈类似,区别是主要为 Native 方法服务.

虚拟机管理的内存中最大的一块,线程共享.垃圾回收管理的主要区域,所以也成为GC堆.几乎所有的对象实例以及数组都在这分配内存.

  • 方法区

线程共享,主要存放被虚拟机加载的类的信息,常量,静态变量等.

多线程

  • 虚拟机栈和本地⽅法栈为什么是私有的?

这两个栈都是用来存放局部变量的,所以私有是为了防止别的线程访问到当前线程的局部变量.

  • 说说并发与并⾏的区别?
  1. 并发:一段时间内,多个任务都在执行.(单位时间内,只有一个任务在执行.)
  2. 并行:单位时间内,多个任务同时执行.
  • 什么是上下⽂切换?

当一个线程的时间片用完的时候就会变成就绪态,让其他线程使用,这个过程就是一次上下文切换.

  • 什么是线程死锁?如何避免死锁?

线程1拥有资源A,线程2拥有资源B,此时线程1去申请资源B,由于B已经被线程2持有,所以线程1进入阻塞等待状态,并且不会主动释放所持有的资源A,这就叫死锁.

  • 如何避免线程死锁?

产生死锁的条件:

  1. 互斥条件 : 该资源某一时刻只能被一个线程所持有.
  2. 请求与保持条件 : 进程因为请求资源而阻塞时,已持有的资源保持不放.
  3. 不剥夺条件 : 线程所持有的资源除非自己主动释放,否则一直持有,其他线程无法强行剥夺.
  4. 循环等待条件 : 多个线程形成一个资源循环等待环.

避免死锁,破坏条件:

  1. 破坏互斥条件 : 无法破坏,因为锁的本意就是让资源在一段时间内只能被一个线程所持有.
  2. 破坏请求与保持条件 : 一次性申请所有资源.
  3. 破坏不剥夺条件 : 当线程申请资源失败的时候,释放自己所持有的资源.
  4. 破坏循环等待条件 : 按顺序申请资源.
  • sleep() ⽅法和 wait() ⽅法区别?
  1. sleep不会释放锁,wait会释放锁.
  2. 都可以暂停线程.
  3. wait调用后不会自动苏醒,需要别的线程调用notify()唤醒,但是可以使用wait(long timeout)超时自动苏醒.
  • 为什么我们调⽤ start() ⽅法时会执⾏ run() ⽅法,为什么我们不能直接调⽤ run() ⽅法?

调用start()时,会启动一个线程,当线程获得CPU时间时执行run()里面的内容;直接调用run(),会把run()当初main()下的一个普通方法去执行.

  • synchronized关键字最主要的三种使⽤⽅式?
  1. 修饰实例(普通)方法 : 给当前实例对象加锁.
//假设 Demo 类的 demo() 被 synchronized 修饰
//例1 : 线程1,2抢占的是同一把锁,只能有一个线程持有.
public static void main(String[] args) {
       Demo demo = new Demo();
       Thread thread1 = new Thread(new MyThread(demo));
       Thread thread2 = new Thread(new MyThread(demo));
       thread1.start();
       thread2.start();
}

//例2 : 线程1,2抢占的是两把锁
public static void main(String[] args) {
       Demo demo1 = new Demo();
       Demo demo2 = new Demo();
       Thread thread1 = new Thread(new MyThread(demo1));
       Thread thread2 = new Thread(new MyThread(demo2));
       thread1.start();
       thread2.start();
}
  1. 修饰静态方法 : 给当前类加锁,作用于类的所有实例对象.

修饰静态方法后上面不论是例1还是例2都是抢占一个锁.

  1. 修饰代码块 : 给指定对象加锁.
  • synchronized 关键字的底层原理?

    synchronized 关键字属于JVM层面.
    
    通过查看字节码信息可以发现, synchronized 锁定代码的前后分别是 monitorenter 和  monitorexit ;当执行 monitorenter 时,线程去尝试获取锁(monitor,它存在于每个Java对象的头部,
    所以Java对象都可以作为锁.),当计数器为0时获取成功,然后将计数器加1;执行 monitorexit 时,计数器减一. 如果锁获取失败,那么当前线程就一直等待,直到锁被另一个线程释放.
    
  • volatile关键字?

  1. 可见性 : 通知JVM这个变量是不稳定的,每次去主内存(线程共享)中取,而不是使用工作内存中的值(缓存).
  2. 指令重排 : 本例中如果不禁止指令重排可能造成 instance 对象还未初始化完成就被拿去使用了.
//此操作非原子性
instance= new Singleton();

memory =allocate();    //1:分配对象的内存空间 
ctorInstance(memory);  //2:初始化对象 
instance =memory;     //3:设置instance指向刚分配的内存地址
  • 并发编程的三个重要特性?
  1. 原子性 :
  2. 可见性 : 一个线程对共享变量进行修改,其他线程都是可以立即看到修改后的最新值.
  3. 有序性 : java编译及运行期间的优化可能使代码的执行顺序未必就是编写代码时的顺序.
  • ThreadLocal

创建一个 ThreadLocal 变量,那么每个访问这个变量的线程都会有这个变量的副本.

  • ThreadLocal原理

对于 Thread 类,我们发现每个线程都有一个 ThreadLocalMap

public class Thread implements Runnable {
       ......
      //与此线程有关的ThreadLocal值。由ThreadLocal类维护
      ThreadLocal.ThreadLocalMap threadLocals = null;
      //与此线程有关的InheritableThreadLocal值。由InheritableThreadLocal类维护
      ThreadLocal.ThreadLocalMap inheritableThreadLocals = null;
       ......
}

ThreadLocal 的 set() 方法;总结: 最终变量存在与当前线程的 ThreadLocalMap 中,而 ThreadLocal 仅仅用来传递变量值.

 public void set(T value) {
       Thread t = Thread.currentThread();
       ThreadLocalMap map = getMap(t);
       if (map != null)
             map.set(this, value);
       else
             createMap(t, value);
       }
       ThreadLocalMap getMap(Thread t) {
             return t.threadLocals;
 }
  • ThreadLocal 内存泄露问题

ThreadLocalMap 是用来存放是数据的,其中 key 就是 ThreadLocal 对象 ,value 就是 set(Object value)传入的值.
ThreadLocalMap 的实现并不是传统的继承MAP接口,而是自己实现的,它用 Entry[] 来存储数据

//Entry对象继承了 WeakReference(弱引用) 所以他的key是弱引用
static class Entry extends WeakReference<ThreadLocal> {
    /** The value associated with this ThreadLocal. */
    Object value;
    Entry(ThreadLocal k, Object v) {
        super(k);
        value = v;
    }
}
//WeakReference<ThreadLocal> 指定了 ThreadLocal 为弱引用.

所以在GC的时候 key 会被清除掉,然而 value 是强引用,所以不会被清除,此时就会出现key为空的Entry ,value 永远无法被回收,所以最好手动调用 remove()

线程池

  • 优点
  1. 降低资源消耗:频繁的创建销毁线程是很消耗系统资源的。
  2. 提高响应速度:请求到了直接拿来就可以用,不用再去新建。
  3. 提高线程的可管理性:线程资源统一分配,调优和监控。
  • Runnable 和 Callable 的区别?

Runnable 无返回值,Callable 有返回值。

  • execute 和 submit 区别?

前者无返回值,后者有返回值。

  • ThreadPoolExecutor3个最重要的参数:
  1. corePoolSize:核心线程数。
  2. maximumPoolSize:最大线程数(任务队列满时)
  3. workQueue:任务队列

转自JavaGuide面试突击版

原文地址:https://www.cnblogs.com/qifengle1412/p/12885440.html