多核的时代,所以面向多核编程很重要,所以java的并发跟多线程的开发就很重要。
1.线程池
WHY:复用线程,可以降低创建线程的开销,因为在线程执行结束后执行回收操作,不是真正去销毁线程。
本地环境测试,线程中执行很简单,创建线程的开销占整个时间的比例较大,100000次运算的时候,ThreadPoolExecutor:Thread执行时间为,88:6480(ms)
Java中还有定时的线程池,ScheduledThreadPoolExecutor。
2.synchronized
WHY:多线程环境中,thread1跟thread2并发执行时,可能会有不一致情况发生,比如把实例的state读到thread1的时候,在计算、返回到实例内存之前,thread2从实例内存读取了state,这样thread2返回的值就会覆盖thread1返回的值。单线程中不存在这种情况。
可以声明在方法或者代码块,实现互斥和可见性。
细节跟远离,
1)静态方法
public class X{
public synchronized static method1(){}
public synchronized static method2(){}
}
class X级别(包含所有X的实例)method1和method2是互斥的。
2)成员变量方法
public class Y{
public synchronized void method1(){}
public synchronized void method2(){}
}
多线程中,对class Y的同一个实例y而言,y.method1和y.method2是互斥的,thread1 call y.method1跟thread2 call y.method2会互斥。
3)代码块
synchronized (this) block1,跟其他synchronized成员方法是互斥的。
synchronized (Z.class) block1,跟类中synchronized静态方法是互斥的。
3.ReentrantLock
从jdk1.5开始引入,java.util.concurrent.lock,需要显式调用unlock方法,特点
1)tryLock方法可以立刻返回是否取得lock结果false/true,
2)构造ReentrantLock对象的时候,参数可以设置是否公平锁,公平锁严格按照请求的顺序来排队获取锁,非公平锁可以抢占。
4.volatile
只能加在变量前,保证变量在线程间的可见性,但不保证互斥,多线程中可能会发生该变量的状态不一致的情况。
在JVM中该变量不会有线程的本地副本,只会放在主存中,而synchronized保证线程的本地副本与主存的同步,在synchronized写完后(本地副本同步给主存),还需要synchronized读(从主存读取并更新本地副本)。
5.Atomics
从jdk1.5开始引入,java.util.concurrent.atomic包,Atomic+CLASS,提供原子操作。
跟synchronized相比,代码变简洁,性能明显提升。内部通过Java Native Interface的方式使用了硬件支持的CompareAndSwap指令。
细节跟原理,
6.wait、notify和notifyAll
都是Java的Object对象的方法,来实现线程间的状态通信。这三个方法的调用都必须在对象的synchronized块中。notifyAll会唤醒所有等待线程,notify只会唤醒一个等待线程。
7.CountDownLatch
java.util.concurrent包中的一个类,
当多个线程都达到了预期状态,其他线程(可以多个)可以等待这个事件来触发自己的后续工作。多个线程countDwon都执行完成时候,会触发其他线程从await方法返回,执行后面的代码。
初始化的count如果是1,效果就跟wait和notifyAll一样了。
8.CyclicBarrier
可以协同多个线程,让多个线程在这个屏障前等待,直到所有线程都到达了这个屏障的时候,再一起继续执行后面的动作。比如,thread1 call barrier.await方法的时候就会进入阻塞等待状态,直到其他thread也都执行到barrier.await方法到的时候,才会从await返回,继续执行后面的代码。
9.Exchanger
两个线程进行数据交换。线程会阻塞再exchanger1.exchange方法上,直到另一个线程也执行到exchanger1.exchange方法时,二者进行交换。
10.Semaphore
主要用来控制并发数。acquire/release来获取/释放信号,可加个数。
用于管理信号量,构造时传入信号量的数值,每次acquire成功,可用信号量减1,如果没有可用信号量,acquire调用就会阻塞,等待有release调用释放信号后,才能得到信号并返回。
如果管理的信号量为1,就跟互斥锁一个效果了。
11.并发容器
线程安全的容器,不仅线程安全,还要考虑并发性,尽量不用锁,而加锁互斥的就时串行了,降低了并发性。
代表的比如CopyOnWrite和Concurrent开头的几个容器。CopyOnWrite在写的时候重建一次容器,适用与读多写少的场景。Concurrent的原理则不完全相同。
使用 CAS 的非阻塞算法
public class NonblockingCounter {
private AtomicInteger value;
public int getValue() {
return value.get();
}
public int increment() {
int v;
do {
v = value.get();
while (!value.compareAndSet(v, v + 1));
return v + 1;
}
}
compareAndSet()
AtomicInteger
compareAndSet()
NonblockingCounter.increment()