我们可以利用synchronized关键字来对程序进行加锁。它既可以用来声明一个synchronized代码块,也可以直接标记静态方法或者实例方法。
synchronized怎么实现线程的同步?
早期的synchronized属于重量级锁,依赖于mutex lock实现,线程之间的切换涉及到 用户态--》内核态的转换,由于状态的改变设计一个线程的所有变量状态的保存,以及空间的重新分配,系统开销十分巨大。
jdk1.6之后,引入了偏向锁,轻量级锁,在并发程度不是很高的情况下,也能保证良好的性能。
锁升级的步骤?
无锁--》偏向锁--》轻量级锁--》重量级锁
偏向锁: 目的是减少同一线程获取锁的代价。大多数情况下,锁是不存在多线程竞争的,总是由同一个线程多次获得。
偏向锁如何加:如果一个线程获得了锁,锁就进入了偏向模式。对象头MarkWorld的锁标志微为01,同时记录了该线程的线程id,当该线程再次请求锁的时候,无需再做任何同步操作,只需检查MarkWorld的锁标记位位现象锁以及线程id相同即可,色和功能区了中间有关锁申请的操作
轻量级锁:有偏向锁升级而来,偏向锁运行的一个线程进入同步块的情况下。当第二个线程加入锁竞争的时候,偏向锁就会升级为轻量级的锁。
适用场景:线程交替执行同步块。
Synchronized和ReentrantLock区别?
1.Synchronized基于操作 MarkWord,ReentrantLock 调用Unsafe类的方法
2.ReentrantLock可以对获取锁的等待时间进行设置,避免死锁
3.ReentrantLock可以实现公平,非公平锁
4.ReentrantLock配合condition可以实现更加精确的线程控制
5.Synchronized无需进行锁的释放,ReentrantLock需要进行锁的释放
synchronized的线程同步在JVM层面,通过字节码中的monitorenter和monitorexit指令。
public void test(Object lock){
synchronized (lock){
lock.toString();
}
}
如果使用synchronized标记方法,你会看到字节码中方法的访问标记包括ACC_SYNCHRONIZED。
该标记表示在进入该方法时,Java 虚拟机同样需要进行monitorenter操作。
/** * @Author:daboluo * @Date: 2019/9/29 8:58 * @Version 1.0 * * SynchronizedCountExample保证多线程相加原子性 */ public class SynchronizedCountExample { private static int clientTotal = 5000; private static int threadTotal = 200; private static int count = 0; public static void main(String[] args) throws Exception{ ExecutorService exec = Executors.newCachedThreadPool(); final Semaphore semaphore = new Semaphore(threadTotal); final CountDownLatch countDownLatch = new CountDownLatch(clientTotal); for (int i = 0; i < clientTotal; i++) { exec.execute(()->{ try { semaphore.acquire(); add(); semaphore.release(); } catch (InterruptedException e) { e.printStackTrace(); } countDownLatch.countDown(); }); } countDownLatch.await(); exec.shutdown(); System.out.println("count = " + count); } /** * * synchronized : 不可终端的,适合竞争不激烈的,可读性号 * lovk:可中断锁,多样化同步,竞争激烈时能维持常态 * atomic:竞争激烈时能维持常态,比lock的性能号;侄儿能同步一个值 * * */ private synchronized static void add(){ count++; } } /** * 结果: * count = 5000 * */
锁优化的方案?
1.降低锁的时间(只需要在有线程安全要求的程序代码上加锁,而不是整个代码块加)
2.降低锁的细粒度(之前的ConcurrentHashMap通过减少锁的细粒度,提升性能)
3.读写分离(应用系统中,读操作次数远远大于写操作)
4.锁的粗化(通过把synchronized加载for循环外,避免每次for循环都涉及线程的切换)
5.锁消除(StringBuffer是一个局部变量,堆栈封闭,方法没有把StringBuffer返回,所以不可能会有多线程去访问它。
当我们在一些不会有线程安全的情况下使用这些类的方法时,达到某些条件时,编译器会将锁消除来提高性能。)