Java高并发------锁优化及注意事项--------4
锁优化及注意事项
4.1 有助于提高锁性能的几点建议
4.1.1 减少锁持有的时间
public synchronized void syncMethod(){ method1(); method2(); method3(); }
上述代码中假如只有method2需要加锁,那么就不用给整个方法加锁了。
4.1.2 减少锁粒度
java.util.concurrent.ConcurrentHashMap.put()
当每次向上述的线程安全HashMap中插入数据,其实可以不用锁住整个HashMap
步骤:
1.先查找数据key的哈希值
2.锁住该key对应的哈希桶
3.完成插入操作
总结:可以不用把整个对象全部锁住,锁住想要锁住的就行了。
4.1.3 用读写分离锁来替换独占锁
ReadWriteLock就是典型的代表,对系统功能点的分割
4.1.4 锁分离
跟上面一样的咯
4.1.5 锁粗化
如果修改一个Person对象的信息,修改名字锁一下、修改ID锁住set方法,那倒不如直接将Person对象锁住,加一把锁,就不用锁这个锁那个了
与上面的优化方法相反
4.2 Java虚拟机对锁优化所作的努力
4.2.1偏向锁
如果一个线程获得了锁,那么就进入倒偏向模式。当这个线程下次再次请求锁时,就不必申请了。
类似于:我认得你了,请进吧!
使用虚拟机参数开启偏向锁:-XX:+UseBiasedLocking
4.2.2 轻量级锁
不懂
4.2.3 自旋锁
不随随便便挂起某个线程,假如该线程在接下来的时间内将获得锁对象,那么就让它自己循环几下等一下嘛,如果几轮之后还是没有获得,那么就直接挂起吧。
4.2.4 锁消除
Java虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁。
4.3 ThreadLocal
除了控制资源的访问外,我们还可以通过增加资源来保证所有对象的线程安全!
例子
1 import java.text.ParseException; 2 import java.text.SimpleDateFormat; 3 import java.util.Date; 4 import java.util.concurrent.ExecutorService; 5 import java.util.concurrent.Executors; 6 7 public class ThreadLocalTest { 8 9 // private static final SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); 10 static ThreadLocal<SimpleDateFormat> t1 = new ThreadLocal<>(); 11 public static class ParseDate implements Runnable{ 12 13 int i = 0; 14 public ParseDate(int i){ 15 this.i = i; 16 } 17 18 @Override 19 public void run() { 20 try { 21 if (t1.get() == null){ 22 t1.set(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 23 } 24 Date t = t1.get().parse("2015-03-29 19:29:" + i%60); 25 System.out.println(i + ":" + t); 26 } catch (ParseException e) { 27 e.printStackTrace(); 28 } 29 } 30 } 31 32 public static void main(String[] args) { 33 34 ExecutorService es = Executors.newFixedThreadPool(10); 35 for (int i = 0; i < 1000; i++) { 36 es.execute(new ParseDate(i)); 37 } 38 } 39 }
假如使用行号9代码来替代10,会报错;sdf不是线程安全的对象
看22行:
证明:ThreadLocal底层存储为Map《线程,值》
看24行:
证明:key = 这个线程
线程退出时,会清除
但是线程池使用ThreadLocal有可能造成内存泄漏,因为线程不死,就不会清除啊!
解决方案:
所以在每次run方法的末尾,ThreadLocal.remove()
为每一个线程分配一个独立的对象对系统性能也许会有帮助,但是也不一定啦,看对象的结构
4.4 无锁
无锁策略:比较交换(CAS, Compare And Swap)
特点:
- 非阻塞性,所以死锁天生免疫
- 线程间的相互影响远远比锁的方式小
算法过程:
坦白说,就是我先拿到了一个值,然后,我打算修改这个值;修改的时候,我要看那个值还是不是我起初看到的那个值,如果是,那就直接改;如果不是,那我就重新来一遍,知道成功,当然也可以失败放弃!
官方解释:它包含了三个参数CAS(V,E,N),其中V表示要更新的变量,E表示预期值,N表示新值。当V值等于E值时,才会将V的值设为N,如果V值与E值不同,说明已经有其他线程做了更新,则当前线程什么都不做。
CAS的类都在:
java.util.concurrent.atomic
4.4.2无锁线程安全整数:AtomicInteger
一个可变的Integer,保证线程安全的。
方法:
public final int get() // 取得当前值 public final void set(int newValue) //设置当前值 public final int getAndSet(int newValue) //设置新值,并返回旧值 public final boolean compareAndSet(int expect, int u) //如果当前值为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() // 当前值增加delta,返回新值
核心字段:
private volatile int value;
private static final long valueOffset;
看看源码,怎么实现的吧
说句老实话,没看懂!但是可以看到循环比较,直到判断成功为止。偏移量是为了很快很快的定位此时的变量,快到没有人能在我读之前修改。但是如果说,有线程快得很,先改了一次,但是又改了回来,对于中间过程我们是无所谓的。
AtomicLong:
AtomicBoolean
AtomicReference
4.4.3 无锁的对象引用:AtomicReference
static AtomicReference<Integer> money = new AtomicReference<Integer>();
大多数情况下,这个money是安全的,但是,我们有时候会关注对象修改的中间过程,不能像上面的一样;
例如:
一家蛋糕店需要为每一个低于20元的会员送20元,但是在多线程修改的过程中,有一个家伙刚被冲上,就消费了;别的线程一看,再冲20;然后就造成了冲两次的现象。
所以,提出了下面的东西。
4.4.4 带有时间戳的对象引用:AtomicStampeReference
带有时间戳的AtomicStampeReference
这下好了,记录了中间过程。
4.4.5 数组也能无锁:AtomicIntegerArray
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
public final int get(int i) public final int length() public final int getAndSet(int i, int newValue) public final boolean compareAndSet(int i, int expect, int update) public final int getAndIncrement(int i) public final int getAndDecrement(int i) public final int getAndAdd(int i, int delta)
4.4.7 让普通变量也享受原子操作:AtomicIntegerFieldUpdater
AtomicIntegerFieldUpdater、AtomicLongFieldUpdater、AtomicReferenceFieldUpdater
public final static AtomicIntegerFileldUpdater<Candidate> soreUpdater = AtomicIntegerFieldUpdater.newUpdater(Candidate.class, "score")