Synchronized和Lock接口

打印 上一主题 下一主题

主题 926|帖子 926|积分 2778

Synchronized

Synchronized关键字回顾

synchronized是java中的关键字,是一种同步锁。它修饰的对象有以下几种:

  • 1.修饰一个代码块,被修饰的代码块称为同步代码块,其作用的范围是大括号{},括起来的代码,作用的对象是调用这个代码块的对象,synchronized不能修饰静态代码块。
  • 2.修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象。
  • 3.修饰一个静态方法,其作用范围是整个静态方法,作用的对象是这个类的所有对象。
  • 4.修饰一个类,其作用范围是synchronized后面括号括起来的部分,作用主要的对象是这个类的所有对象。
作用的对象,有点不了解。以及synchronized锁作用在this对象,和作用在类.class上有什么区别?学完后面的课程,记得来回答这个问题。
关于synchronized的理解,共有两种类型的锁:

(1)类锁:只有synchronized修饰静态方法或者修饰一个类的class对象时,才是类锁。
(2)对象锁:除了类锁,所有其他的上锁方式都认为是对象锁。比如synchronized修饰普通方法或者synchronized(this)给代码块上锁等。
应该注意的是,因为一个类只有一个class对象,因此所有的访问者在访问被加了类锁的代码时,都是共用同一把锁,而类的实例却可以有很多个,因此不同对象访问加了对象锁的代码,它们的访问互不干扰。
synchronized锁的访问规则

(1)加了相同锁的代码,它们的访问规则是相同的,即当某个访问者获得该锁时,它们一起向该访问者开放访问,向其他没有获得该锁的访问者关闭访问。
(2)加了不同锁的代码访问互相不干扰。
(3)而没有加锁的代码随时都可以任意访问,不受任何限制。
判断是否同一把synchronized锁

(1)不同类型的锁不是同一把锁。
(2)加的是对象锁,那么必须是同一个对象实例才是同一把锁。
(3)加的是类锁,那必须是同一类才是同一把锁。
对于sysnchronized修饰的代码能否访问

1.首先判断是不是同一把锁。
2.然后判断各自的访问规则。
注意事项

虽然synchronized关键字可以来修饰方法,但synchronized关键字不属于方法定义的一部分。因此,synchronized关键字不能被继承。如果在父类中的某个方法使用了synchronized关键字,子类又覆盖了该方法,在子类中的方法默认情况下是不进行同步的,而必须显示在子类的方法上也加上synchronized关键字才可以。当然,也可以在子类中直接调用父类方法,这样虽然子类方法不同步但是方法体的内容,是同步的,因此相当于子类方法也同步。
多线程编程步骤(上)

第一,创建资源类,创建属性和操作方法。第二 创建多线程调用资源类的方法。
售票案例

sale 销售 ticket 票
案例要求,3个售票员进行售票,共售卖30张票。
代码
  1. /**
  2. * @author 长名06
  3. * @version 1.0
  4. * 多线程编程步骤,第一步 创建资源类,定义属性和方法
  5. * 第二步,创建多个线程,调用资源类的操作方法
  6. */
  7. public class SaleTickets {
  8.     public static void main(String[] args) {
  9.         Ticket ticket = new Ticket();
  10.         new Thread(() -> {
  11.             for (int i = 0; i < 40; i++){
  12.                 ticket.sale();
  13.             }
  14.         }, "aa").start();
  15.         new Thread(() -> {
  16.             for (int i = 0; i < 40; i++){
  17.                 ticket.sale();
  18.             }
  19.         }, "bb").start();
  20.         new Thread(() -> {
  21.             for (int i = 0; i < 40; i++){
  22.                 ticket.sale();
  23.             }
  24.         }, "cc").start();
  25.     }
  26. }
  27. class Ticket {
  28.     //票数
  29.     private static int number = 30;
  30.     public synchronized void sale() {
  31.         if (number > 0) {
  32.             System.out.println(Thread.currentThread().getName() + "\t 卖出一张票"
  33.                     + "剩下票数为" + --number);
  34.         }
  35.     }
  36. }
复制代码
代码分析


  • 1.synchronized关键字修饰的非静态方法,此时这个方法的锁就是synchronized(this)锁,就是对调用当前方法的对象上锁。案例中的aa,bb,cc线程都是使用ticket这同一个对象,调用的sale()方法,所以这三个线程是互斥的,同时只能由三个中的一个,使用sale()方法。从输出结果,也可以看出,是互斥访问的。
  • 2.当一个线程获取了对应的锁,可以访问对应锁的代码块,其他需要访问该代码块的线程,就要等待。等待获取锁的线程释放锁,但是获取到锁的线程的执行有两种情况。
    1)获取锁的线程,执行对应的代码块,然后线程释放锁,无事发生,正常情况;
    2)拥有锁的线程执行中,出现了异常,此时JVM会让线程自动释放锁。
  • 3.但如果获取锁的线程的操作,需要等待IO或者其他原因(如sleep方法)被阻塞了,但是线程没有释放锁,其他的线程就只能等待,会很影响执行效率。所以需要一种机制可以不让等待的线程一直无限的等待下去(等待一定的时间,就能响应中断),通过Lock(java.util.concurrent.locks包下的接口)就可以实现。
  • 4.以上锁都是synchronized锁。
Lock

基本介绍

Lock锁实现,并提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock提供了比synchronized更多的功能。
Lock和synchronized的区别


  • 1.Lock不是java语言内置的关键字,synchronized是Java语言的关键字,是内置特性。Lock是一个接口,可以通过其实现类实现异步访问。
  • 2.采用synchronized关键字,不需要用户去手动释放锁,当synchronized关键字修饰的方法或代码块执行完之后,有JVM自动让线程释放锁。但是Lock则必须让用户手动释放锁,如果没有主动释放锁,就可能会出现死锁现象。
使用Lock实现sale_ticket案例
  1. import java.util.concurrent.locks.ReentrantLock;
  2. /**
  3. * @author 长名06
  4. * @version 1.0
  5. * 多线程编程步骤,第一步 创建资源类,定义属性和方法
  6. * 第二步,创建多个线程,调用资源类的操作方法
  7. */
  8. public class SaleTickets {
  9.     public static void main(String[] args) {
  10.         Ticket ticket = new Ticket();
  11.         new Thread(() -> {
  12.             for (int i = 0; i < 40; i++){
  13.                 ticket.sale();
  14.             }
  15.         }, "aa").start();
  16.         new Thread(() -> {
  17.             for (int i = 0; i < 40; i++){
  18.                 ticket.sale();
  19.             }
  20.         }, "bb").start();
  21.         new Thread(() -> {
  22.             for (int i = 0; i < 40; i++){
  23.                 ticket.sale();
  24.             }
  25.         }, "cc").start();
  26.     }
  27. }
  28. class Ticket {
  29.     //票数
  30.     private static int number = 30;
  31.     private final ReentrantLock lock = new ReentrantLock();
  32.     public void sale() {
  33.         lock.lock();//开启锁
  34.         try {
  35.             if (number > 0) {
  36.                 System.out.println(Thread.currentThread().getName() + "\t 卖出一张票"
  37.                         + "剩下票数为" + --number);
  38.             }
  39.         }finally {
  40.             lock.unlock();//释放锁
  41.         }
  42.     }
  43. }
复制代码
关于Thread#start()&start0()
  1. public synchronized void start() {//这个方法,完成线程的启动,但是实际创建线程是start0()方法完成的
  2.     /**
  3.      * This method is not invoked for the main method thread or "system"
  4.      * group threads created/set up by the VM. Any new functionality added
  5.      * to this method in the future may have to also be added to the VM.
  6.      *
  7.      * A zero status value corresponds to state "NEW".
  8.      */
  9.     if (threadStatus != 0)
  10.         throw new IllegalThreadStateException();
  11.     /* Notify the group that this thread is about to be started
  12.      * so that it can be added to the group's list of threads
  13.      * and the group's unstarted count can be decremented. */
  14.     group.add(this);
  15.     boolean started = false;
  16.     try {
  17.         start0();//这里这个方法的调用,才是完成线程的创建
  18.         started = true;
  19.     } finally {
  20.         try {
  21.             if (!started) {
  22.                 group.threadStartFailed(this);
  23.             }
  24.         } catch (Throwable ignore) {
  25.             /* do nothing. If start0 threw a Throwable then
  26.               it will be passed up the call stack */
  27.         }
  28.     }
  29. }
  30. private native void start0();//此方法,java代码无法完成,而是用native关键字修饰的方法,就是使用jvm底层的JNI(Java Native Interface)机制调用c语言库完成的。这个创建线程的方法,以及是否创建,创建顺序,不是由java程序决定的,而是由程序运行的OS决定的。
复制代码
lock接口方法
  1. public interface Lock {
  2.         void lock();//获取锁
  3.         void lockInterruptibly() throws InterruptedException;
  4.     boolean tryLock();
  5.     boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
  6.         void unlock();//释放锁
  7.     Condition newCondition();
  8. }
复制代码
lock方法

lock()方法使用最多的方法,功能,获取锁,如果锁被其他线程获取,则进行等待。
使用Lock,必须主动去释放锁,并且发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}finally{}块中进行,并且将释放锁的操作在finally块中,用来保证锁一定被释放,防止死锁的发生。通常使用Lock实现同步的,是以如下形式的。
  1. lock.lock();//开启锁
  2. try {
  3.     //...具体代码
  4. }catch(Exception e){
  5.    
  6. }
  7. finally {
  8.     lock.unlock();//释放锁
  9. }
复制代码
newCondition方法

关键字synchronizedwait()/notify()这两个方法一起使用,可实现等待/通知模式。Lock锁的newCondition()方法返回Condition对象,Condition类也可以实现等待/通知的模式。wait()和notify()是Object类下的方法,notify()被调用时,JVM会随机的唤醒某个等待的线程,使用Condition类可进行选择性通知,Condition常用的方法,awiat()方法,会使当前线程等待,同时会释放当前线程持有的锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。signal()用于唤醒等待的线程。
注意,在使用Condition接口(使用其具体的实现类)的await()和signa()方法前,需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在signal()调用后会从当前Condition对象的等待队列中,唤醒一个线程,唤醒的线程尝试获得锁,一旦获得锁成功,就执行。
小结

Lock和synchronized有以下几点不同

  • 1.Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现的。
  • 2.synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。Lock在发生异常时,如果没有主动通过unLock()会释放锁,则很可能会造成死锁现象,因此使用Lock时需要在finally块中释放锁。
  • 3.Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一致等待,不能够使用中断。
  • 4.通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。
  • 5.Lock可以提高多个线程进行读操作的效率。
    在性能上来说,如果资源竞争不激烈,二者的性能是差不多的,而当资源竞争激烈时,此时Lock的性能要远远优于synchronized。
只是为了记录自己的学习历程,且本人水平有限,不对之处,请指正。

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

数据人与超自然意识

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表