目录
状态对象
如果一个对象有被修改的成员变量 被称为有状态的对象相反如果没有可被修改的成员变量 称为无状态的对象
示例
public class MyThreadTest {
public static void main(String[] args) {
Runnable r = new MyThread();
Thread t1 = new Thread(r);
Thread t2 = new Thread(r);
t1.start();
t2.start();
}
}
class MyThread implements Runnable {
/**
* 如果一个对象有被修改的成员变量 被称为有状态的对象
* 相反如果没有可被修改的成员变量 称为无状态的对象
*
* 由于两个线程同时访问有状态的对象 当一个线程x++完
* 此时另外一个线程又将x变成0 此时就会输出两次0
*/
int x;
@SneakyThrows
@Override
public void run() {
x = 0;
while (true) {
System.out.println("result: " + x++);
Thread.sleep((long) Math.random() * 1000);
if (x == 30) {
break;
}
}
}
}
/*
result: 0
result: 0
result: 1
result: 2
result: 3
result: 4
result: 5
result: 6
result: 7
result: 8
result: 9
....
*/
示例2
/**
* 类锁和对象锁互相不干扰 线程可以获取对象锁不影响其他的线程获取类锁 反之亦然
*/
public class MyThreadTest2 {
public static void main(String[] args) {
MyClass myClass = new MyClass();
MyClass myClass2 = new MyClass();
Thread t1 = new Thread1(myClass);
Thread t2 = new Thread2(myClass);
t1.start();
try {
System.out.println("name: "+Thread.currentThread().getName());
Thread.sleep(700);//睡眠main线程
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
}
}
class MyClass {
public synchronized void hello() {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}
public synchronized void world() {
System.out.println("world");
}
}
class Thread1 extends Thread {
private MyClass myClass;
public Thread1(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
myClass.hello();
}
}
class Thread2 extends Thread {
private MyClass myClass;
public Thread2(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
myClass.world();
}
}
输出
name: main
hello
world
将代码修为
Thread t1 = new Thread1(myClass);
// Thread t2 = new Thread2(myClass);
Thread t2 = new Thread2(myClass2);
输出
name: main
world
hello
结论:每个实例对象都有一个唯一的Monitor(锁)
synchronized修饰代码块
当我们用synchronized修饰代码块时字节码层面上是通过monitorenter和monitorexit指令来实现的锁的获取与释放动作。
/**
* 当我们使用synchronized关键字来修饰代码块时,
* 字节码层面上是通过monitorenter和monitorexit指令来实现的锁的获取与释放动作。
* monitorenter跟monitorexit 是一对多的关系
*
* 当线程进入到monitorenter指令后,线程将会持有Monitor对象,执行monitorexit指令,线程将会释放Monitor对象
*/
public class MyTest1 {
private Object object = new Object();
public void method() {
int i = 1;
/*
此处的不是只能锁object 所有的都可以
此处synchronized 它会尝试去获取该对象的锁 有执行无则阻塞
*/
synchronized (object) {
System.out.println("hello world!");
//当应用主动抛出异常此时字节码 会直接执行并且直接执行monitorexit解锁
throw new RuntimeException();
}
}
public void method2() {
synchronized (object) {
System.out.println("welcome");
}
}
}
synchronized代码块修饰多个成员对象和this对象
结论:synchronized代码块 锁定多个成员对象 和this对象 此时成员对象和this对象之间是互不影响的,只有当前代码块锁定的是同一个对象时才会等待。
注意: 成员属性对象加锁 若该类属于单例,那么该属性值全局并发修改始终以最新的值为主(volatile 关键字就是用来辅助线程读取最新的值 ),例如 A B C线程 线程修改(每次+1)某类的 sum =0 属性值 A最先修改为0+1 = 1 后续B接着修改就会是1+1 =2 以此类推 如果想让每个线程访问都是默认值0 需要使用Spring 的scope 的protype作用域 或者ThreadLocal 或者将其放置在方法中
/**
* synchronized代码块 锁定多个成员对象 和this对象 此时成员对象和this对象互不影响
*/
public class Test {
public static void main(String[] args) {
MyClass myClass = new MyClass();
Thread t1 = new Thread1(myClass);
Thread t2 = new Thread2(myClass);
Thread t3 = new Thread3(myClass);
t3.start();//5000
t1.start();//4000
try {
System.out.println("name: " + Thread.currentThread().getName());
Thread.sleep(700);//睡眠main线程
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start(); //t1 t2 t3
}
}
class MyClass {
final Object o1 = new Object();
final Object o2 = new Object();
public void hello() {
//只锁o1的对象 由于o1和o2 是不同的对象两个方法互不影响
synchronized (o1) {
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("hello");
}
public void world() {
//只锁o2的对象
synchronized (o2) {
System.out.println("world");
}
}
public synchronized void test() {
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("test");
}
}
class Thread1 extends Thread {
private MyClass myClass;
public Thread1(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
myClass.hello();
}
}
class Thread2 extends Thread {
private MyClass myClass;
public Thread2(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
myClass.world();
}
}
class Thread3 extends Thread {
private MyClass myClass;
public Thread3(MyClass myClass) {
this.myClass = myClass;
}
@Override
public void run() {
myClass.test();
}
}
name: main
world
hello
test
/*
0 iconst_1
1 istore_1
2 aload_0
3 getfield #3 <com/example/demo/com/concurrecy/concurrency3/MyTest1.object>
6 dup
7 astore_2
8 monitorenter
9 getstatic #4 <java/lang/System.out>
12 ldc #5 <hello world!>
14 invokevirtual #6 <java/io/PrintStream.println>
17 aload_2
18 monitorexit
19 goto 27 (+8)
22 astore_3
23 aload_2
24 monitorexit //22-26为了保证抛出异常也能释放锁
25 aload_3
26 athrow //IO流输出 存在这种异常情况发生
27 return
*/
/*
当加了throw new RuntimeException();
code 字节码生成的助记符 只生成一个monitorexit
0 iconst_1
1 istore_1
2 aload_0
3 getfield #3 <com/example/demo/com/concurrecy/concurrency3/MyTest1.object>
6 dup
7 astore_2
8 monitorenter
9 getstatic #4 <java/lang/System.out>
12 ldc #5 <hello world!>
14 invokevirtual #6 <java/io/PrintStream.println>
17 new #7 <java/lang/RuntimeException>
20 dup
21 invokespecial #8 <java/lang/RuntimeException.<init>>
24 athrow
25 astore_3
26 aload_2
27 monitorexit//此时在方法体中抛出了异常 异常结束 直接释放了锁
28 aload_3
29 athrow //表示该方法一定会以异常结束
*/
synchronized修饰方法
对于synchronized关键字修饰方法说,并没有出现monitorenter与monitorexit指令,而是出现一个ACC_SYNCHRONIZED标志。
/**
* 对于synchronized关键字修饰方法说,并没有出现monitorenter与monitorexit指令,而是出现一个ACC_SYNCHRONIZED标志。
* <p>
* JVM使用了ACC_SYNCHRONIZED访问标志来区分一个方法是否为同步方法;当方法被调用时,调用指令会检查该方法是否拥有ACC——SYNCHRONIZED标志
* 如果有,那么执行线程将会先持有方法所在对象的Monitor对象,然后再去执行方法体;在改方法执行期间,其他任何线程均无法在获取到这个
* monitor,当线程执行完该方法后,它会释放掉这个Monitor对象。
*/
public class MyTest2 {
public synchronized void method() {
System.out.println("hello world");
}
}
/*
0 getstatic #2 <java/lang/System.out>
3 ldc #3 <hello world>
5 invokevirtual #4 <java/io/PrintStream.println>
8 return
*/
而其对应的标识符如下
此时就没有通过monitorebter和moniterexit 来获取锁而是通过ACC_SYNCHRONIZED标识符来尝试获取锁
synchronized修饰静态方法
当synchronized修饰静态方法其实跟修饰成员方法一样 只不过方法标识符多了个ACC_STATIC,并且其锁的是类锁
/**
* 当synchronized修饰静态方法其实跟修饰成员方法一样 只不过方法标识符多了个ACC_STATIC
* 其次锁的是 类锁
*/
public class MyTest3 {
/**
* static静态方法不存在this局部变量
* 原因直接类名.就能调用
*/
public static synchronized void method() {
System.out.println("hello world!");
}
}
/*
0 getstatic #2 <java/lang/System.out>
3 ldc #3 <hello world!>
5 invokevirtual #4 <java/io/PrintStream.println>
8 return
*/
Monitor设计的概念
互斥与同步定义
关于“互斥”和“同步”的概念
- 答案很清楚了,互斥就是线程A访问了一组数据,线程BCD就不能同时访问这些数据,直到A停止访问了
- 同步就是ABCD这些线程要约定一个执行的协调顺序,比如D要执行,B和C必须都得做完,而B和C要开始,A必须先得做完。
synchronized底层原理
JVM中的同步是基于进入与退出监视器对象(管程对象)(Monitor)来实现的,每个对象实例都会有一个Monitor对象(每个Class生成时都会有且只有一个Monitor对象(锁) ), Monitor对象会和Java对象一同创建并销毁。Monitor对象是由C++来实现的。
当多个线程同时访问一段同步代码时,这些线程会被放到一个EntryList集合当中,处于阻塞状态(未获取对象锁 要区别WaitSet)的线程都会被放到该列表当中。 接下来,当线程获取到对象的Monito时,Monitor是依赖于底层操作系统的mutex lock(互斥锁)来实现互斥的,线程获取mutex成功。则会持有该mutex,这时其他线程就无法获取到该mutex.。
如果线程调用了wait方法(意思的调用wait方法才会进入WaitSet 竞争monitor时是和entryList 公平竞争),那么该线程就会释放掉所持有的mutex, 并且该线程会进入到WaitSet集合(等待集合)中,等待下一次被其他该对象锁线程调用notify/notifyAll唤醒(此处注意如果在WaitSet中被唤醒的线程没有竞争到锁该线程会进入entryList阻塞集合)。如果当前线程顺利执行完毕方法。那么它也会释放掉所持有的mutex.
用户态和内核态资源调度
总结一下:同步锁在这种实现方式当中,因为Monitor是依赖底层的操作系统实现,这样就存在用户态(如程序执行业务代码在用户端)与内核态(Monitor是依赖于底层操作系统 此时阻塞就是内核执行)之间的切换,所以会增加性能开销。 采用自旋作为回退机制当线程自旋时还是用户态占用的是CPU资源==(自旋太久也会造成CUP资源的浪费) 当自旋时间超过预期值还是会进入内核态。
通过对象互斥锁的概念来保证共享数据操作的完整性。每个对象都对应与一个可称为【互斥锁】的标记,这个标记用于保证在任何时刻,只能有一个线程访问该对象。
存在问题
那些处于EntryList与WaitSet中的线程均处于阻塞状态(两个集合都属于Monitor对象的成员变量),阻塞操作是由操作系统来完成的,在Linux下是通过pthread_mutex_lock函数实现的。 线程被阻塞后便会进入到内核调度状态,这会导致系统在用户态与内核态之间来回切换,严重影响锁的性能
解决方案
解决上述问题的办法便是自旋(Spin)。其原理是:当发生对Monitor的争用时,若Owner(拥有线程或BasicLock指针)能够在很短的时间内释放掉锁,则哪些正在争用的线程就可以稍微等待一下(即所谓的自旋),在Owner线程释放锁之后,争用线程可能会立刻获取到锁,从而避免了系统阻塞(内核态).不过,当Owner运行的时间超过了临界值后。争用线程自旋一段时间后依然无法获取到锁,这时争用线程则会停止自旋而进入到阻塞状态(内核态)。所有总体的思想:先自旋,不成功再进行阻塞,尽量降低阻塞的可能性,这对哪些执行时间很短的代码块来时有极大的性能提升。显然自旋在多处理器(多核心)上才有意义。
*
互斥锁属性
- PTHREAD_MUTEX_TIMED_NP: 这是省缺值,也就是普通锁,当一个线程加锁以后,其余请求锁的线程将会形成一个等待队列,并且在解锁后按照优先级获取到锁,这种策略可以确保资源分配的公平性。
- PTHREAD_MUTEX_RECURSIVE_NP:嵌套锁.允许一个线程对同一个锁成功获取多次,并通过unlock解锁。如果是不同线程请求,则在加锁线程解锁时重写进行竞争。
- PTHREAD_MUTEX_ERRORCHECK_NP:检错锁。如果一个线程请求同一把锁,则返回EDEADLK,否则与PTHREAD_MUTEX_TIMEDNP类型动作相同,这样就能保证了当不允许多次加锁时不会出现最简单的死锁。
- PTHREAD_MUTEX_ADAPTIVE_NP:适应锁.动作最简单的锁类型,仅仅等待解锁后重新竞争。
Monitor
JVM中的同步是基于进入与退出监视器对象(管程对象)(Monitor)来实现的,每个对象实例都会有一个Monitor对象(每个Class生成对象时都会有且只有一个Monitor对象(锁)伴生 ), Monitor对象会和Java对象一同创建并销毁。Monitor对象是由C++来实现的。
Monitor对象是啥?
OpenJDK地址
通过OpenJDK翻看JVM底层的一些C++代码
点击进入hpp后缀文件找到如下的方法
ObjectWaiter对当前线程的封装 底层通过链表来记录
//截取如下
class ObjectWaiter : public StackObj {
public:
enum TStates { TS_UNDEF, TS_READY, TS_RUN, TS_WAIT, TS_ENTER, TS_CXQ } ;
enum Sorted { PREPEND, APPEND, SORTED } ;
ObjectWaiter * volatile _next;//指向下一个ObjectWaiter
ObjectWaiter * volatile _prev;//指向上游的ObjectWaiter
Thread* _thread;
这么做的好处
我们可以从一个ObjectWaiter 知道其他ObjectWaiter 的位置可以根据对应的策略选择性的唤醒对应的ObjectWaiter 如首位 中间指定等。
当waitset中唤醒的线程没有获取到monitor 就会将唤醒的线程放到entryList(也是链表格式)当中当entryList当中拿到锁就将对应线程从entryList中移除
当没有遇到wait()方法时直接进入EntryList集合当中
注意:WaitSet线程只是那些调用了wait()的线程,而EntryList是用来存储阻塞线程。
Wait JVM底层核心代码解析
class ObjectMonitor {
public:
enum {
OM_OK, // no error 没有错误
OM_SYSTEM_ERROR, // operating system error 操作系统错误
OM_ILLEGAL_MONITOR_STATE, // IllegalMonitorStateException 异常
OM_INTERRUPTED, // Thread.interrupt()
OM_TIMED_OUT // Object.wait() timed out 超时
};
对应成员变量
// initialize the monitor, exception the semaphore, all other fields
// 初始化monitor,
// are simple integers or pointers
ObjectMonitor() {
_header = NULL;
_count = 0;
_waiters = 0,
_recursions = 0;//嵌套锁 递归嵌套
_object = NULL;
_owner = NULL;//拥有线程或BasicLock指针
_WaitSet = NULL;//wait等待集合
_WaitSetLock = 0 ; //自旋锁标识字段 保护等待队列
_Responsible = NULL ;
_succ = NULL ;
_cxq = NULL ;
FreeNext = NULL ;
_EntryList = NULL ; //阻塞集合
_SpinFreq = 0 ;
_SpinClock = 0 ;
OwnerIsThread = 0 ;
_previous_owner_tid = 0;
}
如下文档注释
protected:
ObjectWaiter * volatile _WaitSet; // LL of threads wait()ing on the monitor
//Monitor上的所有线程等待()集合
protected:
ObjectWaiter * volatile _EntryList ; // Threads blocked on entry or reentry.
//线程在进入或返回时被阻塞。
protected: // protected for jvmtiRawMonitor
void * volatile _owner; // pointer to owning thread OR BasicLock
//指向拥有线程或BasicLock的指针
由JVM底层C++代码和文档注释我们可知_WaitSet和_EntryList 其实是其Monitor对应的的成员变量 初始值都为NULL
在objectMonitor.cpp文件当中
如wait方法实际对应与java Object基类当中的wait(0)所对应的方法
// Wait/Notify/NotifyAll
//
// Note: a subset of changes to ObjectMonitor::wait()
// will need to be replicated in complete_exit above
void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) {
.....
ObjectWaiter node(Self);//被包装的线程节点
node.TState = ObjectWaiter::TS_WAIT ;
Self->_ParkEvent->reset() ;
OrderAccess::fence(); // ST into Event; membar ; LD interrupted-flag
// Enter the waiting queue, which is a circular doubly linked list in this case
//输入等待队列,在本例中是一个循环的双链接列表
// but it could be a priority queue or any data structure.
//但它可以是优先级队列或任何数据结构。(链表优势)
// _WaitSetLock protects the wait queue. Normally the wait queue is accessed only
//_WaitSetLock保护等待队列。通常只访问等待队列
// by the the owner of the monitor *except* in the case where park()
//由监视器的所有者*except*在park()的情况下
// returns because of a timeout of interrupt. Contention is exceptionally rare
//由于中断超时而返回。争论异常罕见
// so we use a simple spin-lock instead of a heavier-weight blocking lock.
//所以我们使用了一个简单的自旋锁,而不是一个更重的重量级锁。
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ;//自旋捕获 锁
AddWaiter (&node) ;//用来更换指针引用
......
exit (true, Self) ; // exit the monitor 退出monitor
更换内容如下
inline void ObjectMonitor::AddWaiter(ObjectWaiter* node) {
assert(node != NULL, "should not dequeue NULL node");
assert(node->_prev == NULL, "node already in list");
assert(node->_next == NULL, "node already in list");
// put node at end of queue (circular doubly linked list)
if (_WaitSet == NULL) {
_WaitSet = node;
node->_prev = node;
node->_next = node;
} else {
ObjectWaiter* head = _WaitSet ;
ObjectWaiter* tail = head->_prev;
assert(tail->_next == head, "invariant check");
tail->_next = node;
head->_prev = node;
node->_next = head;
node->_prev = tail;
}
}
在这我们看出当调用了waitSet方法时底层C++时,先进行SpinAcquire (自旋捕获)尝试获取锁,没获取到则将对应线程添加到waitSet当中以链表的形式,当完成上述操作时exit monitor
notify底层核心代码解析
void ObjectMonitor::notify(TRAPS) {
CHECK_OWNER();
if (_WaitSet == NULL) {//_WaitSet 为null 直接返回
TEVENT (Empty-Notify) ;
return ;
}
....
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ;
//DequeueWaiter 根据不同的调度策略获取waitSet集合链表中目标线程
ObjectWaiter * iterator = DequeueWaiter() ;
.....
if (Policy == 0) { // prepend to EntryList
if (List == NULL) {
iterator->_next = iterator->_prev = NULL ;
_EntryList = iterator ;
} else {
List->_prev = iterator ;
iterator->_next = List ;
iterator->_prev = NULL ;
_EntryList = iterator ; //此时如果目标线程未获取到monitor则放入ENtryList当中
}
总结:notify底层会先根据不同调度策略获取waitSet集合链表中目标线程,此时如果目标线程未获取到monitor则放入ENtryList当中
notifyAll底层核心代码解析
void ObjectMonitor::notifyAll(TRAPS) {
CHECK_OWNER();
ObjectWaiter* iterator;
if (_WaitSet == NULL) { //WaitSet null 直接返回
TEVENT (Empty-NotifyAll) ;
return ;
}
DTRACE_MONITOR_PROBE(notifyAll, this, object(), THREAD);
int Policy = Knob_MoveNotifyee ;
int Tally = 0 ;
Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notifyall") ;
for (;;) { //遍历所有
iterator = DequeueWaiter () ; //拿到对应的WaitSet 全部唤醒
if (iterator == NULL) break ;
TEVENT (NotifyAll - Transfer1) ;
++Tally ;
....
总结:notifyAll底层通过死循环唤醒WaitSet 所有的ObjectWaiter 目标线程,