一、synchronized锁

1.1 synchronized锁是什么

synchronized是Java的一个关键字,它能够将代码块(方法)锁起来

  它的使用起来非常简单,只要在代码块(方法)添加关键字synchronized,即可以实现同步功能。

1 public synchronized void test() {
2         // doSomething
3     }

synchronized是一种互斥锁

  一次只能允许一个线程进入被锁住的代码块

synchronized是一种内置锁/监视器锁

  Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用对象的内置锁(监视器)来将代码块(方法)锁定的。

 

1.2 synchronized用处是什么呢?

synchronized保证了线程的原子性(被保护的代码块是一次被执行的,没有任何线程会同时访问)

synchronized保证了可见性(当执行完synchronized之后,修改后的变量对其他线程是可见的)

Java的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。

 

1.3 synchronized的原理

我们首先来看一段synchronized修饰方法和代码块的代码

1 public class Main {
 2     //修饰方法
 3     public synchronized void test1(){
 4 
 5     }
 6 
 7 
 8     public void test2(){
 9         // 修饰代码块
10         synchronized (this){
11 
12         }
13     }
14 }

反编译看看

java 锁接口 java 锁的是什么_sed

 

 

 同步代码块

monitorenter和monitorexit指令实现的

同步方法(在这看不出来需要看JVM底层实现)

  方法修饰符上的ACC_SYNCHRONIZED实现

synchronized底层是通过monitor对象,对象有自己的对象头,储存了很多信息,其中一个信息标识是被哪个线程持有

 

1.4 synchronized如何使用

修饰普通方法

修饰代码块

修饰静态方法

1.4.1 修饰普通方法

用的锁是java对象(内置锁)

public class Java {
        // 修饰普通方法,此时用的锁是Java对象(内置锁)
    public synchronized void test() {
        // doSomething
    }

}

1.4.2修饰代码块

用的锁是java对象(this)

1 public class Java {
 2 
 3     public  void test() {
 4 
 5         // 修饰代码块,此时用的锁是Java对象(内置锁)--->this
 6         synchronized (this){
 7 
 8             // doSomething
 9         }
10     }
11 }

当然了,我们使用synchronized修饰代码块的时候未必适用this,还可以使用其他对象(随便一个对象都有一个内置锁)

所以我们可以这么干

1 public class Java {
 2 
 3 
 4     // 使用object作为锁(任何对象都有对应的锁标记,object也不例外)
 5     private Object object = new Object();
 6 
 7 
 8     public void test() {
 9 
10         // 修饰代码块,此时用的锁是自己创建的锁Object
11         synchronized (object){
12 
13             // doSomething
14         }
15     }
16 
17 }

上面那种方式(随便使用一个对象作为锁)在书上称之为客户端锁,是不建议使用的。

书上想要实现的功能是:给Arraylist添加一个putIfAbsent(),这是需要线程安全的。

假定直接添加synchronized是不可行的。

java 锁接口 java 锁的是什么_代码块_02

 

 

 使用客户端锁,会将当前的实现与原本的list耦合了

java 锁接口 java 锁的是什么_Java_03

 

 

 书上给出的办法是组合的方式,也就是装饰器模型

java 锁接口 java 锁的是什么_Java_04

 

 

 

1.4.3 修饰静态方法

获取到的类锁(类的字节码文件对象)

修饰静态方法代码块,静态方法属于类方法,它属于这个类,获取到的锁是属于类的锁(类的字节码文件对象)-java.class

 

1.4.4类锁与对象锁

synchronized修饰的静态方法获取的是类锁(类的字节码文件对象),synchronized修饰普通方法或者代码块获取的是对象锁。

它俩是不冲突的,也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的

1 public class SynchoronizedDemo {
 2 
 3     //synchronized修饰非静态方法
 4     public synchronized void function() throws InterruptedException {
 5         for (int i = 0; i <3; i++) {
 6             Thread.sleep(1000);
 7             System.out.println("function running...");
 8         }
 9     }
10     //synchronized修饰静态方法
11     public static synchronized void staticFunction()
12             throws InterruptedException {
13         for (int i = 0; i < 3; i++) {
14             Thread.sleep(1000);
15             System.out.println("Static function running...");
16         }
17     }
18 
19     public static void main(String[] args) {
20         final SynchoronizedDemo demo = new SynchoronizedDemo();
21 
22         // 创建线程执行静态方法
23         Thread t1 = new Thread(() -> {
24             try {
25                 staticFunction();
26             } catch (InterruptedException e) {
27                 e.printStackTrace();
28             }
29         });
30 
31         // 创建线程执行实例方法
32         Thread t2 = new Thread(() -> {
33             try {
34                 demo.function();
35             } catch (InterruptedException e) {
36                 e.printStackTrace();
37             }
38         });
39         // 启动
40         t1.start();
41         t2.start();
42     }
43 }

结果证明:类锁和对象锁是不会冲突的

java 锁接口 java 锁的是什么_sed_05

 

 

 

1.5重入锁

我们看看下面的代码

1 public class Widget {
 2 
 3     // 锁住了
 4     public synchronized void doSomething() {
 5         ...
 6     }
 7 }
 8 
 9 public class LoggingWidget extends Widget {
10 
11     // 锁住了
12     public synchronized void doSomething() {
13         System.out.println(toString() + ": calling doSomething");
14         super.doSomething();
15     }
16 }

1、当线程A进入到LoggingWidget的doSomething方法时,此时拿到了LoggingWidget实例对象的锁

2、随后在方法上又调用了父类Widget的doSomething方法,它又是被synchronized修饰

3、那咱们LoggingWidget实例对象的锁还没有释放,进入父类Widget的dosomething方法还需要一把锁吗

不需要的

因为锁的持有者是线程而不是调用,线程A已经有了LoggingWidget实例对象的锁了,当在需要的时候可以继续开锁进去的

这就是内置锁的可重入性

1.6释放锁的时机

1、当方法(代码块)执行完毕后会自动释放锁,不需要做任何的操作

2、当一个线程执行代码出现异常的时候,其所持有的锁会自动释放

  不会由于异常导致出现死锁现象

 

二、Lock显式锁

2.1 Lock显式锁简单介绍

Lock显式锁是JDK1.5之后才有的,之前我们都是使用Synchronized锁来使线程安全的

Lock显式锁是一个接口,我们康康

java 锁接口 java 锁的是什么_代码块_06

 

 

 我们看看顶部注释,看看是干什么的

1 /**
  2  * {@code Lock} implementations provide more extensive locking
  3  * operations than can be obtained using {@code synchronized} methods
  4  * and statements.  They allow more flexible structuring, may have
  5  * quite different properties, and may support multiple associated
  6  * {@link Condition} objects.
  7  *
  8  * <p>A lock is a tool for controlling access to a shared resource by
  9  * multiple threads. Commonly, a lock provides exclusive access to a
 10  * shared resource: only one thread at a time can acquire the lock and
 11  * all access to the shared resource requires that the lock be
 12  * acquired first. However, some locks may allow concurrent access to
 13  * a shared resource, such as the read lock of a {@link ReadWriteLock}.
 14  *
 15  * <p>The use of {@code synchronized} methods or statements provides
 16  * access to the implicit monitor lock associated with every object, but
 17  * forces all lock acquisition and release to occur in a block-structured way:
 18  * when multiple locks are acquired they must be released in the opposite
 19  * order, and all locks must be released in the same lexical scope in which
 20  * they were acquired.
 21  *
 22  * <p>While the scoping mechanism for {@code synchronized} methods
 23  * and statements makes it much easier to program with monitor locks,
 24  * and helps avoid many common programming errors involving locks,
 25  * there are occasions where you need to work with locks in a more
 26  * flexible way. For example, some algorithms for traversing
 27  * concurrently accessed data structures require the use of
 28  * "hand-over-hand" or "chain locking": you
 29  * acquire the lock of node A, then node B, then release A and acquire
 30  * C, then release B and acquire D and so on.  Implementations of the
 31  * {@code Lock} interface enable the use of such techniques by
 32  * allowing a lock to be acquired and released in different scopes,
 33  * and allowing multiple locks to be acquired and released in any
 34  * order.
 35  *
 36  * <p>With this increased flexibility comes additional
 37  * responsibility. The absence of block-structured locking removes the
 38  * automatic release of locks that occurs with {@code synchronized}
 39  * methods and statements. In most cases, the following idiom
 40  * should be used:
 41  *
 42  *  <pre> {@code
 43  * Lock l = ...;
 44  * l.lock();
 45  * try {
 46  *   // access the resource protected by this lock
 47  * } finally {
 48  *   l.unlock();
 49  * }}</pre>
 50  *
 51  * When locking and unlocking occur in different scopes, care must be
 52  * taken to ensure that all code that is executed while the lock is
 53  * held is protected by try-finally or try-catch to ensure that the
 54  * lock is released when necessary.
 55  *
 56  * <p>{@code Lock} implementations provide additional functionality
 57  * over the use of {@code synchronized} methods and statements by
 58  * providing a non-blocking attempt to acquire a lock ({@link
 59  * #tryLock()}), an attempt to acquire the lock that can be
 60  * interrupted ({@link #lockInterruptibly}, and an attempt to acquire
 61  * the lock that can timeout ({@link #tryLock(long, TimeUnit)}).
 62  *
 63  * <p>A {@code Lock} class can also provide behavior and semantics
 64  * that is quite different from that of the implicit monitor lock,
 65  * such as guaranteed ordering, non-reentrant usage, or deadlock
 66  * detection. If an implementation provides such specialized semantics
 67  * then the implementation must document those semantics.
 68  *
 69  * <p>Note that {@code Lock} instances are just normal objects and can
 70  * themselves be used as the target in a {@code synchronized} statement.
 71  * Acquiring the
 72  * monitor lock of a {@code Lock} instance has no specified relationship
 73  * with invoking any of the {@link #lock} methods of that instance.
 74  * It is recommended that to avoid confusion you never use {@code Lock}
 75  * instances in this way, except within their own implementation.
 76  *
 77  * <p>Except where noted, passing a {@code null} value for any
 78  * parameter will result in a {@link NullPointerException} being
 79  * thrown.
 80  *
 81  * <h3>Memory Synchronization</h3>
 82  *
 83  * <p>All {@code Lock} implementations <em>must</em> enforce the same
 84  * memory synchronization semantics as provided by the built-in monitor
 85  * lock, as described in
 86  * <a href="http://docs.oracle.com/javase/specs/jls/se7/html/jls-17.html#jls-17.4">
 87  * The Java Language Specification (17.4 Memory Model)</a>:
 88  * <ul>
 89  * <li>A successful {@code lock} operation has the same memory
 90  * synchronization effects as a successful <em>Lock</em> action.
 91  * <li>A successful {@code unlock} operation has the same
 92  * memory synchronization effects as a successful <em>Unlock</em> action.
 93  * </ul>
 94  *
 95  * Unsuccessful locking and unlocking operations, and reentrant
 96  * locking/unlocking operations, do not require any memory
 97  * synchronization effects.
 98  *
 99  * <h3>Implementation Considerations</h3>
100  *
101  * <p>The three forms of lock acquisition (interruptible,
102  * non-interruptible, and timed) may differ in their performance
103  * characteristics, ordering guarantees, or other implementation
104  * qualities.  Further, the ability to interrupt the <em>ongoing</em>
105  * acquisition of a lock may not be available in a given {@code Lock}
106  * class.  Consequently, an implementation is not required to define
107  * exactly the same guarantees or semantics for all three forms of
108  * lock acquisition, nor is it required to support interruption of an
109  * ongoing lock acquisition.  An implementation is required to clearly
110  * document the semantics and guarantees provided by each of the
111  * locking methods. It must also obey the interruption semantics as
112  * defined in this interface, to the extent that interruption of lock
113  * acquisition is supported: which is either totally, or only on
114  * method entry.
115  *
116  * <p>As interruption generally implies cancellation, and checks for
117  * interruption are often infrequent, an implementation can favor responding
118  * to an interrupt over normal method return. This is true even if it can be
119  * shown that the interrupt occurred after another action may have unblocked
120  * the thread. An implementation should document this behavior.
121  *
122  * @see ReentrantLock
123  * @see Condition
124  * @see ReadWriteLock
125  *
126  * @since 1.5
127  * @author Doug Lea
128  */

可以简单概括一下:

  Lock方法来获取锁支持中断、超时不获取、是非阻塞的

  提高了语义化,哪里加锁,哪里解锁都写得出来

  Lock显式锁可以给我们带来很好得灵活性,但同时我们必须手动释放锁

  支持Condition条件对象

  允许多个读线程同时访问共享资源

 

2.3 synchronized锁和Lock锁使用哪个呢?

前面说了,Lock显式锁给我们得程序带来了很多灵活性,很多特性都是Synchronized锁没有得。那Synchronized锁有没有存在得必要。

当然有,Lock锁在刚出的时候很多性能方面都比Synchronized锁要好,但是从jdk1.6开始,Synchronized锁就做出理论很多优化

优化操作:适应自旋锁,锁消除,锁粗化,轻量级锁,偏向锁

详情可参考:https://blog.csdn.net/chenssy/article/details/54883355

所以到现在,Lock锁和Synchronized锁的性能差别不是很大,而synchronized锁用起来又特别简单,Lock锁还得顾忌到它的特性,要手动释放锁才行(如果忘了释放,就是一个隐患)

所以说,我们大多数时候还是使用Synchronized锁

 

2.3 公平锁

公平锁理解起来非常简单

  线程按照他们发出请求的顺序来获取锁

非公平锁

  线程发出请求的时候可以插队获取锁

Lock和synchronized都是默认使用非公平锁的,如果不是必要情况,不要使用公平锁。

  公平锁会带来一些性能的消耗

 

四、最后

synchronized好用,简单,性能不差

没有使用到Lock显式锁特性就不要用Lock锁了。

 

publicclass SynchoronizedDemo{

    //synchronized修饰非静态方法
    public synchronized void function() throws{
        for (int i = 0; i <3; i++) {
            Thread.sleep(1000);
            System.out.println("function running...");
        }
    }
    //synchronized修饰静态方法
    public static synchronized void staticFunction()
            throws{
        for (int i = 0; i < 3; i++) {
            Thread.sleep(1000);
            System.out.println("Static function running...");
        }
    }

    public static void main(String[] args){
        final SynchoronizedDemo demo = new SynchoronizedDemo();

        // 创建线程执行静态方法
        Thread t1 = new Thread(() -> {
            try {
                staticFunction();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        // 创建线程执行实例方法
        Thread t2 = new Thread(() -> {
            try {
                demo.function();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        // 启动
        t1.start();
        t2.start();
    }
}