JavaSE_5_线程
1、多线程中的i++线程安全吗?为什么?
不安全,因为每个线程都有自己的工作内存,每个线程需要对共享变量操作时必须把共享变量从主内存中加载到自己的工作内存,等完成操作后再保存到内存中,如果一个线程运算完成后还没刷新到主内存中,另一个线程又对这个共享变量进行操作,那么读取到的数据就是脏数据了。
2、如何线程安全的实现一个计数器?
可以使用Sychronized关键字对计数器方法加锁,或者直接使用支持原子性操作的类AtomicInteger。
3、多线程同步的方法
- 同步方法:即有synchronized关键字修饰的方法。 由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,内置锁会保护整个方法,在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
- 同步代码块:即有synchronized关键字修饰的语句块。被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
- 使用特殊域变量(volatile)实现线程同步:
(1) volatile关键字为域变量的访问提供了一种免锁机制;
(2) 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新;
(3) 因此每次使用该域就要重新计算,而不是使用寄存器中的值;
(4) volatile不会提供任何原子操作,它也不能用来修饰final类型的变量。
- 使用重入锁实现线程同步:在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。ReentrantLock类是可重入、互斥、实现了Lock接口的锁, 它与使用了synchronized的方法和块具有相同的基本行为和语义,并且扩展了其能力。ReenreantLock类的常用方法有,ReentrantLock() :创建一个ReentrantLock实例;lock() :获得锁;unlock() :释放锁。
- 本地线程存储实现线程同步(ThreadLocal):如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。
4、介绍一下生产者消费者模式?
5、线程,进程,然后线程创建有很大开销,怎么优化?
进程是程序运行和资源分配的基本单位,一个程序至少有一个进程,一个进程至少有一个线程;进程在执行过程中拥有独立的内存单元,而多个线程共享内存资源,减少切换次数,从而效率更高;线程是进程的一个实体,是cpu调度的基本单位;同一进程中的多个线程之间可以并发执行。
使用线程池可以避免频繁地创建和销毁线程产生的过大开销,以达到线程对象的重用,还可以根据项目灵活地控制并发数目。
6、线程池运行流程,参数,策略。
当提交一个新任务到线程池时,线程池的运行流程如下:
- 判断核心线程池(corePoolSize)是否已满,没满,则创建一个工作线程来执行任务,满了,则进入2流程;
- 判断工作队列(workQueue)是否已满,没满,则将新提交的任务存储在工作队列里,满了,则进入3流程;
- 判断整个线程池的线程数是否已超过maximumPoolSize,没满,则创建一个新的工作线程来执行任务,满了,则交给拒绝策略来处理这个任务。
线程池主要参数:
- corePoolSize:核心线程池数量(线程池维护线程的最少数量)
- maximumPoolSize:线程池维护线程的最大数量
- keepAliveTime:线程池维护线程所允许的空闲时间
- unit:线程池维护线程所允许的空闲时间的单位
- workQueue:线程池所使用的缓冲队列(工作队列)
- threadFactory:线程创建的工厂
- handler:线程池对拒绝任务的处理策略
线程池的拒绝策略:
- AbortPolicy:丢弃任务,并抛出异常;
- DiscardPolicy:不能执行的任务将被丢弃;
- DiscardOldestPolicy:如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试该执行程序;该策略稍微复杂一些,在pool没有关闭的前提下首先丢掉缓存在队列中的最早的任务,然后重新尝试运行该任务;
- CallerRunsPolicy:线程调用运行该任务的 execute 本身,这个策略不想放弃执行任务。但是由于池中已经没有任何资源了,那么就直接使用调用该execute的线程本身来执行。