一、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 }
反编译看看
同步代码块
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是不可行的。
使用客户端锁,会将当前的实现与原本的list耦合了
书上给出的办法是组合的方式,也就是装饰器模型
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 }
结果证明:类锁和对象锁是不会冲突的
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显式锁是一个接口,我们康康
我们看看顶部注释,看看是干什么的
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();
}
}