史上最详细Java多线程面试题及答案!

synchronized实现原理

当一个线程试图访问同步代码块时,他首先必须获得锁,退出或抛出异常时必须释放锁。那么锁到底存在哪里呢?锁里面会存储什么信息呢?java

从JVM规范中能够看到Synchronized在JVM里的实现原理,JVM基于进入和退出Monitor对象来实现方法同步和代码块同步,但二者的实现细节不同。代码块同步是使用monitorenter和monitorexit指令实现的,而方法同步是使用另一种方式实现的,细节在JVM规范里并无详细说明。可是,方法的同步一样可使用这两个指令来实现。程序员

monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每一个monitorenter必须有对应的monitorexit与之配对。任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态。线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor的全部权,即尝试得到对象的锁。面试

i++ 是线程安全的吗?

不是 每一个线程都有本身的工做内存,每一个线程须要对共享变量操做时必须先把共享变量从主内存 load 到本身的工做内存,等完成对共享变量的操做时再 save 到主内存。算法

问题就出在这了,若是一个线程运算完后还没刷到主内存,此时这个共享变量的值被另一个线程从主内存读取到了,这个时候读取的数据就是脏数据了,它会覆盖其余线程计算完的值。。。编程

这也是经典的内存不可见问题,那么把 count 加上 volatile 让内存可见是否能解决这个问题呢? 答案是:不能。由于 volatile 只能保证可见性,不能保证原子性。多个线程同时读取这个共享变量的值,就算保证其余线程修改的可见性,也不能保证线程之间读取到一样的值而后相互覆盖对方的值的状况。缓存

解决方案 说了这么多,对于 i++ 这种线程不安全问题有没有其余解决方案呢?固然有,请参考如下几种解决方案。安全

一、对 i++ 操做的方法加同步锁,同时只能有一个线程执行 i++ 操做;服务器

二、使用支持原子性操做的类,如 java.util.concurrent.atomic.AtomicInteger,它使用的是 CAS 算法,效率优于第 1 种;多线程

多线程有什么用?

1)发挥多核CPU的优点架构

随着工业的进步,如今的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都很多见,若是是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工做,多线程,能够真正发挥出多核CPU的优点来,达到充分利用CPU的目的。

2)防止阻塞

从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优点,反而会由于在单核CPU上运行多线程致使线程上下文的切换,而下降程序总体的效率。可是单核CPU咱们仍是要应用多线程,就是为了防止阻塞。试想,若是单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来以前就中止运行了。多线程能够防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。

3)便于建模

这是另一个没有这么明显的优势了。假设有一个大的任务A,单线程编程,那么就要考虑不少,创建整个程序模型比较麻烦。可是若是把这个大的任务A分解成几个小任务,任务B、任务C、任务D,分别创建程序模型,并经过多线程分别运行这几个任务,那就简单不少了。

建立线程的方式

实现多线程的3种方式

  1. 继承Thread类 看jdk源码能够发现,Thread类实际上是实现了Runnable接口的一个实例,继承Thread类后须要重写run方法并经过start方法启动线程。 继承Thread类耦合性太强了,由于java只能单继承,因此不利于扩展。

  2. 实现Runnable接口 经过实现Runnable接口并重写run方法,并把Runnable实例传给Thread对象,Thread的start方法调用run方法再经过调用Runnable实例的run方法启动线程。 因此若是一个类继承了另一个父类,此时要实现多线程就不能经过继承Thread的类实现。

  3. 实现Callable接口 经过实现Callable接口并重写call方法,并把Callable实例传给FutureTask对象,再把FutureTask对象传给Thread对象。它与Thread、Runnable最大的不一样是Callable能返回一个异步处理的结果Future对象并能抛出异常,而其余两种不能。

示例代码

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * Created by binzhang on 2019/3/23.
 */
public class TestThread {
    static class Thread1 extends Thread{
        @Override
        public void run() {
            System.out.println("Thread1 running...");
        }
    }

    static class Thread2 implements Runnable{
        @Override
        public void run() {
            System.out.println("Thread2 running...");
        }
    }

    static class Thread3<T> implements Callable<T>{
        @SuppressWarnings("unchecked")
        @Override
        public T call() throws Exception {
            System.out.println("Thread3 running...");
            return (T)new String("hello callable...");
        }
    }

    public static void main(String[] args) throws Exception{
        Thread1 t1 = new Thread1();
        Thread t2 = new Thread(new Thread2());

        FutureTask<String> ft = new FutureTask<>(new Thread3<>());
        Thread t3 = new Thread(ft);

        t1.start();
        t2.start();
        t3.start();

        System.out.println(ft.get());
    }
}

输出

Thread1 running...
Thread2 running...
Thread3 running...
hello callable...

start()方法和run()方法的区别

只有调用了start()方法,才会表现出多线程的特性,不一样线程的run()方法里面的代码交替执行。若是只是调用run()方法,那么代码仍是同步执行的,必须等待一个线程的run()方法里面的代码所有执行完毕以后,另一个线程才能够执行其run()方法里面的代码。

Runnable接口和Callable接口的区别

有点深的问题了,也看出一个Java程序员学习知识的广度。

Runnable接口中的run()方法的返回值是void,它作的事情只是纯粹地去执行run()方法中的代码而已;Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合能够用来获取异步执行的结果。

这实际上是颇有用的一个特性,由于多线程相比单线程更难、更复杂的一个重要缘由就是由于多线程充满着未知性,某条线程是否执行了?某条线程执行了多久?某条线程执行的时候咱们指望的数据是否已经赋值完毕?没法得知,咱们能作的只是等待这条多线程的任务执行完毕而已。而Callable+Future/FutureTask却能够获取多线程运行的结果,能够在等待时间太长没获取到须要的数据的状况下取消该线程的任务,真的是很是有用。

CyclicBarrier和CountDownLatch的区别

两个看上去有点像的类,都在java.util.concurrent下,均可以用来表示代码运行到某个点上,两者的区别在于:

  1. CyclicBarrier的某个线程运行到某个点上以后,该线程即中止运行,直到全部的线程都到达了这个点,全部线程才从新运行;CountDownLatch则不是,某线程运行到某个点上以后,只是给某个数值-1而已,该线程继续运行。

  2. CyclicBarrier只能唤起一个任务,CountDownLatch能够唤起多个任务。

  3. CyclicBarrier可重用,CountDownLatch不可重用,计数值为0该CountDownLatch就不可再用了。

volatile关键字的做用

一个很是重要的问题,是每一个学习、应用多线程的Java程序员都必须掌握的。理解volatile关键字的做用的前提是要理解Java内存模型,这里就不讲Java内存模型了,能够参见第31点,volatile关键字的做用主要有两个:

1)多线程主要围绕可见性和原子性两个特性而展开,使用volatile关键字修饰的变量,保证了其在多线程之间的可见性,即每次读取到volatile变量,必定是最新的数据。

2)代码底层执行不像咱们看到的高级语言----Java程序这么简单,它的执行是Java代码-->字节码-->根据字节码执行对应的C/C++代码-->C/C++代码被编译成汇编语言-->和硬件电路交互,现实中,为了获取更好的性能JVM可能会对指令进行重排序,多线程下可能会出现一些意想不到的问题。使用volatile则会对禁止语义重排序,固然这也必定程度上下降了代码执行效率。

从实践角度而言,volatile的一个重要做用就是和CAS结合,保证了原子性,详细的能够参见java.util.concurrent.atomic包下的类,好比AtomicInteger,更多详情请点击这里进行学习。

什么是线程安全

又是一个理论的问题,各式各样的答案有不少,我给出一个我的认为解释地最好的:若是你的代码在多线程下执行和在单线程下执行永远都能得到同样的结果,那么你的代码就是线程安全的。

这个问题有值得一提的地方,就是线程安全也是有几个级别的:

1)不可变

像String、Integer、Long这些,都是final类型的类,任何一个线程都改变不了它们的值,要改变除非新建立一个,所以这些不可变对象不须要任何同步手段就能够直接在多线程环境下使用

2)绝对线程安全

无论运行时环境如何,调用者都不须要额外的同步措施。要作到这一点一般须要付出许多额外的代价,Java中标注本身是线程安全的类,实际上绝大多数都不是线程安全的,不过绝对线程安全的类,Java中也有,比方说CopyOnWriteArrayList、CopyOnWriteArraySet

3)相对线程安全

相对线程安全也就是咱们一般意义上所说的线程安全,像Vector这种,add、remove方法都是原子操做,不会被打断,但也仅限于此,若是有个线程在遍历某个Vector、有个线程同时在add这个Vector,99%的状况下都会出现ConcurrentModificationException,也就是fail-fast机制。

4)线程非安全

这个就没什么好说的了,ArrayList、LinkedList、HashMap等都是线程非安全的类,点击这里了解为何不安全。

Java中如何获取到线程dump文件

死循环、死锁、阻塞、页面打开慢等问题,打线程dump是最好的解决问题的途径。所谓线程dump也就是线程堆栈,获取到线程堆栈有两步:

  • 1)获取到线程的pid,能够经过使用jps命令,在Linux环境下还可使用ps -ef | grep java

  • 2)打印线程堆栈,能够经过使用jstack pid命令,在Linux环境下还可使用kill -3 pid

另外提一点,Thread类提供了一个getStackTrace()方法也能够用于获取线程堆栈。这是一个实例方法,所以此方法是和具体线程实例绑定的,每次获取获取到的是具体某个线程当前运行的堆栈。

一个线程若是出现了运行时异常会怎么样

若是这个异常没有被捕获的话,这个线程就中止执行了。另外重要的一点是:若是这个线程持有某个某个对象的监视器,那么这个对象监视器会被当即释放

如何在两个线程之间共享数据

经过在线程之间共享对象就能够了,而后经过wait/notify/notifyAll、await/signal/signalAll进行唤起和等待,比方说阻塞队列BlockingQueue就是为线程之间共享数据而设计的

sleep方法和wait方法有什么区别

这个问题常问,sleep方法和wait方法均可以用来放弃CPU必定的时间,不一样点在于若是线程持有某个对象的监视器,sleep方法不会放弃这个对象的监视器,wait方法会放弃这个对象的监视器。

生产者消费者模型的做用是什么

这个问题很理论,可是很重要:

1)经过平衡生产者的生产能力和消费者的消费能力来提高整个系统的运行效率,这是生产者消费者模型最重要的做用

2)解耦,这是生产者消费者模型附带的做用,解耦意味着生产者和消费者之间的联系少,联系越少越能够独自发展而不须要收到相互的制约

ThreadLocal有什么用

简单说ThreadLocal就是一种以空间换时间的作法,在每一个Thread里面维护了一个以开地址法实现的ThreadLocal.ThreadLocalMap,把数据进行隔离,数据不共享,天然就没有线程安全方面的问题了

为何wait()方法和notify()/notifyAll()方法要在同步块中被调用

这是JDK强制的,wait()方法和notify()/notifyAll()方法在调用前都必须先得到对象的锁

wait()方法和notify()/notifyAll()方法在放弃对象监视器时有什么区别

wait()方法和notify()/notifyAll()方法在放弃对象监视器的时候的区别在于:wait()方法当即释放对象监视器,notify()/notifyAll()方法则会等待线程剩余代码执行完毕才会放弃对象监视器。

为何要使用线程池

避免频繁地建立和销毁线程,达到线程对象的重用。另外,使用线程池还能够根据项目灵活地控制并发的数目。点击这里学习线程池详解。

怎么检测一个线程是否持有对象监视器

我也是在网上看到一道多线程面试题才知道有方法能够判断某个线程是否持有对象监视器:Thread类提供了一个holdsLock(Object obj)方法,当且仅当对象obj的监视器被某条线程持有的时候才会返回true,注意这是一个static方法,这意味着"某条线程"指的是当前线程。

synchronized和ReentrantLock的区别

synchronized是和if、else、for、while同样的关键字,ReentrantLock是类,这是两者的本质区别。既然ReentrantLock是类,那么它就提供了比synchronized更多更灵活的特性,能够被继承、能够有方法、能够有各类各样的类变量,ReentrantLock比synchronized的扩展性体如今几点上:

(1)ReentrantLock能够对获取锁的等待时间进行设置,这样就避免了死锁

(2)ReentrantLock能够获取各类锁的信息

(3)ReentrantLock能够灵活地实现多路通知

另外,两者的锁机制其实也是不同的。ReentrantLock底层调用的是Unsafe的park方法加锁,synchronized操做的应该是对象头中mark word,这点我不能肯定。

ConcurrentHashMap的并发度是什么

ConcurrentHashMap的并发度就是segment的大小,默认为16,这意味着最多同时能够有16条线程操做ConcurrentHashMap,这也是ConcurrentHashMap对Hashtable的最大优点,任何状况下,Hashtable能同时有两条线程获取Hashtable中的数据吗?

ReadWriteLock是什么

首先明确一下,不是说ReentrantLock很差,只是ReentrantLock某些时候有局限。若是使用ReentrantLock,可能自己是为了防止线程A在写数据、线程B在读数据形成的数据不一致,但这样,若是线程C在读数据、线程D也在读数据,读数据是不会改变数据的,没有必要加锁,可是仍是加锁了,下降了程序的性能。

由于这个,才诞生了读写锁ReadWriteLock。ReadWriteLock是一个读写锁接口,ReentrantReadWriteLock是ReadWriteLock接口的一个具体实现,实现了读写的分离,读锁是共享的,写锁是独占的,读和读之间不会互斥,读和写、写和读、写和写之间才会互斥,提高了读写的性能。

FutureTask是什么

这个其实前面有提到过,FutureTask表示一个异步运算的任务。FutureTask里面能够传入一个Callable的具体实现类,能够对这个异步运算的任务的结果进行等待获取、判断是否已经完成、取消任务等操做。固然,因为FutureTask也是Runnable接口的实现类,因此FutureTask也能够放入线程池中。

Linux环境下如何查找哪一个线程使用CPU最长

这是一个比较偏实践的问题,这种问题我以为挺有意义的。能够这么作:

(1)获取项目的pid,jps或者ps -ef | grep java,这个前面有讲过

(2)top -H -p pid,顺序不能改变

这样就能够打印出当前的项目,每条线程占用CPU时间的百分比。注意这里打出的是LWP,也就是操做系统原生线程的线程号,我笔记本山没有部署Linux环境下的Java工程,所以没有办法截图演示,网友朋友们若是公司是使用Linux环境部署项目的话,能够尝试一下。

使用"top -H -p pid"+"jps pid"能够很容易地找到某条占用CPU高的线程的线程堆栈,从而定位占用CPU高的缘由,通常是由于不当的代码操做致使了死循环。

最后提一点,"top -H -p pid"打出来的LWP是十进制的,"jps pid"打出来的本地线程号是十六进制的,转换一下,就能定位到占用CPU高的线程的当前线程堆栈了。

Java编程写一个会致使死锁的程序

第一次看到这个题目,以为这是一个很是好的问题。不少人都知道死锁是怎么一回事儿:线程A和线程B相互等待对方持有的锁致使程序无限死循环下去。固然也仅限于此了,问一下怎么写一个死锁的程序就不知道了,这种状况说白了就是不懂什么是死锁,懂一个理论就完事儿了,实践中碰到死锁的问题基本上是看不出来的。

真正理解什么是死锁,这个问题其实不难,几个步骤:

1)两个线程里面分别持有两个Object对象:lock1和lock2。这两个lock做为同步代码块的锁;

2)线程1的run()方法中同步代码块先获取lock1的对象锁,Thread.sleep(xxx),时间不须要太多,50毫秒差很少了,而后接着获取lock2的对象锁。这么作主要是为了防止线程1启动一会儿就连续得到了lock1和lock2两个对象的对象锁

3)线程2的run)(方法中同步代码块先获取lock2的对象锁,接着获取lock1的对象锁,固然这时lock1的对象锁已经被线程1锁持有,线程2确定是要等待线程1释放lock1的对象锁的

这样,线程1"睡觉"睡完,线程2已经获取了lock2的对象锁了,线程1此时尝试获取lock2的对象锁,便被阻塞,此时一个死锁就造成了。代码就不写了,占的篇幅有点多,Java多线程7:死锁这篇文章里面有,就是上面步骤的代码实现。

点击这里提供了一个死锁的案例。

怎么唤醒一个阻塞的线程

若是线程是由于调用了wait()、sleep()或者join()方法而致使的阻塞,能够中断线程,而且经过抛出InterruptedException来唤醒它;若是线程遇到了IO阻塞,无能为力,由于IO是操做系统实现的,Java代码并无办法直接接触到操做系统。

不可变对象对多线程有什么帮助

前面有提到过的一个问题,不可变对象保证了对象的内存可见性,对不可变对象的读取不须要进行额外的同步手段,提高了代码执行效率。

什么是多线程的上下文切换

多线程的上下文切换是指CPU控制权由一个已经正在运行的线程切换到另一个就绪并等待获取CPU执行权的线程的过程。

若是你提交任务时,线程池队列已满,这时会发生什么

这里区分一下:

1)若是使用的是无界队列LinkedBlockingQueue,也就是无界队列的话,不要紧,继续添加任务到阻塞队列中等待执行,由于LinkedBlockingQueue能够近乎认为是一个无穷大的队列,能够无限存听任务

2)若是使用的是有界队列好比ArrayBlockingQueue,任务首先会被添加到ArrayBlockingQueue中,ArrayBlockingQueue满了,会根据maximumPoolSize的值增长线程数量,若是增长了线程数量仍是处理不过来,ArrayBlockingQueue继续满,那么则会使用拒绝策略RejectedExecutionHandler处理满了的任务,默认是AbortPolicy

Java中用到的线程调度算法是什么

抢占式。一个线程用完CPU以后,操做系统会根据线程优先级、线程饥饿状况等数据算出一个总的优先级并分配下一个时间片给某个线程执行。

Thread.sleep(0)的做用是什么

这个问题和上面那个问题是相关的,我就连在一块儿了。因为Java采用抢占式的线程调度算法,所以可能会出现某条线程经常获取到CPU控制权的状况,为了让某些优先级比较低的线程也能获取到CPU控制权,可使用Thread.sleep(0)手动触发一次操做系统分配时间片的操做,这也是平衡CPU控制权的一种操做。

什么是自旋

不少synchronized里面的代码只是一些很简单的代码,执行时间很是快,此时等待的线程都加锁多是一种不太值得的操做,由于线程阻塞涉及到用户态和内核态切换的问题。既然synchronized里面的代码执行得很是快,不妨让等待锁的线程不要被阻塞,而是在synchronized的边界作忙循环,这就是自旋。若是作了屡次忙循环发现尚未得到锁,再阻塞,这样多是一种更好的策略。

什么是Java内存模型

Java内存模型定义了一种多线程访问Java内存的规范。Java内存模型要完整讲不是这里几句话能说清楚的,我简单总结一下Java内存模型的几部份内容:

1)Java内存模型将内存分为了主内存和工做内存。类的状态,也就是类之间共享的变量,是存储在主内存中的,每次Java线程用到这些主内存中的变量的时候,会读一次主内存中的变量,并让这些内存在本身的工做内存中有一份拷贝,运行本身线程代码的时候,用到这些变量,操做的都是本身工做内存中的那一份。在线程代码执行完毕以后,会将最新的值更新到主内存中去

2)定义了几个原子操做,用于操做主内存和工做内存中的变量

3)定义了volatile变量的使用规则

4)happens-before,即先行发生原则,定义了操做A必然先行发生于操做B的一些规则,好比在同一个线程内控制流前面的代码必定先行发生于控制流后面的代码、一个释放锁unlock的动做必定先行发生于后面对于同一个锁进行锁定lock的动做等等,只要符合这些规则,则不须要额外作同步措施,若是某段代码不符合全部的happens-before规则,则这段代码必定是线程非安全的

什么是CAS

CAS,全称为Compare and Swap,即比较-替换。假设有三个操做数:内存值V、旧的预期值A、要修改的值B,当且仅当预期值A和内存值V相同时,才会将内存值修改成B并返回true,不然什么都不作并返回false。固然CAS必定要volatile变量配合,这样才能保证每次拿到的变量是主内存中最新的那个值,不然旧的预期值A对某条线程来讲,永远是一个不会变的值A,只要某次CAS操做失败,永远都不可能成功。更多CAS详情请点击这里学习。

什么是乐观锁和悲观锁

1)乐观锁:就像它的名字同样,对于并发间操做产生的线程安全问题持乐观状态,乐观锁认为竞争不老是会发生,所以它不须要持有锁,将比较-替换这两个动做做为一个原子操做尝试去修改内存中的变量,若是失败则表示发生冲突,那么就应该有相应的重试逻辑。

2)悲观锁:仍是像它的名字同样,对于并发间操做产生的线程安全问题持悲观状态,悲观锁认为竞争老是会发生,所以每次对某资源进行操做时,都会持有一个独占的锁,就像synchronized,无论三七二十一,直接上了锁就操做资源了。

点击这里了解更多乐观锁与悲观锁详情。

什么是AQS

简单说一下AQS,AQS全称为AbstractQueuedSychronizer,翻译过来应该是抽象队列同步器。

若是说java.util.concurrent的基础是CAS的话,那么AQS就是整个Java并发包的核心了,ReentrantLock、CountDownLatch、Semaphore等等都用到了它。AQS实际上以双向队列的形式链接全部的Entry,比方说ReentrantLock,全部等待的线程都被放在一个Entry中并连成双向队列,前面一个线程使用ReentrantLock好了,则双向队列实际上的第一个Entry开始运行。

AQS定义了对双向队列全部的操做,而只开放了tryLock和tryRelease方法给开发者使用,开发者能够根据本身的实现重写tryLock和tryRelease方法,以实现本身的并发功能。

单例模式的线程安全性

老生常谈的问题了,首先要说的是单例模式的线程安全意味着:某个类的实例在多线程环境下只会被建立一次出来。单例模式有不少种的写法,我总结一下:

1)饿汉式单例模式的写法:线程安全

2)懒汉式单例模式的写法:非线程安全

3)双检锁单例模式的写法:线程安全

Semaphore有什么做用

Semaphore就是一个信号量,它的做用是限制某段代码块的并发数。Semaphore有一个构造函数,能够传入一个int型整数n,表示某段代码最多只有n个线程能够访问,若是超出了n,那么请等待,等到某个线程执行完毕这段代码块,下一个线程再进入。由此能够看出若是Semaphore构造函数中传入的int型整数n=1,至关于变成了一个synchronized了。

Hashtable的size()方法中明明只有一条语句"return count",为何还要作同步?

这是我以前的一个困惑,不知道你们有没有想过这个问题。某个方法中若是有多条语句,而且都在操做同一个类变量,那么在多线程环境下不加锁,势必会引起线程安全问题,这很好理解,可是size()方法明明只有一条语句,为何还要加锁?

关于这个问题,在慢慢地工做、学习中,有了理解,主要缘由有两点:

1)同一时间只能有一条线程执行固定类的同步方法,可是对于类的非同步方法,能够多条线程同时访问。因此,这样就有问题了,可能线程A在执行Hashtable的put方法添加数据,线程B则能够正常调用size()方法读取Hashtable中当前元素的个数,那读取到的值可能不是最新的,可能线程A添加了完了数据,可是没有对size++,线程B就已经读取size了,那么对于线程B来讲读取到的size必定是不许确的。而给size()方法加了同步以后,意味着线程B调用size()方法只有在线程A调用put方法完毕以后才能够调用,这样就保证了线程安全性

2)CPU执行代码,执行的不是Java代码,这点很关键,必定得记住。Java代码最终是被翻译成机器码执行的,机器码才是真正能够和硬件电路交互的代码。即便你看到Java代码只有一行,甚至你看到Java代码编译以后生成的字节码也只有一行,也不意味着对于底层来讲这句语句的操做只有一个。一句"return count"假设被翻译成了三句汇编语句执行,一句汇编语句和其机器码作对应,彻底可能执行完第一句,线程就切换了。

线程类的构造方法、静态块是被哪一个线程调用的

这是一个很是刁钻和狡猾的问题。请记住:线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

若是说上面的说法让你感到困惑,那么我举个例子,假设Thread2中new了Thread1,main函数中new了Thread2,那么:

1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2本身调用的

2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1本身调用的

同步方法和同步块,哪一个是更好的选择

同步块,这意味着同步块以外的代码是异步执行的,这比同步整个方法更提高代码的效率。请知道一条原则:同步的范围越小越好。

借着这一条,我额外提一点,虽然说同步的范围越少越好,可是在Java虚拟机中仍是存在着一种叫作锁粗化的优化方法,这种方法就是把同步范围变大。这是有用的,比方说StringBuffer,它是一个线程安全的类,天然最经常使用的append()方法是一个同步方法,咱们写代码的时候会反复append字符串,这意味着要进行反复的加锁->解锁,这对性能不利,由于这意味着Java虚拟机在这条线程上要反复地在内核态和用户态之间进行切换,所以Java虚拟机会将屡次append方法调用的代码进行一个锁粗化的操做,将屡次的append的操做扩展到append方法的头尾,变成一个大的同步块,这样就减小了加锁–>解锁的次数,有效地提高了代码执行的效率。

高并发、任务执行时间短的业务怎样使用线程池?并发不高、任务执行时间长的业务怎样使用线程池?并发高、业务执行时间长的业务怎样使用线程池?

这是我在并发编程网上看到的一个问题,把这个问题放在最后一个,但愿每一个人都能看到而且思考一下,由于这个问题很是好、很是实际、很是专业。关于这个问题,我的见解是:

1)高并发、任务执行时间短的业务,线程池线程数能够设置为CPU核数+1,减小线程上下文的切换

2)并发不高、任务执行时间长的业务要区分开看:

a)假如是业务时间长集中在IO操做上,也就是IO密集型的任务,由于IO操做并不占用CPU,因此不要让全部的CPU闲下来,能够加大线程池中的线程数目,让CPU处理更多的业务

b)假如是业务时间长集中在计算操做上,也就是计算密集型任务,这个就没办法了,和(1)同样吧,线程池中的线程数设置得少一些,减小线程上下文的切换

c)并发高、业务执行时间长,解决这种类型任务的关键不在于线程池而在于总体架构的设计,看看这些业务里面某些数据是否能作缓存是第一步,增长服务器是第二步,至于线程池的设置,设置参考其余有关线程池的文章。最后,业务执行时间长的问题,也可能须要分析一下,看看能不能使用中间件对任务进行拆分和解耦。

ThreadLocal的使用场景

ThreadLocal 适用于每一个线程须要本身独立的实例且该实例须要在多个方法中被使用,也即变量在线程间隔离而在方法或类间共享的场景。