减少锁持有时间
只在必要时进行同步,能明显减少线程持有锁的时间,提高系统的吞吐量
减少锁粒度
如ConcurrentHashMap,它内部细分若干个小的hashMap,成为段,默认16段,如果增加一个新的表项,并不将整个hashmap加锁,首先根据hashcode得到该表项应该存放到哪个段中,然后对该段加锁,完成put,多个线程同时put,只有被加入的表项不存放在同一个段中,实现真正的并行
系统需要全局锁时,消耗的资源较多,如size方法,先使用无锁方式求和,失败则对所有段加锁,统计总数,释放锁
减少锁粒度,就是指缩小锁定对象的范围,从而减少锁冲突的可能性
读写分离锁替换独占锁
索分离
如LinkedBlockingQueue,take和put函数分别实现了从队列中取得数据和往队列中增加数据的可能,分别作用于队列的前端和尾端,理论上不冲突,使用独占锁,会彼此等待,因此使用俩把锁,分离了take和put
/** Lock held by take, poll, etc */
private final ReentrantLock takeLock = new ReentrantLock();
/** Wait queue for waiting takes */
private final Condition notEmpty = takeLock.newCondition();
/** Lock held by put, offer, etc */
private final ReentrantLock putLock = new ReentrantLock();
/** Wait queue for waiting puts */
private final Condition notFull = putLock.newCondition();
take方法
public E take() throws InterruptedException {
E x;
int c = -1;
final AtomicInteger count = this.count;
final ReentrantLock takeLock = this.takeLock;
takeLock.lockInterruptibly();//不能有俩个线程同时取得数据
try {
while (count.get() == 0) {//当前没有可用数据,一直等待
notEmpty.await();//等待put操作通知
}
x = dequeue();
c = count.getAndDecrement();//数量减一,原子操作,因为会和put同时访问count,c是count-1前的值
if (c > 1)
notEmpty.signal();//通知其他take操作
} finally {
takeLock.unlock();//释放锁
}
if (c == capacity)
signalNotFull();//通知put操作,已有空余空间
return x;
}
public void put(E e) throws InterruptedException {
if (e == null) throw new NullPointerException();
int c = -1;
Node<E> node = new Node<E>(e);
final ReentrantLock putLock = this.putLock;
final AtomicInteger count = this.count;
putLock.lockInterruptibly(); //不能同时有俩个线程同时put
try {
while (count.get() == capacity) { //如果队列已满
notFull.await(); //等待
}
enqueue(node);
c = count.getAndIncrement();//更新总数,变量c是count加1前的值
if (c + 1 < capacity)//有足够的空间,通知其他线程
notFull.signal();
} finally {
putLock.unlock();//释放锁
}
if (c == 0)
signalNotEmpty();//插入成功后,通知take取数据
}
锁粗化
请求释放锁会消耗资源,一连串不断对同一个锁进行请求释放操作,虚拟机会把所有操作整合成对锁的一次请求,减少对锁的请求同步次数,即锁的粗化
public void demoMethod(){
synchronized (lock) {
}
//不同步的操作,很快
synchronized (lock) {
}
}
会变为
public void demoMethod(){
synchronized (lock) {
//不同步的操作,很快
}
}
循环内取得锁时改为循环外加个锁
java虚拟机的锁优化
锁偏向
如果一个线程获得锁,锁进入偏向模式,这个线程再次请求锁,无需做任何同步操作,节省大量锁请求操作,提高性能,多个线程竞争锁时,不如不用锁偏向
轻量级锁
偏向锁失败,虚拟机不会立即挂起线程,会使用轻量级锁,简单地将对象头部作为指针,指向持有锁的线程堆栈内部,来判断线程是否持有对象锁,有,则顺利进入临界区,失败,则是其他线程抢先占了锁,当前线程的锁请求就会膨胀为重量级锁
自旋锁
锁膨胀后,虚拟机为了避免线程真实的在操作系统层面挂起,会使用自旋锁,让当前线程做几个空循环,若干次循环后,可以得到锁,顺利进入临界区,否则挂起线程
锁消除
虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁
public String[]createStrings(){
Vector<String> v = new Vector<>();
for(int i=0;i<100;i++){
v.add(Integer.toString(i));
}
return v.toArray(new String[]{});
}
v只在createStrings中使用,代表它是局部变量,局部变量在线程栈上分配的,属于线程私有的数据,不可能被其他线程访问,所以Vector不需要锁
锁消除涉及到逃逸分析技术,即某个变量是否会逃出某个作用域。如果这个方法返回的是v,则变量v逃逸出了当前函数,v可能被其他线程访问,虚拟机就不能消除v中的锁操作
ThreadLocal
它是一个线程的局部变量,只有当前线程可以访问
public class ThreadLocalDemo {
//SimpleDateFormat不是线程安全的,共享此对象必然报错
private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static class ParseDate implements Runnable{
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run(){
try {
Date t = sdf.parse("2017-07-27 22:01:"+i%60);
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0;i<1000;i++){
es.execute(new ParseDate(i));
}
}
}
}
解决办法,在sdf.parse前后加锁
或者
public class ThreadLocalDemo {
private static ThreadLocal<SimpleDateFormat> t = new ThreadLocal<>();//为每个线程产生一个SimpleDateFormat对象实例
public static class ParseDate implements Runnable{
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run(){
try {
if(t.get()==null){
t.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
}
Date t1 = t.get().parse("2017-07-27 22:01:"+i%60);
System.out.println(i+" "+t1);
} catch (ParseException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0;i<1000;i++){
es.execute(new ParseDate(i));
}
}
}
}
为每一个线程分配不同的对象,需要在应用层面保证,ThreadLocal只是起到简单的容器作用
ThreadLocal实现原理
Thread类中定义
ThreadLocal.ThreadLocalMap threadLocals = null;
public void set(T value) {
//首先获取当前线程对象
Thread t = Thread.currentThread();
//获取线程的ThreadLocalMap,放入ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
ThreadLocalMap可以简单的理解为HashMap,定义在Thread内部的成员,设置到ThreadLocal的数据,保存到这个Map中,key为ThreadLocal当前对象,value就是所要的值,threadLocals本身保存了当前自己所在线程的所有局部变量,即一个ThreadLocal变量的集合
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
这些变量维护在Thread类内部的ThreadLocalMap定义的类,只要线程不退出,对象的引用将一直存在
线程退出,Thread类会进行一些清理工作
使用线程池,可能不会退出线程,ThreadLocal不清理,对象无法被回收,要即时回收,使用ThreadLocal.remove()移除变量,也可以将变量设为null
public class ThreadLocalDemo_Gc {
static volatile ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<>();
@Override
protected void finalize() throws Throwable{
System.out.println(this.toString()+" is gc");
}
static volatile CountDownLatch cd = new CountDownLatch(100);
public static class ParseDate implements Runnable{
int i = 0;
public ParseDate(int i) {
this.i = i;
}
@Override
public void run(){
try {
if(t1.get()==null){
t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"){
@Override
protected void finalize() throws Throwable{
System.out.println(this.toString()+" is gc");
}
});
System.out.println(Thread.currentThread().getId()+":create SimpleDateFormat");
}
Date t = t1.get().parse("2017-07-27 22:01:"+i%60);
System.out.println(i+" "+t1);
} catch (ParseException e) {
e.printStackTrace();
}finally {
cd.countDown();
}
}
}
public static void main(String[] args) throws InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(10);
for(int i=0;i<1000;i++){
es.execute(new ParseDate(i));
}
cd.await();
System.out.println("mission complete!!");
t1=null;
System.gc();
System.out.println("first gc complete!!");
//设置ThreadLocal时,会清除ThreadLocalMap中的无效对象
t1 = new ThreadLocal<>();
cd = new CountDownLatch(100);
for(int i=0;i<1000;i++){
es.execute(new ParseDate(i));
}
cd.await();
Thread.sleep(1000);
System.gc();
System.out.println("second gc complete!!");
}
}
Thread.ThreadLocalMap
ThreadLocalMap类似WeakHashMap
ThreadLocalMap实现了弱引用,垃圾回收时,发现弱引用,会立即回收
static class Entry extends WeakReference<ThreadLocal<?>> {
/** The value associated with this ThreadLocal. */
Object value;
Entry(ThreadLocal<?> k, Object v) {
super(k);
value = v;
}
}
k是Map的key,value是Map的value,k也是ThreadLocal的实例,当ThreadLocal外部强制回收,k会变为null,扫描到就会清理ThreadLocalMap
ThreadLocal性能帮助
共享对象对于竞争的处理容易引起性能损伤,就考虑使用ThreadLocal为线程分配分配单独的对象
public class ThreadLocalRand {
//每个线程产生的随机数数量
public static final int GEN_COUNT=10000000;
//参与工作的线程数量
public static final int THREAD_COUNT =4;
//线程池
static ExecutorService exe = Executors.newFixedThreadPool(THREAD_COUNT);
//被多线程共享的实例产生随机数
public static Random rnd = new Random(123);
//ThreadLocal封装Random
public static ThreadLocal<Random> tRnd = new ThreadLocal<Random>(){
@Override
protected Random initialValue(){
return new Random(123);
}
};
public static class RndTask implements Callable<Long>{
private int mode =0;
public RndTask(int mode) {
this.mode = mode;
}
public Random getRandom(){
if(mode == 0){
return rnd;
}else if(mode == 1){
return tRnd.get();
}else{
return null;
}
}
//线程的工作内容
@Override
public Long call() throws Exception {
long b= System.currentTimeMillis();
for(long i=0;i<GEN_COUNT;i++){
getRandom().nextInt();
}
long e = System.currentTimeMillis();
System.out.println(Thread.currentThread().getName()+" spend "+(e-b)+"ms");
return e-b;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
Future<Long>[] futs = new Future[THREAD_COUNT];
for(int i=0;i<THREAD_COUNT;i++){
futs[i] = exe.submit(new RndTask(0));
}
long totaltime=0;
for(int i=0;i<THREAD_COUNT;i++){
totaltime+=futs[i].get();
}
System.out.println("多线程访问同一个Random实例:"+totaltime+"ms");
//ThreadLocal的情况
for(int i=0;i<THREAD_COUNT;i++){
futs[i] = exe.submit(new RndTask(1));
}
totaltime=0;
for(int i=0;i<THREAD_COUNT;i++){
totaltime+=futs[i].get();
}
System.out.println("使用ThreadLocal包装Random实例:"+totaltime+"ms");
exe.shutdown();
}
}
无锁
无锁的策略是使用比较交换的技术来鉴别线程冲突,一但检测到冲突,重试当前操作直到没有冲突
比较交换
非阻塞性,对死锁问题免疫,它包含三个参数CAS(V,E,N)。V表示要更新的变量,E表示预期值,N 表示新值,仅当V值等于E值时,才会将V值设为N,如果V值和E值不同,则说明已经有其他线程做了更新,则当前线程什么都不做,最后,CAS返回当前V的真实值,当多个线程同时使用CAS操作一个变量时,只有一个会胜出,并成功更新,其余均失败,失败的线程不会被挂起,仅被告知失败,允许再次尝试,也允许放弃操作。CAS需要额外给出一个期望值,即认为这个变量现在应该是什么样子的,如果不是,表明是被修改过了,就重新读取,再次尝试修改
无锁的选错安全整数:AtomicInteger
AtomicInteger也是一个整数,但可变,并且线程安全,对其进行修改等任何操作,都是CAS指令进行的
public final int get()//取得当前值
public final void set(int newValue)//设置当前值
public final int getAndSet(int newValue)//设置新值,并返回旧值
public final boolean compareAndSet(int expect, int update)//如果当前值为expect,则设置为U
public final int getAndIncrement()//当前值加1,返回旧值
public final int getAndDecrement()//当前值减1,返回旧值
public final int getAndAdd(int delta)//当前值增加delta,返回旧值
public final int incrementAndGet()//当前值加1,返回新值
public final int decrementAndGet()//当前值减1,返回新值
public final int addAndGet(int delta)//当前值减增加delta,返回新值
核心字段
代表AtomicInteger当前实际取值
private volatile int value;
private static final long valueOffset;
保存着value字段在这个对象中的偏移量
public final int incrementAndGet() {
return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
}
java中的指针:unsafe类 public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
public final native boolean compareAndSwapInt(Object O,long offset,int expected,int x)
没找到上述方法,O为给定对象,offset为对象内的偏移量(一个字段到对象头部的偏移量,通过这个偏移量可以快速定位字段),expected表示期望值,x表示要设置的值,如果指定字段的值等于expected,就会把它设置为x
无锁的对象引用:AtomicReference
保证修改对象引用时的安全性
线程判断被修改对象是否可以正确写入的条件是对象的当前值和期望值是否一致,一般是正确的,但如果获得当前数据,准备修改时,对象的值被其他线程修改了俩次,恢复为旧值,当前线程就无法判断这个对象是否被修改过
一般这种情况可以接受,只要改回原值就行,但是修改对象的值不仅取决于当前值,还和对象的过程变化相关,AtomicReference就不行了
丢失状态信息,对象值本身与状态画上等号
public class AtomicReferenceDemo {
public static void main(String[] args) {
AtomicReference<Integer> money = new AtomicReference<Integer>();
money.set(19);
//模拟多个线程同时充值
for (int i = 0; i < 3; i++) {
new Thread() {
@Override
public void run() {
while (true) {
Integer m = money.get();
if (m < 20) {
if (money.compareAndSet(m, m + 20)) {
System.out.println("余额小于20,充值成功,余额:" + money.get() + "元");
break;
}
} else {
System.out.println("余额大于20");
break;
}
}
}
}.start();
}
//模拟消费线程
new Thread() {
@Override
public void run() {
for (int i = 0; i < 4; i++) {
while (true) {
Integer m = money.get();
if (m > 10) {
System.out.println("余额大于10");
if (money.compareAndSet(m, m - 10)) {
System.out.println("消费10元,余额:" + money.get() + "元");
break;
}
} else {
System.out.println("金额不够");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
}
余额大于20
余额小于20,充值成功,余额:39元
余额大于20
余额大于10
消费10元,余额:29元
余额大于10
消费10元,余额:19元
余额大于10
没遇到多次充值情况
带有时间戳的对象引用:AtomicStampedReference
内部维护了对象值和时间戳,对应的数值被改变,除更新数据本身,还更新时间戳
//比较设置,参数为期望值,写入新值,期望时间戳,新时间戳
public boolean compareAndSet(V expectedReference,
V newReference,
int expectedStamp,
int newStamp)
public V getReference()//获取当前对象引用
public int getStamp()//获取当前时间戳
public void set(V newReference, int newStamp)//设置当前对象引用和时间戳
public class AtomicReferenceDemo {
//static AtomicReference<Integer> money = new AtomicReference<Integer>();
static AtomicStampedReference<Integer> money = new AtomicStampedReference<Integer>(19, 0);
public static void main(String[] args) {
//模拟多个线程同时充值
for (int i = 0; i < 3; i++) {
final int timestamp = money.getStamp();
new Thread() {
@Override
public void run() {
while (true) {
Integer m = money.getReference();
if (m < 20) {
if (money.compareAndSet(m, m + 20,timestamp,timestamp+1)) {
System.out.println("余额小于20,充值成功,余额:" + money.getReference() + "元");
break;
}
} else {
System.out.println("余额大于20");
break;
}
}
}
}.start();
}
//模拟消费线程
new Thread() {
@Override
public void run() {
for (int i = 0; i < 4; i++) {
final int timestamp = money.getStamp();
while (true) {
Integer m = money.getReference();
if (m > 10) {
System.out.println("余额大于10");
if (money.compareAndSet(m, m - 10,timestamp,timestamp+1)) {
System.out.println("消费10元,余额:" + money.getReference() + "元");
break;
}
} else {
System.out.println("金额不够");
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}.start();
}
}
余额大于20
余额小于20,充值成功,余额:39元
余额大于20
余额大于10
消费10元,余额:29元
余额大于10
消费10元,余额:19元
余额大于10
消费10元,余额:9元
金额不够
无锁数组:AtomicIntegerArray
原子数组,AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray,分别为整数数组,long型数组,对象数组
本质是对int[]封装,使用Unsafe类通过CAS方式控制int[]在多线程下的安全性
一些核心方法
public final int get(int i)//获取di第i个元素
public final int length()//获取长度
public final int getAndSet(int i, int newValue)//第i个坐标设为newValue,并返回旧值
public final boolean compareAndSet(int i, int expect, int update)//CAS操作,如果第i个下标的元素等于expect,则设置为update,返回true
public final int getAndIncrement(int i)//第i个下标的元素加1
public final int getAndDecrement(int i)//第i个下标的元素减1
public final int getAndAdd(int i, int delta)//第i个下标的元素增加delta
线程安全,下面值都为10000.不安全则部分或全部小于10000
public class AtomicIntegerArrayDemo {
//申明含有10个元素的数组
static AtomicIntegerArray arr = new AtomicIntegerArray(10);
public static class AddThread implements Runnable{
@Override
public void run(){
for(int i=0;i<10000;i++){
arr.getAndIncrement(i%arr.length());
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] td = new Thread[10];
for(int i=0;i<10;i++){
td[i] = new Thread(new AddThread());
}
for(int i=0;i<10;i++){
td[i].start();
}
for(int i=0;i<10;i++){
td[i].join();
}
System.out.println(arr);
}
}
[10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000, 10000]
普通变量为原子操作:AtomicIntegerFieldUpdater
可以在不改动或极少改动代码的情况下,让普通变量也使用CAS,AtomicIntegerFieldUpdater,AtomicLongFieldUpdater,AtomicReferenceFieldUpdater
public class AtomicIntegerFieldUpdaterDemo {
public static class Candidate{
int id;
volatile int score;
}
//用来对Candidate.score进行写入
public final static AtomicIntegerFieldUpdater<Candidate> scoreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score");
//检查Updater是否工作正确
public static AtomicInteger allScore = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
final Candidate stu = new Candidate();
Thread[] t = new Thread[10000];
for (int i = 0; i <10000; i++) {
t[i] = new Thread(){
@Override
public void run(){
if(Math.random()>0.4){
scoreUpdater.incrementAndGet(stu);
allScore.incrementAndGet();
}
}
};
t[i].start();
}
for (int i = 0; i < 10000; i++) {
t[i].join();
}
System.out.println("score="+stu.score);
System.out.println("allScore="+allScore);
}
}
Updater只能修改它可见范围内的变量,因为使用反射得到这个变量,变量不可见,就会报错
变量必须是volatile,为了确保被正确读取
CAS操作会通过对象实例中的偏移量直接进行赋值,不支持static关键字(Unsafe.objectFieldOffset()不支持静态变量)
无锁的Vector实现
SynchronousQueue的实现
容量为0,对它的写需要等待对它的读
abstract E transfer(E e, boolean timed, long nanos);
e非空表示当前操作传递给一个消费者,空表示当前操作需要请求一个数据,timed表示是否存在timeout时间,nanos决定了timeout时长,返回值非空,表示数据已经接受或者正常提供,为空,表示失败,超时或中断
内部会维护一个线程等待队列,等待队列中保存等待线程以及相关数据
transfer函数实现
for (;;) {
SNode h = head;//SNode表示等待队列中的节点,内部封装了当前线程,next节点,匹配节点,数据内容等信息
if (h == null || h.mode == mode) { // 队列为空或模式相同,将当前操作压入队列等待
if (timed && nanos <= 0) { // 不进行等待
if (h != null && h.isCancelled())
casHead(h, h.next); // 处理取消行为
else
return null;
} else if (casHead(h, s = snode(s, e, h, mode))) {//生成一个新的节点并置于队列头部,这个节点表示当前线程
SNode m = awaitFulfill(s, timed, nanos); // 等待直到有匹配操作出现
if (m == s) { //等待被取消
clean(s);
return null;
}
if ((h = head) != null && h.next == s)
casHead(h, s.next); // 帮助s的fulfiller,帮助对应的线程完成俩个头部节点的出队操作
return (E) ((mode == REQUEST) ? m.item : s.item);//返回读取或写入的数据
}
} else if (!isFulfilling(h.mode)) { // 判断头部节点是否处于fulfill状态,等待中的元素和本次操作是互补的,就插入一个完成状态的节点,并匹配到一个等待节点上,接着弹出这俩个节点,使对应的俩个线程继续执行
if (h.isCancelled()) // 如果以前取消了
casHead(h, h.next); // 弹出并重试
else if (casHead(h, s=snode(s, e, h, FULFILLING|mode))) {//生成一个SNode,设置为fulfill模式并压入队列头部
for (;;) { // 一直循环直到匹配或者没有等待者了
SNode m = s.next; // m是s的匹配者
if (m == null) { // 没有等待者
casHead(s, null); // 弹出fulfill节点
s = null; // 下次使用新节点
break; // 重新开始主循环
}
SNode mn = m.next;
if (m.tryMatch(s)) {
casHead(s, mn); // 弹出s和m
return (E) ((mode == REQUEST) ? m.item : s.item);
} else // match失败
s.casNext(m, mn); // 帮助删除节点
}
}
} else { // 等待队列的节点就是完成节点,这个节点完成任务,流程和步骤2一致
SNode m = h.next; // m 是h的match
if (m == null) // 没有等待者
casHead(h, null); // 弹出fulfiler节点
else {
SNode mn = m.next;
if (m.tryMatch(h)) // 尝试match
casHead(h, mn); // 弹出h 和 m
else
h.casNext(m, mn); // 帮助删除节点
}
}
}
}