Java 并发编程
并发和并行
-
什么是并发
并发是指两个或多个事件在同一时间间隔发生,但是实际上处理的只能是其中的一个,但是可以交替去处理其他的。 -
什么是并行
两个或多个事件在同一时刻发生被处理。 -
并发和并行区别
一个是交替执行,一个是同时执行.
什么是线程,与进程的区别
@TODO
线程池
submit() 和 execute()
区别
- 参数不同
submit和execute由于参数不同有四种实现形式
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
void execute(Runnable command);
- submit有返回值,execute没有
submit在执行过程中不会抛出异常,而是把异常保存在成员变量中,在Future.get()阻塞获取到异常信息。
execute直接抛出异常之后线程就结束了,submit保存异常线程没有结束。
为什么不允许使用 Executors 创建线程池
线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。
-
Executors 返回的线程池对象的弊端
- FixedThreadPool 和 SingleThreadPool:允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。
- CachedThreadPool 和 ScheduledThreadPool:允许的创建线程数量为 Integer.MAX_VALUE, 可能会创建大量的线程,从而导致 OOM。
Positive example 1:
//org.apache.commons.lang3.concurrent.BasicThreadFactory
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("example-schedule-pool-%d").daemon(true).build());
Positive example 2:
ThreadFactory namedThreadFactory = new ThreadFactoryBuilder()
.setNameFormat("demo-pool-%d").build();
//Common Thread Pool
ExecutorService pool = new ThreadPoolExecutor(5, 200,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(1024), namedThreadFactory, new ThreadPoolExecutor.AbortPolicy());
pool.execute(()-> System.out.println(Thread.currentThread().getName()));
pool.shutdown();//gracefully shutdown
Positive example 3:
<bean id="userThreadPool"
class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor">
<property name="corePoolSize" value="10" />
<property name="maxPoolSize" value="100" />
<property name="queueCapacity" value="2000" />
<property name="threadFactory" value= threadFactory />
<property name="rejectedExecutionHandler">
<ref local="rejectedExecutionHandler" />
</property>
</bean>
//in code
userThreadPool.execute(thread);
线程安全
线程安全和内存模型的关系
- 内存模型
Java内存模型(JMM)包含:工作内存(本地内存)和主内存
工作内存:工作内存可以简单理解为计算机当中的CPU高速缓存,但又不完全等同。每一个线程拥有自己的工作内存,对于一个共享变量来说,工作内存当中存储了它的“副本”。
主内存:主内存可以简单理解为计算机当中的内存,但又不完全等同。主内存被所有的线程所共享,对于一个共享变量(比如静态变量,或是堆内存中的实例)来说,主内存当中存储了它的“本尊”。
- volatile
- volatile关键字具有许多特性,其中最重要的特性就是保证了用volatile修饰的变量对所有线程的可见性。当一个线程修改了变量的值,新的值会立刻同步到主内存当中。而其他线程读取这个变量的时候,也会从主内存中拉取最新的变量值。为什么volatile关键字可以有这样的特性?这得益于java语言的先行发生原则(happens-before)。
- 阻止编译时和运行时的指令重排。编译时JVM编译器遵循内存屏障的约束,运行时依靠CPU屏障指令来阻止重排。
锁
锁的类型
悲观锁和乐观锁
悲观锁
悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block直到拿到锁。Java中的悲观锁就是Synchronized, AQS框架下的锁则是先尝试cas乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。
乐观锁
乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
Java中的乐观锁基本都是通过CAS操作实现的,CAS(Compare And
Swap)是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
关于锁的详细介绍:Java中的偏向锁、轻量级锁与重量级锁(synchronized)
死锁
synchronized
volatile
sleep 和 wait
wait 和 notify
notify 和 notifyAll
ThreadLocal
用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立的改变自己的副本,而不会和其他的副本冲突。
ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本。
ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean、事务管理、任务调度、AOP等模块都出现了它的身影。
Spring中绝大部分Bean都可以声明成Singleton作用域,采用ThreadLocal进行封装,因此有状态的Bean就能够以singleton的方式在多线程中正常工作了。
ThreadLocal使用场合主要解决多线程中数据数据因并发产生不一致问题。ThreadLocal为每个线程的中并发访问的数据提供一个副本,通过访问副本来运行业务,这样的结果是耗费了内存,单大大减少了线程同步所带来性能消耗,也减少了线程并发控制的复杂度。
ThreadLocal不能使用原子类型,只能使用Object类型。ThreadLocal的使用比synchronized要简单得多。
ThreadLocal和Synchonized都用于解决多线程并发访问。但是ThreadLocal与synchronized有本质的区别。synchronized是利用锁的机制,使变量或代码块在某一时该只能被一个线程访问。而ThreadLocal为每一个线程都提供了变量的副本,使得每个线程在某一时间访问到的并不是同一个对象,这样就隔离了多个线程对数据的数据共享。而Synchronized却正好相反,它用于在多个线程间通信时能够获得数据共享。
Synchronized用于线程间的数据共享,而ThreadLocal则用于线程间的数据隔离。
当然ThreadLocal并不能替代synchronized,它们处理不同的问题域。Synchronized用于实现同步机制,比ThreadLocal更加复杂。