Java多线程面经题总结

一、基本概念

  1. 什么是进程?什么是线程?
    进程和线程都是一个时间段的描述,是CPU工作时间段的描述,不过是颗粒大小不同。
    CPU很快,那当多个任务要执行的时候怎么办呢?答案是CPU轮流着来执行。
    执行一段程序代码,当得到CPU的时候,相关的资源必须也已经就位,然后CPU开始执行。这里除了CPU以外所有的就构成了这个程序的执行环境,也就是我们所定义的程序上下文。当这个程序执行完了,或者分配给他的CPU执行时间用完了,那它就要被切换出去,等待下一次CPU的临幸。在被切换出去的最后一步工作就是保存程序上下文,因为这个是下次他被CPU临幸的运行环境,必须保存。
    因此前面讲过在CPU看来所有的任务都是一个一个的轮流执行的,具体的轮流方法就是:先加载程序A的上下文,然后开始执行A,保存程序A的上下文,调入下一个要执行的程序B的程序上下文,然后开始执行B,保存程序B的上下文。
    进程和线程就是这样的背景出来的,两个名词不过是对应的CPU时间段的描述,名词就是这样的功能。
    进程就是包换上下文切换的程序执行时间总和 = CPU加载上下文+CPU执行+CPU保存上下文。
    线程是什么呢?进程的颗粒度太大,每次都要有上下的调入,保存,调出。如果我们把进程比喻为一个运行在电脑上的软件,那么一个软件的执行不可能是一条逻辑执行的,必定有多个分支和多个程序段,就好比要实现程序A,实际分成 a,b,c等多个块组合而成。那么这里具体的执行就可能变成:
    程序A得到CPU =》CPU加载上下文,开始执行程序A的a小段,然后执行A的b小段,然后再执行A的c小段,最后CPU保存A的上下文。
    这里a,b,c的执行是共享了A的上下文,CPU在执行的时候没有进行上下文切换的。这里的a,b,c就是线程,也就是说线程是共享了进程的上下文环境的更为细小的CPU时间段。
  2. 什么是进程?什么是线程?
    进程就是在内存中执行的程序;线程是轻量级进程;各进程是独立的,而各线程则不一定。
  3. 什么是并行?什么是并发?
    并行:并行的关键是你有同时处理多个任务的能力;
    并发:有处理多个任务的能力,不一定要同时;
  4. 为什么要使用多线程?
    最大化利用CPU和CPU时间片。发挥多处理器的作用,提高性能和响应速度。
  5. 多线程研究什么?
    1. 线程间的互斥:多线程加法;
    2. 线程间的协同:生产者消费者模型;
      i++的操作分为如下下三步:1. 读取内存中原有i值; 2. 计算i+1; 3. 将i写入内存;
  6. 用户进程间通信主要哪几种方式?
    1、管道:管道是单向的、先进先出的、无结构的、固定大小的字节流,它把一个进程的标准输出和另一个进程的标准输入连接在一起。写进程在管道的尾端写入数据,读进程在管道的道端读出数据。数据读出后将从管道中移走,其它读进程都不能再读到这些数据。管道提供了简单的流控制机制。进程试图读空管道时,在有数据写入管道前,进程将一直阻塞。同样地,管道已经满时,进程再试图写管道,在其它进程从管道中移走数据之前,写进程将一直阻塞。
    无名管道:管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系(通常是指父子进程关系)的进程间使用。
    命名管道:命名管道也是半双工的通信方式,在文件系统中作为一个特殊的设备文件而存在,但是它允许无亲缘关系进程间的通信。当共享管道的进程执行完所有的I/O操作以后,命名管道将继续保存在文件系统中以便以后使用。
    2、信号量:信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
    3、消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
    4、信号:信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
    5、共享内存:共享内存就是映射一段能被其它进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的IPC方式,它是针对其它进程间通信方式运行效率低而专门设计的。它往往与其它通信机制(如信号量)配合使用,来实现进程间的同步和通信。
    6、套接字:套接字也是一种进程间通信机制,与其它通信机制不同的是,它可用于不同机器间的进程通信。

二、多线程的实现方法

  1. 创建线程的方法?
    1. 继承Thread类,重写run()方法;无返回值。
    2. 实现Runnable接口,重写run()方法。创建Runnable实现类的实例,并依此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象;runnable接口是一个task,需要分配一个线程。无返回值。
    3. 实现Callable接口。有返回值。
  2. 一些常用的多线程的方法(Thread类):
    1. currentThread():返回对当前正在执行的线程对象的引用。
    2. getId():返回此线程的标识符;
    3. getName():返回此线程的名称;
    4. getPriority():返回此线程的优先级;
    5. isAlive():测试这个线程是否还处于活动状态;
    6. sleep(long millis):使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行);
    7. interrupt():中断这个线程;
    8. interrupted(): 测试当前线程是否已经是中断状态
    9. setDaemon(boolean on):将此线程标记为 daemon线程或用户线程;
    10. join():如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。join()方法是指等待调用join()方法的线程执行结束,程序才会继续执行下去。
    11. yield():作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。
  3. 如何停止一个线程?
    一个线程不应该由其他线程来强制中断或停止,而是应该由线程自己自行停止。所以,Thread.stop, Thread.suspend, Thread.resume 都已经被废弃了。而 Thread.interrupt 的作用其实也不是中断线程,而是「通知线程应该中断了」,具体到底中断还是继续运行,应该由被通知的线程自己处理。
    具体来说,当对一个线程,调用 interrupt() 时,有两种情况:
    ① 如果线程处于被阻塞状态(例如处于sleep, wait, join 等状态),那么线程将立即退出被阻塞状态,并抛出一个InterruptedException异常。仅此而已。
    ② 如果线程处于正常活动状态,那么会将该线程的中断标志设置为true,仅此而已。被设置中断标志的线程将继续正常运行,不受影响。
    interrupt() 并不能真正的中断线程,需要被调用的线程自己进行配合才行。也就是说,一个线程如果有被中断的需求,那么就可以这样做:
    ① 在正常运行任务时,经常检查本线程的中断标志位,如果被设置了中断标志就自行停止线程。
    ② 在调用阻塞方法时正确处理InterruptedException异常。(例如,catch异常后就结束线程。)
  4. 多线程的分类?
    1. 用户进程;
    2. 守护进程,最常见的守护线程:垃圾回收线程

三、线程的生命周期

  1. [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8VrKmeLe-1612354614399)(http://williamsun.cn/images/ThreadLifecycle.jpeg)]
  2. wait/notify机制主要用于线程间的协同。
    等待/通知机制,是指一个线程A调用了对象O的wait()方法进入等待状态,而另一个线程B调用了对象O的notify()/notifyAll()方法,线程A收到通知后退出等待队列,进入可运行状态,进而执行后续操作。上诉两个线程通过对象O来完成交互,而对象上的wait()方法和notify()/notifyAll()方法的关系就如同开关信号一样,用来完成等待方和通知方之间的交互工作。
    当方法wait()被执行后,锁自动被释放,但执行完notify()方法后,锁不会自动释放。必须执行完notify()方法所在的synchronized代码块后才释放。

三、线程属性

  1. 什么是线程组?
    在Java中每一个线程都归属于某个线程组管理的一员,例如在主函数main()主工作流程中产生一个线程,则产生的线程属于main这个线程组管理的一员。
    简单地说,线程组就是由线程组成的管理线程的类,这个类是java.lang.ThreadGroup类。
    默认情况下,系统线程组是所有线程和线程组的父级。
  2. 获取当前线程?
    Thread current = Thread.currentThread();
  3. wait(),sleep()和start()方法?
    sleep():释放CPU的执行权,不释放锁;
    wait():释放CPU的执行权,释放锁;
    start():让线程跑起来;

四、线程锁

  1. 隐式锁synchronized?
    如果两个线程同时操作对象中的实例变量,则会出现“非线程安全”,解决办法就是在方法前加上synchronized关键字即可。
    synchronized取得的锁都是对象锁,而不是把一段代码或方法当做锁。前提是多个线程访问的是同一个对象。如果多个线程访问的是同一个对象,哪个线程先执行带synchronized关键字的方法,则哪个线程就持有该方法,那么其他线程只能呈等待状态。
    利用sychronized实现同步的基础:java中每一个对象都可以作为锁。具体表现为以下3中形式。
    1.对于普通方法的同步,锁是当前实例对象。
    2.对于静态方法的同步,锁是当前类的Class对象。
    3.对于同步方法块,锁是sychronized括号里配置的对象。
    当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。尤其关键的是,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。
    如果父类有一个带synchronized关键字的方法,子类继承并重写了这个方法。 但是同步不能继承,所以还是需要在子类方法中添加synchronized关键字。

  2. 显示锁Lock?
    Lock接口的实现类:
    ReentrantLock, ReentrantReadWriteLock.ReadLock,ReentrantReadWriteLock.WriteLock
    synchronized关键字与wait()和notify/notifyAll()方法相结合可以实现等待/通知机制,ReentrantLock类当然也可以实现,但是需要借助于Condition接口与newCondition() 方法。
    在使用notify/notifyAll()方法进行通知时,被通知的线程是有JVM选择的,使用ReentrantLock类结合Condition实例可以实现“选择性通知”,这个功能非常重要,而且是Condition接口默认提供的。await()/signal()/signalAll()

  3. Synchronized和lock区别?

  4. 公平锁与非公平锁.
    Lock锁分为:公平锁 和 非公平锁。公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得的FIFO先进先出顺序。而非公平锁就是一种获取锁的抢占机制,是随机获取锁的,和公平锁不一样的就是先来的不一定先的到锁,这样可能造成某些线程一直拿不到锁,结果也就是不公平的了。
    Service service = new Service(true);//true为公平锁,false为非公平锁

  5. ThreadLocal?
    线程局部变量,是一个map,保存线程的副本。

  6. 关键字volatile?
    在当前的 Java 内存模型下,线程可以把变量保存高速缓存(比如机器的寄存器)中,而不是直接在主存中进行读写。这就可能造成一个线程在主存中修改了一个变量的值,而另外一个线程还继续使用它在寄存器中的变量值的拷贝,造成数据的不一致。 要解决这个问题,就需要把变量声明为 volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取和写入。在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性。volatile,这就指示 JVM,这个变量是不稳定的,每次使用它都到主存中进行读取。
    可见性:
    volatile 修饰的成员变量在每次被线程访问时,都强迫从主存(共享内存)中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到主存(共享内存)。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值,这样也就保证了同步数据的可见性。

  7. 原子操作:atomic?
    支持在单个变量上进行线程安全编程。例如:AtomicBoolean, AtomicInteger, AtomicLong, AtomicReference等一些基本类型。

  8. 为什么不能使用String类型的锁?
    因为数据类型String的常量池属性,所以synchronized(string)在使用时某些情况下会出现一些问题,比如两个线程运行
    synchronized(“abc”){
    }和
    synchronized(“abc”){
    }修饰的方法时,这两个线程就会持有相同的锁,导致某一时刻只有一个线程能运行。所以尽量不要使用synchronized(string)而使用synchronized(object)

  9. volatile和synchronized的比较?

    1. volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized关键字要好。但是volatile关键字只能用于变量而synchronized关键字可以修饰方法以及代码块。
    2. 多线程访问volatile关键字不会发生阻塞,而synchronized关键字可能会发生阻塞;
    3. volatile关键字能保证数据的可见性,但不能保证数据的原子性。synchronized关键字两者都能保证。
    4. volatile关键字用于解决变量在多个线程之间的可见性,而synchronized关键字解决的是多个线程之间访问资源的同步性。
  10. 什么是死锁?死锁的条件有哪些?
    两个或者多个线程之间相互等待,导致线程都无法执行,叫做线程死锁。
    互斥条件:使用的资源是不能共享的。
    不可抢占条件:线程持有一个资源并等待获取一个被其他线程持有的资源。
    请求与保持条件:线程持有一个资源并等待获取一个被其他线程持有的资源。
    循环等待条件:线程之间形成一种首尾相连的等待资源的关系。

五、线程同步

  1. 什么是线程安全?
    当一个类,不断被多个线程调用,仍能表现出正确的行为时,那它就是线程安全的。多线程同时访问同一份资源,才有可能发生线程安全问题。
    要想写出线程安全的代码,我们需要用到使用synchronize、volatile,或者将线程安全委托给基础构建模块(concurrent包下)。
  2. 什么是线程同步?
    多线程之间访问线程安全。线程同步是实现线程安全的一种手段。
  3. java.util.concurrent.*;包下的类。
  4. 什么是同步?什么是异步?
    同步是指两个线程的运行是相关的,其中一个线程要阻塞等待另外一个线程的运行。异步的意思是两个线程毫无相关,自己运行自己的。

六、线程间通信

  1. Exchanger:两线程交换数据;
  2. locks.Condition():线程互相唤醒和等待;
  3. 通过多线程的线程安全集合变量;
  4. 使用管道流;
  5. 线程间通信的方式?
    synchronized关键字、volatile关键字以及等待/通知(wait/notify)机制。管道输入/输出流、Thread.join()的使用、ThreadLocal的使用。
    1. 管道输入/输出流和普通文件的输入/输出流或者网络输入、输出流不同之处在于管道输入/输出流主要用于线程之间的数据传输,而且传输的媒介为内存。
    2. Thread.join():主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了。另外,一个线程需要等待另一个线程也需要用到join()方法。
    3. ThreadLocal:线程局部变量。有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。

七、线程池

  1. 无限制创建线程的不足。线程池的优势。
    降低资源消耗。提高响应速度。提高响应速度。

  2. Executor 框架

    Executor 框架结构主要由三大部分组成:

    1. 任务:执行任务需要实现的Runnable接口或Callable接口。
    2. 任务的执行:包括任务执行机制的核心接口Executor ,以及继承自Executor 接口的ExecutorService接口。ScheduledThreadPoolExecutor和ThreadPoolExecutor这两个关键类实现了ExecutorService接口。
    3. 异步计算的结果
  3. 父类ThreadPoolExecutor.
    (1)线程池大小corePoolSize();
    当线程池里的线程数少于corePoolSize时,每来一个任务,我就创建一条线程去处理,不管线程池中有没有空闲的线程;
    线程池里的线程数达到corePoolSize时,新来的任务,会先放到任务队列里面;
    当任务队列放满了(如果队列是有界队列),那么要怎么办?马上拒绝新的任务吗?似乎不妥,面对这种业务突然繁忙的情况,我是不是可以破例再创建多几条线程呢?于是就有了maximumPoolSize,如果任务队列满了,但是线程池中的线程数还少于maximumPoolSize,那我就允许线程池继续创建线程;
    从厨房拿出来的桌子,在高峰期过后,就要渐渐撤回了吧?同样,当我发现线程池中线程的数量超过corePoolSize,就会去监控线程,发现某条线程很久没有工作了,就把它关掉,这里的很久是多久,那就要看你传过来的keepAliveTime是多少了。
    (2)饱和策略:RejectedExecutionHandler
    1)AbortPolicy:默认情况,直接抛出异常;
    2)CallerRunsPolicy:只用调用者所在线程执行任务;
    3)DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务;
    4)DiscardPolicy:不处理,丢弃掉;
    5)实现RejectedExecutionHandler接口,自定义策略;

  4. Executors类,在父类基础上做了一些包装。
    Executors.newCachedThreadPool():无界线程池,可以自动进行线程回收;
    Executors.newFixedThreadPool(int):固定大小线程池;
    Executors.newSingleThreadExecutor():单个后台线程;

  5. ExecutorService类的方法execute(), submit(Runnable task), shutdown;

  6. 各种线程池的适用场景介绍
    FixedThreadPool: 适用于为了满足资源管理需求,而需要限制当前线程数量的应用场景。它适用于负载比较重的服务器;
    SingleThreadExecutor: 适用于需要保证顺序地执行各个任务并且在任意时间点,不会有多个线程是活动的应用场景。
    CachedThreadPool: 适用于执行很多的短期异步任务的小程序,或者是负载较轻的服务器;
    ScheduledThreadPoolExecutor: 适用于需要多个后台执行周期任务,同时为了满足资源管理需求而需要限制后台线程的数量的应用场景;
    SingleThreadScheduledExecutor: 适用于需要单个后台线程执行周期任务,同时保证顺序地执行各个任务的应用场景。

八、线程队列

  1. BlockingQueue相关实现:
    1. ArrayBlockingQueue;
    2. LinkedBlockingQueue;
    3. PriorityBlockingQueue;
    4. SychronousQueue;

九、线程阀

  1. CountDownLatch:计数装置。当需要等待某个条件达到要求后才能做后面的事情;同时当线程都完成后也会触发时间,以便进行后面的操作。这时候就使用CountDownLatch,里面最终要的方法是countDown()和await();

  2. CyclicBarrier:循环屏障。与CountDownLatch相似,但是可以设定等待次数的规则;

  3. Future->FutureTask:闭锁。结合Runnable使用。一般FutureTask多用于耗时的计算,主线程在完成自己的任务后,再去获取结果。只有在计算完成时获取,否则会一直阻塞直到任务完成状态。

  4. Semaphore:信号量。维护当前访问个数,提供同步机制,控制同时访问的个数。

  5. ThreadLocal的使用?
    变量值的共享可以使用public static变量的形式,所有线程都使用一个public static变量。如果想实现每一个线程都有自己的共享变量该如何解决呢?JDK中提供的ThreadLocal类正是为了解决这样的问题。ThreadLocal类主要解决的就是让每个线程绑定自己的值,可以将ThreadLocal类形象的比喻成存放数据的盒子,盒子中可以存储每个线程的私有数据。
    ThreadLocal翻译成中文比较准确的叫法应该是:线程局部变量。
    这个玩意有什么用处,或者说为什么要有这么一个东东?先解释一下,在并发编程的时候,成员变量如果不做任何处理其实是线程不安全的,各个线程都在操作同一个变量,显然是不行的,并且我们也知道volatile这个关键字也是不能保证线程安全的。那么在有一种情况之下,我们需要满足这样一个条件:变量是同一个,但是每个线程都使用同一个初始值,也就是使用同一个变量的一个新的副本。这种情况之下ThreadLocal就非常使用,比如说DAO的数据库连接,我们知道DAO是单例的,那么他的属性Connection就不是一个线程安全的变量。而我们每个线程都需要使用他,并且各自使用各自的。这种情况,ThreadLocal就比较好的解决了这个问题。
    应用场景:当很多线程需要多次使用同一个对象,并且需要该对象具有相同初始化值的时候最适合使用ThreadLocal。
    事实上,从本质来讲,就是每个线程都维护了一个map,而这个map的key就是threadLocal,而值就是我们set的那个值,每次线程在get的时候,都从自己的变量中取值,既然从自己的变量中取值,那肯定就不存在线程安全问题,总体来讲,ThreadLocal这个变量的状态根本没有发生变化,他仅仅是充当一个key的角色,另外提供给每一个线程一个初始值。如果允许的话,我们自己就能实现一个这样的功能,只不过恰好JDK就已经帮我们做了这个事情。

  6. InheritableThreadLocal?
    ThreadLocal类固然很好,但是子线程并不能取到父线程的ThreadLocal类的变量,InheritableThreadLocal类就是解决这个问题的。

  7. 任务从保存到再加载的过程就是一次上下文切换。如何减少上下文切换?

    1. 减少锁的使用。因为多线程竞争锁时会引起上下文切换。
    2. 使用CAS算法。这种算法也是为了减少锁的使用。CAS算法是一种无锁算法。
    3. 减少线程的使用。人物很少的时候创建大量线程会导致大量线程都处于等待状态。
    4. 使用协程。
  8. CAS算法?
    CAS(比较与交换,Compare and swap) 是一种有名的无锁算法。无锁编程,即不使用锁的情况下实现多线程之间的变量同步,也就是在没有线程被阻塞的情况下实现变量的同步,所以也叫非阻塞同步(Non-blocking Synchronization)。实现非阻塞同步的方案称为“无锁编程算法”( Non-blocking algorithm)。
    相对应的,独占锁是一种悲观锁,synchronized就是一种独占锁,它假设最坏的情况,并且只有在确保其它线程不会造成干扰的情况下执行,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

  9. 四种避免死锁的常见方法:
    避免一个线程同时获得多个锁
    避免一个线程在锁内同时占用多个资源,尽量保证每个锁只占用一个资源
    尝试使用定时锁,使用lock.tryLock(timeout)来替代使用内部锁机制
    对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况

  10. 解释下,关于线程安全的最基础的两个关键字:volatile和synchronized?
    volatile是让修改的变量立刻同步到主内存的;volatile实现了比synchronize更轻量级的同步机制,或者说,加锁机制既确保了可见性,有确保了原子性,而volatile只能保证可见性。
    synchronized用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码; 他通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。
    synchronized的作用:确保了操作的原子性。原先count++是三个动作,其他线程可以在这三个操作之间对count变量进行修改,而在使用了synchronize之后,这三个动作就变成一个不可拆分、一气呵成的动作,不必担心在这个操作的过程中会有其他线程进行干扰,这就是原子性。原子操作是线程安全的,这其实也是我们经常使用synchronize来实现线程安全的原因。
    可见性的意思是变量的修改可以被其他线程观察到,在上面计数器的例子中,由于一次只有一个线程可以执行count++,抢不到锁的线程,必须等抢到锁的线程更新完count之后,才可以去执行count++,而这个时候,count也已经完成了更新,新的锁持有者,可以看到更新后的count,而不至于拿着旧的count值去进行计算,这就是可见性。
    synchronized如何“保证在同一时刻最多只有一个线程执行该段代码”的?
    关于synchronize,我们经常使用的隐喻就是锁,首先进入的线程,拿到了锁的唯一一把钥匙,至于其他线程,就只能阻塞(Blocked);等到线程走出synchronize之后,会把锁释放掉,也就是把钥匙扔出去,下一个拿到钥匙的线程,就可以结束阻塞状态,继续运行。
    但是锁从哪来呢?随随便便抓起一个东西就可以作为锁么?
    还真是这样,Java中每一个对象都有一个与之关联的锁,称为内置锁:
    当我们使用synchronize修饰非静态方法时,用的是调用该方法的实例的内置锁,也就是this;
    当我们使用synchronize修饰静态方法时,用的是调用该方法的所在的类对象的内置锁;
    内置锁的可重入性(Reentrancy):子类、父类之间。
    Java中每个对象都有一个内置锁。
    与内置锁相对的是显示锁,使用显示锁需要手动创建Lock对象,而内置锁则是所有对象自带的。
    synchronized使用对象自带的内置锁来进行加锁,从而保证在同一时刻最多只有一个线程执行代码。
    所有的加锁行为,都可以带来两个保障——原子性和可见性。其中,原子性是相对锁所在的线程的角度而言,而可见性则是相对其他线程而言。
    锁的持有者是“线程”,而不是“调用”,这也是锁的为什么是可重入的原因。
    可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。

  11. 堆区:只存放类对象,线程共享;
    方法区:又叫静态存储区,存放class文件和静态数据,线程共享;
    栈区:存放方法局部变量,基本类型变量区、执行环境上下文、操作指令区,线程不共享;

  12. 脏读?读取的值被别人改过了。发生脏读的情况实在读取实例变量时,此值已经被其他线程更改过。

  13. 锁的可重入性。

    public class UnReentrant{
        Lock lock = new Lock();
        public void outer(){
            lock.lock();
            inner();
            lock.unlock();
        }
        public void inner(){
            lock.lock();
            //do something
            lock.unlock();
        }
    }
    

outer中调用了inner,outer先锁住了lock,这样inner就不能再获取lock。其实调用outer的线程已经获取了lock锁,但是不能在inner中重复利用已经获取的锁资源,这种锁即称之为不可重入。
通常也称为自旋锁 。相对来说,可重入就意味着:线程可以进入任何一个它已经拥有的锁所同步着的代码块。

  1. Java中wait、sleep、yield的区别?
    1. sleep()和yield()方法是定义在Thread类中,而wait()方法是定义在Object类中的
    2. wait()和sleep()的关键的区别在于,wait()是用于线程间通信的,而sleep()是用于短时间暂停当前线程。更加明显的一个区别在于,当一个线程调用wait()方法的时候,会释放它锁持有的对象的管程和锁,但是调用sleep()方法的时候,不会释放他所持有的管程。
    3. yield()方法上来,与wait()和sleep()方法有一些区别,它仅仅释放线程所占有的CPU资源,从而让其他线程有机会运行,但是并不能保证某个特定的线程能够获得CPU资源。谁能获得CPU完全取决于调度器,在有些情况下调用yield方法的线程甚至会再次得到CPU资源。所以,依赖于yield方法是不可靠的,它只能尽力而为。
    4. Java中的wait方法应在同步代码块中调用,但是sleep方法不需要。
    5. 使用sleep方法时,被暂停的线程在被唤醒之后会立即进入就绪态(Runnable state),但是使用wait方法的时候,被暂停的线程会首先获得锁(译者注:阻塞态),然后再进入就绪态。所以,根据你的需求,如果你需要暂定你的线程一段特定的时间就使用sleep()方法,如果你想要实现线程间通信就使用wait()方法。
  2. wait和sleep区别?
    wait只能在同步(synchronize)环境中被调用,而sleep不需要。详见Why to wait and notify needs to call from synchronized method
    进入wait状态的线程能够被notify和notifyAll线程唤醒,但是进入sleeping状态的线程不能被notify方法唤醒。
    wait通常有条件地执行,线程会一直处于wait状态,直到某个条件变为真。但是sleep仅仅让你的线程进入睡眠状态。
    wait方法在进入wait状态的时候会释放对象的锁,但是sleep方法不会。
    wait方法是针对一个被同步代码块加锁的对象,而sleep是针对一个线程。
  3. yield和sleep的区别
    记住sleep和yield作用于当前线程。
    yield方法会临时暂停当前正在执行的线程,来让有同样优先级的正在等待的线程有机会执行。如果没有正在等待的线程,或者所有正在等待的线程的优先级都比较低,那么该线程会继续运行。执行了yield方法的线程什么时候会继续运行由线程调度器来决定,不同的厂商可能有不同的行为。yield方法不保证当前的线程会暂停或者停止,但是可以保证当前线程在调用yield方法时会放弃CPU。
  4. 问:阻塞和等待之间的区别
    答:等待在一个等待队列里,要有人通知才能再度回到runnable状态争抢锁,阻塞则是一直在那循环尝试抢占锁。
  5. start方法和run方法区别?
    start() 方法的作用是启动一个新线程,新线程会执行相应的run()方法,start()不能被重复调用。
    而run()方法则只是普通的方法调用,在调用线程中顺序运行而已。
  6. 一个进程一定要有一个线程吗?没有线程的进程是什么?
    通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义。
  7. 并发,为什么要用多线程?
  8. wait方法能不能被重写,wait能不能被中断?
    wait()、notify()和notifyAll()方法是本地方法,并且为final方法,无法被重写
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dylan、

耕码不易,白嫖可耻

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值