1、Synchronized锁
2、Lock锁
synchronized锁
synchronized是Java的一个关键字,它能够将代码块(方法)锁起来。只要在代码块(方法)添加关键字synchronized,即可以实现同步的功能。
public synchronized void test() { // doSomething}
特征
1、synchronized是一种互斥锁。
一次只能允许一个线程进入被锁住的代码块。
2、synchronized是一种内置锁/监视器锁。
Java中每个对象都有一个内置锁(监视器,也可以理解成锁标记),而synchronized就是使用对象的内置锁(监视器)来将代码块(方法)锁定的!
用处
1、synchronized保证了线程的原子性。
被保护的代码块是一次被执行的,没有任何线程会同时访问;2、synchronized还保证了可见性。
当执行完synchronized之后,修改后的变量对其他的线程是可见的;
3、Java中的synchronized,通过使用内置锁,来实现对变量的同步操作,进而实现了对变量操作的原子性和其他线程对变量的可见性,从而确保了并发情况下的线程安全。
使用
synchronized一般用来修饰三种东西:修饰普通方法;修饰代码块;修饰静态方法;
1、修饰普通方法用的锁是MainSyn对象(内置锁)
public class MainSyn { /** * 修饰普通方法,此时用的锁是MainSyn对象(内置锁) */ public synchronized void test() { // doSomething }}
2、修饰代码块用的锁是MainSyn对象(内置锁)--->this
public class MainSyn { public void test() { // 修饰代码块,此时用的锁是MainSyn对象(内置锁)--->this synchronized (this) { // doSomething } }}
当然了,使用synchronized修饰代码块时未必使用this,还可以使用其他的对象(随便一个对象都有一个内置锁)
public class MainSyn { /** * 使用object作为锁(任何对象都有对应的锁标记,object也不例外) */ private final Object object = new Object(); public void test() { // 修饰代码块,此时用的锁是自己创建的锁Object synchronized (object) { // doSomething } }}
3、修饰静态方法获取到的是类锁(类的字节码文件对象):MainSyn.class
public class MainSyn { /** * 修饰静态方法代码块,静态方法属于类方法,它属于这个类。 * 获取到的锁是属于类的锁(类的字节码文件对象)-->MainSyn.class */ public static synchronized void test() { // doSomething }}
类锁与对象锁
synchronized修饰静态方法获取的是类锁(类的字节码文件对象),synchronized修饰普通方法或代码块获取的是对象锁。
它俩是不冲突的,也就是说:获取了类锁的线程和获取了对象锁的线程是不冲突的!
public class SynchronizedDemo { /** * 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("staticFunction running..."); } } public static void main(String[] args) { final SynchronizedDemo demo = new SynchronizedDemo(); // 创建线程执行静态方法 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(); }}
测试结果:类锁和对象锁是不会冲突的。
重入锁
看下面的代码:
public class Widget { /** * 锁住了 */ public synchronized void doSomething() { System.out.println("Widget:calling doSomething"); }}
public class LoggingWidget extends Widget { /** * 锁住了 */ @Override public synchronized void doSomething() { System.out.println("LoggingWidget:calling doSomething"); super.doSomething(); }}
1、当线程A进入到LoggingWidget的doSomething()方法时,此时拿到了LoggingWidget实例对象的锁。2、随后在方法上又调用了父类Widget的doSomething()方法,它又是被synchronized修饰。3、那现在LoggingWidget实例对象的锁还没有释放,进入父类Widget的doSomething()方法还需要一把锁吗?不需要的!
因为锁的持有者是“线程”,而不是“调用”。线程A已经是有了LoggingWidget实例对象的锁了,当再需要的时候可以继续“开锁”进去的!这就是内置锁的可重入性。
释放锁的时机
1、当方法(代码块)执行完毕后会自动释放锁,不需要做任何的操作。
2、当一个线程执行的代码出现异常时,其所持有的锁会自动释放。
不会由于异常导致出现死锁现象。
原理
看下synchronized修饰方法和代码块的代码:
public class SynchronizedDemo { /** * 修饰方法 */ public synchronized void test1() { // doSomething } public void test2() { // 修饰代码块 synchronized (this) { } }}
来反编译看一下javap -c SynchronizedDemo:
根据上面的反编译的图片可以看出:
1、同步代码块:
monitorenter和monitorexit指令实现的。
2、同步方法(在这看不出来需要看JVM底层实现):
方法修饰符上的ACC_SYNCHRONIZED实现。
synchronized底层是是通过monitor对象,对象有自己的对象头,存储了很多信息,其中一个信息标示是被哪个线程持有。
Lock锁
Lock显式锁是JDK1.5之后才有的,之前都是使用Synchronized锁来使线程安全的。
Lock显式锁是一个接口
看下Lock这个接口的注释说明
package java.util.concurrent.locks;import java.util.concurrent.TimeUnit;/*** {@code Lock} implementations provide more extensive locking* operations than can be obtained using {@code synchronized} methods* and statements. They allow more flexible structuring, may have* quite different properties, and may support multiple associated* {@link Condition} objects.* 额外的锁机制。* Lock具有更好的伸缩性,支持Condition条件对象。**
A lock is a tool for controlling access to a shared resource by
* multiple threads. Commonly, a lock provides exclusive access to a* shared resource: only one thread at a time can acquire the lock and* all access to the shared resource requires that the lock be* acquired first. However, some locks may allow concurrent access to* a shared resource, such as the read lock of a {@link ReadWriteLock}.* 通常限定每次一个线程访问共享变量。* 但是ReadWriteLock允许读锁并发访问共享资源。**
The use of {@code synchronized} methods or statements provides
* access to the implicit monitor lock associated with every object, but* forces all lock acquisition and release to occur in a block-structured way:* when multiple locks are acquired they must be released in the opposite* order, and all locks must be released in the same lexical scope in which* they were acquired.* synchronized释放锁的顺序必须是获取锁的相反顺序。**
While the scoping mechanism for {@code synchronized} methods
* and statements makes it much easier to program with monitor locks,* and helps avoid many common programming errors involving locks,* there are occasions where you need to work with locks in a more* flexible way. For example, some algorithms for traversing* concurrently accessed data structures require the use of* "hand-over-hand" or "chain locking": you* acquire the lock of node A, then node B, then release A and acquire* C, then release B and acquire D and so on. Implementations of the* {@code Lock} interface enable the use of such techniques by* allowing a lock to be acquired and released in different scopes,* and allowing multiple locks to be acquired and released in any* order.* 一般来说在使用synchronized来加锁会比较方便,减少出错的频率。* 但Lock显示锁的灵活性会很高。**
With this increased flexibility comes additional
* responsibility. The absence of block-structured locking removes the* automatic release of locks that occurs with {@code synchronized}* methods and statements. In most cases, the following idiom* should be used:**
{@code
* Lock l = ...;* l.lock();* try {* // access the resource protected by this lock* } finally {* l.unlock();* }}
*
* When locking and unlocking occur in different scopes, care must be
* taken to ensure that all code that is executed while the lock is
* held is protected by try-finally or try-catch to ensure that the
* lock is released when necessary.
* Lock灵活性大,那么出错的几率高,使用Lock锁的时候要记得释放锁。
*
*
{@code Lock} implementations provide additional functionality
* over the use of {@code synchronized} methods and statements by
* providing a non-blocking attempt to acquire a lock ({@link
* #tryLock()}), an attempt to acquire the lock that can be
* interrupted ({@link #lockInterruptibly}, and an attempt to acquire
* the lock that can timeout ({@link #tryLock(long, TimeUnit)}).
* 获取锁是非阻塞的。能被中断。可以设置超时。
*
*
A {@code Lock} class can also provide behavior and semantics
* that is quite different from that of the implicit monitor lock,
* such as guaranteed ordering, non-reentrant usage, or deadlock
* detection. If an implementation provides such specialized semantics
* then the implementation must document those semantics.
* 提高语义化,即知道哪里加锁了,哪里释放锁。
*
*
Note that {@code Lock} instances are just normal objects and can
* themselves be used as the target in a {@code synchronized} statement.
* Acquiring the
* monitor lock of a {@code Lock} instance has no specified relationship
* with invoking any of the {@link #lock} methods of that instance.
* It is recommended that to avoid confusion you never use {@code Lock}
* instances in this way, except within their own implementation.
* 建议在使用的时候不要使用Lock实例作为内置锁,因为会导致混乱。
* 与真正的Lock锁混乱。
*
*
Except where noted, passing a {@code null} value for any
* parameter will result in a {@link NullPointerException} being
* thrown.
*
*
Memory Synchronization
* 实现内存可见性
*
*
All {@code Lock} implementations must enforce the same
* memory synchronization semantics as provided by the built-in monitor
* lock, as described in
*
* The Java Language Specification (17.4 Memory Model): * * A successful {@code lock} operation has the same memory * synchronization effects as a successful Lock action.
* A successful {@code unlock} operation has the same * memory synchronization effects as a successful Unlock action.
*
*
* Unsuccessful locking and unlocking operations, and reentrant
* locking/unlocking operations, do not require any memory
* synchronization effects.
*
*
Implementation Considerations
* 根据具体的类来实现。
*
*
The three forms of lock acquisition (interruptible,
* non-interruptible, and timed) may differ in their performance * characteristics, ordering guarantees, or other implementation * qualities. Further, the ability to interrupt the ongoing
* acquisition of a lock may not be available in a given {@code Lock}
* class. Consequently, an implementation is not required to define
* exactly the same guarantees or semantics for all three forms of
* lock acquisition, nor is it required to support interruption of an
* ongoing lock acquisition. An implementation is required to clearly
* document the semantics and guarantees provided by each of the
* locking methods. It must also obey the interruption semantics as
* defined in this interface, to the extent that interruption of lock
* acquisition is supported: which is either totally, or only on
* method entry.
*
*
As interruption generally implies cancellation, and checks for
* interruption are often infrequent, an implementation can favor responding
* to an interrupt over normal method return. This is true even if it can be
* shown that the interrupt occurred after another action may have unblocked
* the thread. An implementation should document this behavior.
*
* @see ReentrantLock
* @see Condition
* @see ReadWriteLock
*
* @since 1.5
* @author Doug Lea
*/
public interface Lock {
简单总结下就是:
1、Lock方式来获取锁支持中断、超时不获取、是非阻塞的。
2、提高了语义化,哪里加锁,哪里解锁都得写出来。
3、Lock显式锁可以给我们带来很好的灵活性,但同时我们必须手动释放锁。
4、支持Condition条件对象。
5、允许多个读线程同时访问共享资源。
synchronized锁和Lock锁使用哪个
Lock锁在刚出来的时候很多性能方面都比Synchronized锁要好,但是从JDK1.6开始Synchronized锁就做了各种的优化。
所以,到现在Lock锁和Synchronized锁的性能其实差别不是很大!而Synchronized锁用起来又特别简单。Lock锁还得顾忌到它的特性,要手动释放锁才行(如果忘了释放,这就是一个隐患)。
所以说,我们绝大部分时候还是会使用Synchronized锁,用到了Lock锁提及的特性,带来的灵活性才会考虑使用Lock显式锁。
公平锁:线程将按照它们发出请求的顺序来获取锁。
非公平锁:线程发出请求的时可以“插队”获取锁。
Lock和synchronize都是默认使用非公平锁的。如果不是必要的情况下,不要使用公平锁。
公平锁会来带一些性能的消耗的。