线程同步的方法(二)

线程同步

       当多个线程访问同一个数据时,容易出现线程安全问题。需要让线程同步,保证数据安全。即当两个或两个以上线程访问同一资源时,需要某种方式来确保资源在某一时刻只被一个线程使用。

线程同步的实现:

1. 使用synchronized关键字(同步方法或代码块)

    java的每个对象都有一个内置锁,内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。

  注:

     synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类。 同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没必要同步整个方法,使用synchronized同步关键代码块即可。

同步方法:给一个方法增加synchronized修饰符后就成为了同步方法,这个方法可以是静态方法和非静态方法,但不能为抽象方法。同步方法是对这个方法块里的代码进行同步,这种情况下锁定的对象就是同步方法所属的主体对象自身。若这个方法为静态同步方法锁定的是这个类对应的java.lang.Class类型的对象。

同步代码块:通过锁定一个指定对象,来对同步代码块中包含的代码进行同步。

synchronized用于保护共享数据,一定要分清哪些数据是共享数据。

2. Lock

      JDK1.5后新增功能,与采用synchronized相比,lock可提供多种锁方案,更灵活。

       java.util.concurrent.lock中的Lock框架是锁定的一个抽象,它允许把锁定的实现作为Java类,而不是作为语言的特性来实现。这就为Lock的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。

       ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,但是添加了类似锁投票、

定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。

注意:如果同步代码有异常,要将unlock()写入finally语句块。

Lock和synchronized的区别:

      1.   synchronized 和 lock 的用法区别
        synchronized(隐式锁):在需要同步的对象中加入此控制,synchronized 可以加在方法上,也可以加在特定代码块中,括号中表示需要锁的对象。
       lock(显示锁):需要显示指定起始位置和终止位置。一般使用 ReentrantLock 类做为锁,多个线程中必须要使用一个
 ReentrantLock 类做为对象才能保证锁的生效。且在加锁和解锁处需要通过 lock() 和 unlock() 显示指出。所以一般会在finally 块中写 unlock() 以防死锁。

     Lock只有代码块锁,synchronized有代码块锁和方法锁

     2. lock是一个接口,而synchronized是java中的关键字,synchronized是内置的语言实现。

     3. synchronized在发生异常时,会自动释放线程占有的锁,不会导致死锁现象的发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

     4. Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

     5.  synchronized 和 lock 性能区别 

      synchronized 是托管给 JVM 执行的,而 lock 是 Java 写的控制锁的代码。使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)。在 JDK 1.5 中,synchronize 是性能低效的。因为这是一个重量级操作,需要调用操作接口,导致有可能加锁消耗的系统时间比加锁以外的操作还多。相比之下使用 Java 提供的 Lock 对象,性能更高一些。但是到了 JDK 1.6,发生了变化。synchronized在语义上很清晰,可以进行很多优化,有适应自旋,锁消除,锁粗化,轻量级锁,偏向锁等等。导致在 JDK 1.6 上 synchronized的性能并不比 Lock 差。

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

    6. synchronized 和 lock 机制区别
synchronized 原始采用的是 CPU 悲观锁机制,即线程获得的是独占锁。独占锁意味着其他线程只能依靠阻塞来等待线程释放锁。Lock 用的是乐观锁方式。所谓乐观锁就是,每次不加锁而是假设没有冲突而去完成某项操作,如果因为冲突失败就重试,直到成功为止。乐观锁实现的机制就是 CAS 操作(Compare and Swap)。

3. volatile实现线程同步

  volatile关键字为域变量的访问提供了一种免锁机制。需要在同步的变量前面加时volatile修饰,即可实现线程同步。

     1.多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。

     2.volatile不能保证原子操作,因此volatile不能代替synchronized

     3.每次要线程要访问volatile修饰的变量时都是从内存中读取,而不是从缓存当中读取,因此每个线程访问到的变量值都是一样的。这样就保证了同步。

使用场景:

      1)对变量的写操作不依赖当前值。

      2)该变量没有包含在具有其他变量的不变式中。也就是变量取值时值的内容独立于程序其他部分。

4. 线程同步的方法:

     wait():使一个线程处于等待状态,并且释放所持有的对象的lock。

     sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉 InterruptedException异常。

     notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的 唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。

     notityAll ():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁, 而是让它们竞争。

线程同步的优缺点:

       优点:解决了线程安全问题。

       缺点:性能下降,会带来死锁。

有关死锁的认知,参考博客:https://blog.csdn.net/duan196_118/article/details/104653053

如有不足,欢迎留言指正。望不吝赐教。。。

发布了132 篇原创文章 · 获赞 1 · 访问量 7257

猜你喜欢

转载自blog.csdn.net/duan196_118/article/details/104651189