1.如何让线程顺序执行:
1.thread.join 方法,可以让主线程等待子线程执行完之后,再执行。 jion方法内部是调的Object.wait()方法,即让调用该方法的线程等待,一直到子线程执行完。
2.Executor.newSingleThreadExecutors().submit(Thread)方法,也可以把线程挨着顺序submit,结果也是一样。这个newSingleThreadExecutors()内部维护了一个队列,将线程存储在一个队列里面,因此也可以达到顺序执行的效果。
2. synchronized和volitale 的区别?
简单点说,volatile 能保证共享变量的内存可见性已经有序性。synchronzied保证了可见性,原子性,有序性。
理解下这三个概念,可以看下 Java并发编程实践的第2、3章,java内存模型(JMM)参考 深入理解java虚拟机12章。
JMM决定一个线程对共享变量的写入何时对另外一个线程可见,它主要定义了java线程从内存里取变量和写变量到内存的操作。java线程是通过线程的工作内存来读写主内存里的变量,读取时从工作内存的变量副本取值,如果工作内存里没有变量副本就从主内存load进来,写入时写到工作内存的变量副本再同步回主内存。
volatile 能保证共享变量的内存可见性已经有序性。也就是说定义为volatile的变量,在写操作时,会立即把新值同步回主内存,在读取前立即从主内存刷新。因为volatile变量赋值操作之后,JVM为其加了一个lock指令,这个lock指令使得当前变量副本里的值立即写入主内存,同时会让别的线程的工作内存副本无效。而且这个指令相当于一个内存屏障,在指令重排序时不能把后面的指令排到内存屏障之前,也就是volatile变量写入内存前所有操作都执行完了,才会执行后面的操作。但是volatile不能保证复合操作的原子性,如读取 修改 写入 i++。
synchronized修饰的方法或者代码块,在字节码文件中会生成一个monitorenter {代码块}monitorexit的指令,只有拿到该对象锁的线程才能访问这个代码块,其他没有拿到锁的线程会被加入一个队列,等待,直到monitorexit指令执行,会通知其他线程取获取锁。也就是synchronized是通过同一时间只能有一个线程访问该代码块来保证可见性,原子性,有序性的。
3. Lock和synchronized的区别?
1.synchronized是一个关键字,是jvm内置锁;Lock是java接口,是用java代码实现的锁。
2.synchronized锁的释放是被动的:线程执行完同步代码块或者方法,JVM会释放锁;执行出现异常,JVM也会释放锁。 Lock可以主动释放锁,需要显示调用unlock()方法。
Lock接口有多个实现:
ReadWriteLock,读写锁,读操作用读锁,写操作用写锁,可以使得多个线程可以同时执行读操作。
ReentrantLock,可重入的,提供了以下三种功能:可中断锁,可以中断正在等待锁的线程;有条件的锁;公平、非公平锁。
ReentrantReadWriteLock
4. 线程的几种状态?
线程一共有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)
1.初始状态 new:t=new Thread()
2.可运行状态 runnabe:t.start(),(runnabe并不代码线程就在运行了,需要线程先获得CPU时间片,在执行程序代码,此时才会运行)
3.阻塞状态:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况
1.t.sleep(),t.jion进入阻塞状态,sleep状态超时或者t.jion子线程执行完,重新进入runnable状态。
2.o.wait(), JVM会把线程放进等待队列,wait时间结束或者被其他线程notify、notifyall,进入锁定,等到获取锁的线程重新进入runable状态
3.线程遇到同步代码块,进入锁定状态,等到获取锁的线程重新进入runable状态
sleep不会释放锁,wait会释放锁
4. TIME_WAITING:超时等待状态,超时以后自动返回
5.结束:线程执行完毕,或者线程异常
如何测试调了某个方法后,线程处于什么状态,用jstack打印堆栈信息,即可看到线程状态。
5. Thread.join和CountDonwLatch比较:
1. join方法必须等子线程执行完成,当前线程才能继续执行。
join方法内部是用一个while循环来不停检测join线程是否存活,存活就让主线程继续等待,直到join线程中止后,线程的this.notifyAll会被调用,主线程继续执行。
2. CountDownlatch是只要计数器变为0,调用await()的线程就就可以继续往下执行,不需要管其他线程是否执行完成,控制更灵活。
CountDownLatch用给定的计数初始化 CountDownLatch。调用countDown()方法,计数减1。在当前计数到达零之前,调用await()方法的线程会一直受阻塞。
当前计数到达0之后,会释放所有等待的线程。
CyclicBarrier是可以重置计数,CountDownLatch的计数只能初始化一次。
6.ThreadPoolExecutor(这个面试问的挺多)
1. 核心类是ThreadPoolExecutor, 是线程池的实现类,可以通过Executors工具类提供的静态方法创建不同的线程池。
主要参数:corePool(核心线程池大小),maxmumPool(最大线程池大小),BlockingQueue,空闲线程存活时间
2.Executors工具类
通过Executors工具类可以创建不同的线程池ThreadPoolExecutor:SingleThreadExecutor、FixedThreadPool和CachedThreadPool。两个关键问题:
如何选择使用哪种线程池??看业务,不过个人理解FixedThreadPool这个用的多,CachedThreadPool这个会无限制
的创建线程,如果任务一直提交且其他线程一直被占用,如果任务执行可能出现这种情况,就不能用CachedThreadPool。
如何设置线程池参数?以下是网上的一些经验之谈
计算密集型: 线程数 = CPU核数+1(jdk1.8以前) 线程数 = CPU内核线程数*2(jdk1.8)
IO密集型:线程数 = CPU核心数/(1-阻塞系数),阻塞系数一般
为0.8~0.9之间,也可以取0.8或者0.9,
套用公式,对于双核CPU来说,它比较理想的线程数就是20,当然这都不是绝对的,需要根据实际情况以及实际业务来调整
3.ThreadPoolExecutor执行顺序
当线程数小于核心线程数时,创建线程。
当线程数大于等于核心线程数,且任务队列未满时,将任务放入任务队列。
当线程数大于等于核心线程数,且任务队列已满
若线程数小于最大线程数,创建线程
若线程数等于最大线程数,抛出异常,拒绝任务
Executor,ReentrantLock、CountDownLatch等具体代码使用,可以看下Java编程思想第21章,有很多实例。
后续计划看下Java并发编程实战,再不定期更新。