Java基础知识_Java锁机制
一、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 InterruptedException {
for (int i = 0; i <3; i++) {
Thread.sleep(1000);
System.out.println("function running...");
}
}
//synchronized修饰静态方法
public static synchronized void staticFunction()
throws InterruptedException {
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();
}
}

浙公网安备 33010602011771号