synchronized
关键字在需要原子性、可见性和有序性这三种特性的时候都可以作为其中一种解决方案,看起来是“万能”的。的确,大部分并发控制操作都能使用synchronized来完成。
上面这段话出自《深入理解Java虚拟机》,在并发编程中,经常会用到synchronized,下面来看一下synchronized的具体细节
可以同步方法或同步代码块,被修饰的部分,在同一时间只能被单线程访问
public class Concurrent {
//同步方法
public synchronized void syn1()
{
System.out.println("this is synchronized method !");
}
//同步代码块
public void syn2()
{
System.out.println("this is not synchronized block !");
synchronized (Concurrent.class)
{
System.out.println("this is synchronized block !");
}
}
}
目的:想要保证一个共享资源在同一时间只会被一个线程访问到时
对于同步方法,JVM采用ACC_SYNCHRONIZED
标记符来实现同步。
ACC_SYNCHRONIZED
标志,当被访问时,首先检查该标志;若有该标志,需要先获得监视器锁,然后开始执行方法,执行后释放锁对于同步代码块,JVM采用monitorenter
、monitorexit
两个指令来实现同步
monitorenter
可以看作加锁,monitorexit
看作释放锁
每个对象维护一个记录加锁次数的计数器
同一个线程多次获得同一个对象锁时,计数器可以自增多次,释放锁时,计数器自减,直至为0,才释放锁
以上都是基于Monitor
实现的,在Java虚拟机(HotSpot)中,Monitor
是基于C++实现的,由ObjectMonitor
实现
在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter
和monitorexit
这两个字节码指令,在Java中对应的关键字就是synchronized
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
保证可见性的一条规则:对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值
这就保证了synchronized
关键字的可见性
synchronized
是无法禁止指令重排和处理器优化的
Java程序中天然的有序性可以总结为一句话:如果在本线程内观察,所有操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无序的
不管怎么重排序(编译器和处理器为了提高并行度),单线程程序的执行结果都不能被改变
因为被synchronized
锁住的期间,只有一个线程可以进行访问,可以认为保证了有序性
JDK1.6之后对锁进行了很多优化,出现了轻量级锁、偏向锁、锁消除、适应性自旋锁和锁粗化
自旋锁
自适应自旋
锁消除
锁粗化(膨胀)
轻量级锁
设计初衷:没有多线程竞争的前提下,减少传统重量级锁使用操作系统互斥量产生的性能消耗
偏向锁
以ReentrantLock为例,实现过程可以归纳为:
state
import java.util.concurrent.locks.ReentrantLock;
public class Concurrent {
public static void main(String[] args) throws InterruptedException {
final int[] counter = {0};
ReentrantLock lock = new ReentrantLock();
for(int i = 0;i<50;i++)
{
new Thread(new Runnable() {
@Override
public void run() {
lock.lock();
try{
int a = counter[0];
counter[0] = a+1;
System.out.println( Thread.currentThread().getName()+".counter is:"+counter[0]);
}
finally {
lock.unlock();
}
}
}).start();
}
Thread.sleep(5000);
System.out.println(counter[0]);
}
}
开50个线程同时更新counter,输出结果如下:
Thread-0.counter is:1
Thread-4.counter is:2
Thread-5.counter is:3
Thread-1.counter is:4
Thread-3.counter is:5
Thread-15.counter is:6
Thread-13.counter is:7
Thread-8.counter is:8
Thread-9.counter is:9
Thread-11.counter is:10
Thread-12.counter is:11
Thread-14.counter is:12
Thread-16.counter is:13
Thread-17.counter is:14
Thread-19.counter is:15
Thread-18.counter is:16
Thread-20.counter is:17
Thread-21.counter is:18
Thread-22.counter is:19
Thread-24.counter is:20
Thread-23.counter is:21
Thread-26.counter is:22
Thread-25.counter is:23
Thread-27.counter is:24
Thread-28.counter is:25
Thread-29.counter is:26
Thread-30.counter is:27
Thread-31.counter is:28
Thread-32.counter is:29
Thread-33.counter is:30
Thread-36.counter is:31
Thread-34.counter is:32
Thread-35.counter is:33
Thread-37.counter is:34
Thread-10.counter is:35
Thread-39.counter is:36
Thread-40.counter is:37
Thread-41.counter is:38
Thread-42.counter is:39
Thread-44.counter is:40
Thread-43.counter is:41
Thread-46.counter is:42
Thread-45.counter is:43
Thread-48.counter is:44
Thread-47.counter is:45
Thread-49.counter is:46
Thread-2.counter is:47
Thread-38.counter is:48
Thread-6.counter is:49
Thread-7.counter is:50
50
/**
* Creates an instance of {@code ReentrantLock}.
* This is equivalent to using {@code ReentrantLock(false)}.
*/
public ReentrantLock() {
sync = new NonfairSync();
}
/** Synchronizer providing all implementation mechanics */
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable
This is equivalent to using {@code ReentrantLock(false)}.
观察源码发现这个类就是一个 双向链表
数据结构主要包括:一个 int类型状态 + 双向链表
public void lock() {
sync.acquire(1);
}
/**
* Acquires in exclusive mode, ignoring interrupts. Implemented
* by invoking at least once {@link #tryAcquire},
* returning on success. Otherwise the thread is queued, possibly
* repeatedly blocking and unblocking, invoking {@link
* #tryAcquire} until success. This method can be used
* to implement method {@link Lock#lock}.
*
* @param arg the acquire argument. This value is conveyed to
* {@link #tryAcquire} but is otherwise uninterpreted and
* can represent anything you like.
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
private Node addWaiter(Node mode) {
Node node = new Node(mode);
for (;;) {
Node oldTail = tail;
if (oldTail != null) {
node.setPrevRelaxed(oldTail);
//cas操作,在线程安全的前提下,将当前线程加入到链表尾部
if (compareAndSetTail(oldTail, node)) {
oldTail.next = node;
return node;
}
} else {
initializeSyncQueue();
}
}
}
final boolean acquireQueued(final Node node, int arg) {
boolean interrupted = false;
try {
for (;;) {
final Node p = node.predecessor();
//当前线程到达头部,尝试CAS更新锁状态,更新成功则该等待线程可以从头部移除
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node))
interrupted |= parkAndCheckInterrupt();
}
} catch (Throwable t) {
cancelAcquire(node);
if (interrupted)
selfInterrupt();
throw t;
}
}
/**
* Attempts to release this lock.
*
* If the current thread is the holder of this lock then the hold
* count is decremented. If the hold count is now zero then the lock
* is released. If the current thread is not the holder of this
* lock then {@link IllegalMonitorStateException} is thrown.
*
* @throws IllegalMonitorStateException if the current thread does not
* hold this lock
*/
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
protected boolean tryRelease(int arg) {
throw new UnsupportedOperationException();
}
最后通过调用NonfairSync.tryRelease()
实现
释放锁就是对AQS中状态值state
进行修改,同时更新下一个链表中的线程等待节点
lock锁带有大量的CAS+自旋,在低锁冲突下使用,如果锁冲突很多,自旋操作很多,会明显降低效率,还不如直接加synchronized
重量级锁
1.前者是非公平锁,后者可以实现公平锁
2.sync是通过操作Mark Word来对字节码进行添加修改指令实现的,是原生语法,需要JVM的支持。lock是通过双向链表结合状态量+cas操作来实现的实现,是API层面的
3.sync锁方便简单,由编译器来保障加锁和释放;lock要手动声明加锁和释放,要记得在try/catch结构中的finally中释放锁
4.sync锁不够灵活,按块锁住粒度很粗;lock灵活度高,由编码人员控制,锁的粒度很细
5.lock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
参考自:https://mp.weixin.qq.com/s?__biz=MzI3NzE0NjcwMg==&mid=2650122072&idx=1&sn=63690ad2cbf2b5390c3d8e1953ffbacf&chksm=f36bba79c41c336fbea8b56289fc2a71e829042f6c3616e3ba051c2542b48f0a3936e3d852f6&mpshare=1&scene=1&srcid=0225xcUOCP6bBS8aCrcd1jBd#rd