加锁前要清楚锁和被保护的对象是不是一个层面的
- 静态字段属于类,类级别的锁才能保护;而非静态字段属于类实例,实例级别的锁就可以保护
加锁要考虑锁的粒度和场景问题
- 不要滥用synchronized
- 如果精细化考虑了锁应用范围后,性能还无法满足需求的话,我们就要考虑另一个维度的粒度问题了,即:区分读写场景以及资源的访问冲突,考虑使用悲观方式的锁还是乐观方式的锁。
- 一般业务代码中,很少需要进一步考虑这两种更细粒度的锁,几个概括的结论:
- 对于读写比例差异明显的场景,考虑使用 ReentrantReadWriteLock 细化区分读写锁,来提高性能。
- 如果你的 JDK 版本高于 1.8、共享资源的冲突概率也没那么大的话,考虑使用 StampedLock 的乐观读的特性,进一步提高性能。
- JDK 里 ReentrantLock 和 ReentrantReadWriteLock 都提供了公平锁的版本,在没有明确需求的情况下不要轻易开启公平锁特性,在任务很轻的情况下开启公平锁可能会让性能下降上百倍。
多把锁要小心死锁问题
- wrk 压力测试工具,需要自己下载并编译
- wrk -c 2 -d 10s url 对指定url进行压力测试
- 死锁问题解决后,wrk的TPS也有明显提升
思考 -线程会不会退出?
我们开启了一个线程无限循环来跑一些任务,有一个 bool 类型的变量来控制循环的退出,默认为 true 代表执行,一段时间后主线程将这个变量设置为了 false。如果这个变量不是 volatile 修饰的,子线程可以退出吗?你能否解释其中的原因呢?
- 一般情况下,不会退出
- 如果是volatile修饰的,可以退出
- 如果希望可以退出,列出ABC中方法
- 每种方法可以保证主线程的数据变更,可以及时同步到线程内存中。
```
@Slf4j
public class Test {
public static void main(String[] args) throws Exception{
} }NoExit noExit = new NoExit();
new Thread(noExit).start();
TimeUnit.SECONDS.sleep(1);
noExit.flag = false;
//子线程不退出,主线程也不会主动退出
System.out.println("test end");
class NoExit implements Runnable { boolean flag = true; //代码段A,使用了 volatile关键字,保证可见性 // volatile boolean flag = true; int i = 0;
@Override
public void run() {
while (flag) {
i++;
//代码段B,让线程sleep也可以使工作内存更新,猜测是sleep完成,阻塞 ->就绪 -> runnig 状态会更新工作内存吧
/ try{ TimeUnit.SECONDS.sleep(2); }catch (Exception e){ e.printStackTrace(); }/
//代码段C,输出语句中的println源码加锁了,sync会让当前线程的工作内存失效。
// System.out.println(i++); } System.out.println(“exiting, i = “ + i); } }
```