synchronized和Lock总结

synchronized 和Lock区别 :

https://www.jianshu.com/p/36eedeb3f912

https://blog.csdn.net/lengxiao1993/article/details/81568130

https://www.zhihu.com/question/57794716/answer/1229073284

 

  1. 来源: 
    lock是一个接口,而synchronized是java的一个关键字,synchronized是内置的语言实现;

  2. 异常是否释放锁: 
    synchronized在发生异常时候会自动释放占有的锁,因此不会出现死锁;而lock发生异常时候,不会主动释放占有的锁,必须手动unlock来释放锁,可能引起死锁的发生。(所以最好将同步代码块用try catch包起来,finally中写入unlock,避免死锁的发生。)

  3. 是否响应中断 
    lock等待锁过程中可以用interrupt来中断等待,而synchronized只能等待锁的释放,不能响应中断;

  4. 是否知道获取锁 
    Lock可以通过trylock来知道有没有获取锁,而synchronized不能;

  5. Lock可以提高多个线程进行读操作的效率。(可以通过readwritelock实现读写分离)

  6. 在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

  7. synchronized使用Object对象本身的wait 、notify、notifyAll调度机制,而Lock可以使用Condition进行线程之间的调度,

2、synchronized和lock性能区别

synchronized是托管给JVM执行的, 
而lock是java写的控制锁的代码。

在Java1.5中,synchronize是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用Java提供的Lock对象,性能更高一些。

作者:敖丙
链接:https://www.zhihu.com/question/57794716/answer/1229073284
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
 

那用户态和内核态又是啥呢?

Linux系统的体系结构大家大学应该都接触过了,分为用户空间(应用程序的活动空间)和内核。

我们所有的程序都在用户空间运行,进入用户运行状态也就是(用户态),但是很多操作可能涉及内核运行,比我I/O,我们就会进入内核运行状态(内核态)。

这个过程是很复杂的,也涉及很多值的传递,我简单概括下流程:

  1. 用户态把一些数据放到寄存器,或者创建对应的堆栈,表明需要操作系统提供的服务。
  2. 用户态执行系统调用(系统调用是操作系统的最小功能单位)。
  3. CPU切换到内核态,跳到对应的内存指定的位置执行指令。
  4. 系统调用处理器去读取我们先前放到内存的数据参数,执行程序的请求。
  5. 调用完成,操作系统重置CPU为用户态返回结果,并执行下个指令。

所以大家一直说,1.6之前是重量级锁,没错,但是他重量的本质,是ObjectMonitor调用的过程,以及Linux内核的复杂运行机制决定的,大量的系统资源消耗,所以效率才低。

还有两种情况也会发生内核态和用户态的切换:异常事件和外围设备的中断 大家也可以了解下。

但是到了Java1.6,发生了变化。synchronize在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在Java1.6上synchronize的性能并不比Lock差。官方也表示,他们也更支持synchronize,在未来的版本中还有优化余地。

2种机制的具体区别: 
synchronized原始采用的是CPU悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。而在CPU转换线程阻塞时会引起线程上下文切换,当有很多线程竞争锁的时候,会引起CPU频繁的上下文切换导致效率很低。

而Lock用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是CAS操作(Compare and Swap)。我们可以进一步研究ReentrantLock的源代码,会发现其中比较重要的获得锁的一个方法是compareAndSetState。这里其实就是调用的CPU提供的特殊指令。

现代的CPU提供了指令,可以自动更新共享数据,而且能够检测到其他线程的干扰,而 compareAndSet() 就用这些代替了锁定。这个算法称作非阻塞算法,意思是一个线程的失败或者挂起不应该影响其他线程的失败或挂起的算法。

========================================

JVM对synchronized 做的优化

  • 无锁 偏向锁,多数情况下,锁不存在竞争,总是由同一线程获得。

1 重量级锁
在上一篇博客中我们知道,Synchronized的实现依赖于与某个对象向关联的monitor(监视器)实现,而monitor是基于底层操作系统的Mutex Lock实现的,而基于Mutex Lock实现的同步必须经历从用户态到核心态的转换,这个开销特别大,成本非常高。所以频繁的通过Synchronized实现同步会严重影响到程序效率,而这种依赖于Mutex Lock实现的锁机制也被称为“重量级锁”,为了减少重量级锁带来的性能开销,JDK对Synchronized进行了种种优化,尤其是在JDK1.5中引入了轻量级锁和偏向锁。

熟悉JVM内存模型的同学都知道,每一个Java实例对象在内存中都包含一部分称之为对象头的区域,对象头记录了对象的运行时数据,这部分运行时数据包括GC分代信息和锁状态。

锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。

2 轻量级锁
轻量级锁是相对于重量级锁而言在获取锁和释放锁时更加高效,但轻量级锁并不能代替重量级锁。轻量级锁适用的场景是在线程交替获取某个锁执行同步代码块的场景,如果出现多个进程同时竞争同一个锁时,轻量级锁会膨胀成重量级锁。

2.1 轻量级锁加锁过程
当代码进入同步块时,如果同步对象锁为无锁状态(偏向锁标识为“0”,锁标志位为“01”),则当前执行线程会在当前栈帧中建立一个锁记录(Lock Record)用于复制同步对象的Mark Word, 称之为Displaced Mark Word
系统通过CAS操作将对象的Mark Word更新指向Lock Word,并将同步对象的Owner Thread指定为当前线程。若果操作成功进入步骤3,失败进入步骤4
如果这个更新动作成功了,那么这个线程就拥有了该对象的锁,并且对象Mark Word的锁标志位设置为“00”,即表示此对象处于轻量级锁定状态
如果这个更新操作失败了,虚拟机首先会检查对象的Mark Word是否指向当前线程的栈帧,如果是就说明当前线程已经拥有了这个对象的锁,那就可以直接进入同步块继续执行。否则说明多个线程竞争锁,轻量级锁就要膨胀为重量级锁,锁标志的状态值变为“10”,Mark Word中存储的就是指向重量级锁(互斥量)的指针,后面等待锁的线程也要进入阻塞状态。 而当前线程便尝试使用自旋来获取锁,自旋就是为了不让线程阻塞,而采用循环去获取锁的过程
2.2 轻量级锁的释放过程
通过CAS操作将Displaced Mark Word替换对象的Mark Word
如果操作成功,同步完成
如果失败,则说明已经有其他线程竞争当前对象,此时对象的锁已经升级为重量级锁。则当前线程在释放的同时需要通知其他等待线程
3 偏向锁
偏向锁是基于一个经验为前提的:在多线程环境下,存在很多同步块只会被一个线程执行。在这样的情况下,使用重量级锁或者轻量锁都不是最经济的。因为轻量级锁的获取及释放依赖多次CAS原子指令,而偏向锁只需要在置换ThreadID的时候依赖一次CAS原子指令。轻量级锁的机制很简单,当某个同步对象被某个线程获取后,同步对象会将其thread owner指向该线程。即使在线程退出同步块之后,同步对象的thread owner依然不会改变。当该线程下次再次进入该代码块时,不用再获取同步对象使用权而直接执行代码块。如果有其他线程竞争该同步对象,则偏向锁失效,偏向锁将升级为轻量级锁或重量级锁。

3.1 偏向锁的获取
访问Mark Word中偏向锁的标识是否设置成1,锁标志位是否为01——确认为可偏向状态
如果为可偏向状态,则测试线程ID是否指向当前线程,如果是,进入步骤(5),否则进入步骤(3)
如果线程ID并未指向当前线程,则通过CAS操作竞争锁。如果竞争成功,则将Mark Word中线程ID设置为当前线程ID,然后执行(5);如果竞争失败,执行(4)
如果CAS获取偏向锁失败,则表示有竞争。当到达全局安全点(safepoint)时获得偏向锁的线程被挂起,偏向锁升级为轻量级锁,然后被阻塞在安全点的线程继续往下执行同步代码
执行同步代码
3.2 偏向锁的释放
偏向锁的撤销在上述第四步骤中有提到。偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动去释放偏向锁。偏向锁的撤销,需要等待全局安全点(在这个时间点上没有字节码正在执行),它会首先暂停拥有偏向锁的线程,判断锁对象是否处于被锁定状态,撤销偏向锁后恢复到未锁定(标志位为“01”)或轻量级锁(标志位为“00”)的状态。

3.3 偏向锁、轻量级锁、重量级锁之间的转换


4 其他优化
4.1 适应性自旋
从轻量级锁获取的流程中我们知道,当线程在获取轻量级锁的过程中执行CAS操作失败时,是要通过自旋来获取重量级锁的。问题在于,自旋是需要消耗CPU的,如果一直获取不到锁的话,那该线程就一直处在自旋状态,白白浪费CPU资源。解决这个问题最简单的办法就是指定自旋的次数,例如让其循环10次,如果还没获取到锁就进入阻塞状态。但是JDK采用了更聪明的方式——适应性自旋,简单来说就是线程如果自旋成功了,则下次自旋的次数会更多,如果自旋失败了,则自旋的次数就会减少。

4.2 锁粗化
锁粗化的概念应该比较好理解,就是将多次连接在一起的加锁、解锁操作合并为一次,将多个连续的锁扩展成一个范围更大的锁。

4.3 锁消除
锁消除即删除不必要的加锁操作。根据代码逃逸技术,如果判断到一段代码中,堆上的数据不会逃逸出当前线程,那么可以认为这段代码是线程安全的,不必要加锁。
--------------------- 

原文:https://blog.csdn.net/asiaLIYAZHOU/article/details/76098607 

 

========================================

 

synchronized 对于 普通同步方法,锁是当前的实例对象,

                       对于静态同步方法,锁是当前类的class对象(存放在方法区的class类信息对象  Test.Class)

                       对于同步方法快,锁是Synchonized括号里配置的对象。

           

synchronized void method{}功能上,等效于

void method{

   synchronized(this) {

    ...

   }

       synchronized {static方法}此代码块等效于

void method{

   synchronized(Obl.class)

   }

 

 

 wait 和notify 是object对象里的方法,执行了wait()的对象不会再占用锁 

// 注意:调用对象的wait()方法时,不会占用锁;调用线程的sleep()方法时的时候会占用锁
 
package com.threadStudy; /**  * Created by shaxj-mac on 16/6/16.  */ 
public class Interrupt {

    public static Object lock = new Object();

    public static void main(String[] args) throws InterruptedException {
        CountDownLatch start = new CountDownLatch(3);
        ThreadClass t1 = new ThreadClass(start);
        ThreadClass t2 = new ThreadClass(start);
        ThreadClass t3 = new ThreadClass(start);

        t1.start();
        t2.start();
        t3.start();

//        t1.interrupt();
        start.await();

        // 如果不加lock会抛出异常
//        IllegalMonitorStateException - if the current thread is not the owner of the object's monitor.
//        根据官方注释解释,“当前线程不是对象管理者的所有者”
        synchronized (lock) {
            System.out.println("notify start");
            lock.notifyAll();
            System.out.println("notify end");
        }
    }

    public static class ThreadClass extends Thread {
        CountDownLatch start;

        ThreadClass(CountDownLatch start) {
            this.start = start;
        }

        @Override
        public void run() {
            System.out.println(this.getId() + ",run");
            // 注意:wait的时候,不会占用锁
            synchronized (lock) {
                try {
                    System.out.println("currentThread:" + this.getName());
                    start.countDown();
                    System.out.println("start.getCount = " + start.getCount());
                    lock.wait();
                    System.out.println("currentThread:" + this.getName() + "alter wait");
//                    sleep(5000);
//                    System.out.println("currentThread:" + this.getName() + "alter sleep");

                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // TODO 获取任务对象,跳出synchronized 模块,否则继续wait
            }
        }
    }
}
 
 
 
 
 
执行notifyAll的时候 会先执行当前持有lock的对象,等当前程序执行完(释放锁后)再执行其他之前wait的对象
思考下面程序中的 (把flag=false放在任意位置对结果没有任何影响!)
lock.notifyAll(); TimeUnit.SECONDS.sleep(10); flag = false;
调用 lock.notifyAll(); 还是会先执行当前的程序,并不会调用notifyall就会立刻去执行其他wait的程序。
 
官方解释:notify()或notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()或notify()的线程释放锁后,等待线程才有机会从wait()返回。
notifyAll()被移动的线程状态由WAITING变成BLOCKED。
 
 
package com.threadStudy; import java.util.concurrent.TimeUnit; /**  * Created by shaxj-mac on 16/6/16.  */ public class WaitNotify { static boolean flag = true; static Object lock = new Object(); public static void main(String args[]) throws Exception { Thread waitThread = new Thread(new Wait(), "waitThread"); waitThread.start(); TimeUnit.SECONDS.sleep(1); Thread notifyThread = new Thread(new Notify(), "notifyThread"); notifyThread.start(); } static class Wait implements Runnable { @Override  public void run() { synchronized (lock) { while (flag) { try { System.out.println(Thread.currentThread() + "flag is true.wait@"); lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(Thread.currentThread() + "flag is false. running @"); } } } static class Notify implements Runnable { @Override  public void run() { synchronized (lock) { try { System.out.println(Thread.currentThread() + "hold lock.notify @"); lock.notifyAll(); TimeUnit.SECONDS.sleep(10); flag = false; Thread.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized (lock) { System.out.println(Thread.currentThread() + " hold lock again . sleep @"); } } } } 

 

 

 

 

 

sleep 是Thread 中的方法。    

 

 volatile 可以用来修饰成员变量,表示当线程使用被volatile修饰的变量时都会强迫cpu从共享内存中取值,而不是从每个线程的缓存中获取。(共享内存可以理解为 web orm模型中的数据库) 

     synchronized

 

       thread.setPriority(Thread.MAX_PRIORITY) 设置线程的优先级 范围从1-10 默认是5 ,优先级高的分配的时间片要多。设置优先级时,针对频繁阻塞(休眠或者I/o操作)的线程需要设置较高的优先级,偏重计算的(需要较多的cpu时间)的线程设置为较低的优先级,确保处理器不会被独占。但是操作系统可能会对java中设置的优先级完全不理会。

 

 

* <p> If this thread is blocked in an invocation of the {@link
* Object#wait() wait()}, {@link Object#wait(long) wait(long)}, or {@link
* Object#wait(long, int) wait(long, int)} methods of the {@link Object}
* class, or of the {@link #join()}, {@link #join(long)}, {@link
* #join(long, int)}, {@link #sleep(long)}, or {@link #sleep(long, int)},
* methods of this class, then its interrupt status will be cleared and it
* will receive an {@link InterruptedException}.

wait /sleep 状态的thread 不能使用interrupt 方法,如果使用则会抛出 InterruptedException异常

 

 

 

 

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值