首先说明,因本人水平有限(还在学习啦),目前还看不懂Java对象头中的数据,只明白有这样的一个数据结构,并知道他是干什么的。文章中会用到syncronized对于对象头中的Mark Word的细节就不讲啦,等我学会了单独写一章。
Java对象在运行时结构如下
指针压缩只在64位机上,Class Point原本应位64位,但是在压缩后只有32位,节省一般的内存空间,具体这里就不细说了。 而对象头中,存放有锁的标志位,记录对象的锁状态。
说了这么多,锁究竟是干什么的?
举个例子,一个寝室通常有5个同学,而每个寝室却一般只有一个厕所。当5个同学,都想使用厕所时(假如厕所一次只能进去一个人),进入厕所的同学第一步会先把厕所锁上,那个其他同学看到厕所锁了,有人使用,就会等待,等使用厕所的同学使用好了,开门,一起上,来抢夺这个厕所,抢到的同学继续锁门,其他同学继续等待,直到所有同学都使用完厕所。
其实,这里的同学就可以理解为线程,而厕所就是多个线程争夺的临界资源,线程争夺到临界资源后,会在临界资源对象的Mark Word上进行标记,其他线程看到临界资源对象的Mark Word上被标记“有人使用”了,就会等待,等锁释放之后,在抢夺临界资源对象,抢夺到的线程再标记,如此往复。
为什么要给对象加锁 java中的多线程其实是并发,即对于多个线程访问同一以资源,可能是在某个时间段里是Thread-1的计算时间,记录状态,某个时间段里是Thread-2的计算时间,记录状态......当然,这个计算时间不可能执行完成一个方法。就像上面说的厕所的例子,总不可能是同学A用一下厕所,刚刚脱下衣服,记录同学A的状态---同学B在用厕所,脱下衣服,记录状态---在有同学A用厕所.....这是不符合日常行为的,但在程序中,若不加锁,这就是真实存在的。
sycronized syncronized是java中的锁关键字,是java内的同步机制。当一个线程获得了锁候,其他线程若想获得这把锁就只能等待或阻塞。在jdk5以前,该关键字是仅有的同步手段。 syncronized关键字可以修饰方法、变量、和代码块。
- 修饰方法
syncronized void Function(){
//具体业务方法
}
等同于
void Function(){
syncronized(this){
//具体业务方法
}
}
即调用对象该方法的线程,去竞争当前对象的锁。
- 修饰静态方法
class Test{
syncronized static void Func(){
//具体业务方法
}
}
等同于
class Test{
static void Func(){
syncronized(Test.class){
//具体业务方法
}
}
}
即线程去竞争Test类的锁
- 修饰代码块
void Func(){
//前置方法
syncronized(XXX.class\instance){
//加锁方法
}
//后置方法
}
运行前置方法、后置方法因为没有上锁,所有线程随时可以执行,但是运行加锁方法,只有在线程竞争到锁后才能执行,没有竞争到的线程只能等待锁释放再一次尝试竞争锁。使用XXX.class线程会竞争XXX.class的锁,使用instance(对象的意思),线程会竞争instance对象的锁。
这里提一个问题:对于多个线程,A、B、C对XXX.class上锁,D对instance上锁,若instalce是有XXX.class new出来的,那么D会影响ABC线程等待或阻塞吗?(这里挖个坑,在例子中解答)。
举个例子
定义资源类型
class Resource{
void use(MyThread myThread){
System.out.println("线程"+myThread.getName()+"用了该资源");
}
}
定义线程
class MyThread implements Runnable{
private String name;
private Resource resource;
MyThread(Resource resource,String name){
this.resource = resource;
this.name = name;
}
public String getName() {
return name;
}
@Override
public void run() {
while (true){
try {
Thread.sleep((int)(Math.random()*1000d));
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource){
System.out.println("线程:"+name+"获得了资源,期间独占该资源");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resource.use(this);
System.out.println("线程:"+name+"用完了 休息");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程:"+name+"准备释放资源");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
main方法
/**
* 创建一个临界资源
* */
Resource resource = new Resource();
for (int i=0;i<5;i++){
new Thread(new MyThread(resource,"Thread-"+i)).start();
}
大家可以模拟运行一下,可以看到各个线程有序运行。 但是,你直到加锁代码块如果做如下改变,虽然程序不变,其中的细节明白吗?
synchronized (Object.class){
}
synchronized (Resource.class){
}
synchronized (resource){
}
其实,将()变化不影响代码运行的主要原因是,各个线程竞争的是同一把锁,synchronized (Object.class),每个线程都去竞争Object.class的锁。使用synchronized (Resource.class),每个线程都去竞争Resource.class的锁,使用synchronized (resource),每个线程都去竞争resource的锁。
synchronized(XXXX){}代码块加锁中XXX的改变,只会影响到竞争XXX的锁的线程, 假如在main方法中添加如下代码
new Thread(()->{
while (true){
try {
Thread.sleep(1001);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (resource){
System.out.println("大家休息5秒");
for (int i=1;i<6;i++){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
其他争夺resource的资源真的停了5秒,即大家休息5秒生效了,因为线程竞争的是同一把锁,即resource对象的锁。 倘若把这段代码修改为
new Thread(()->{
while (true){
try {
Thread.sleep(1001);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Object.class){
System.out.println("大家休息5秒");
for (int i=1;i<6;i++){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
不难发现,其实大家休息五秒的机制就失效了,因为休息的代码精致的是Object.class的锁,而其他线程竞争的是resources对象的锁,就好比914,915 2个寝室,914的同学洗澡肯定是竞争914的厕所的锁,而915的同学厕所锁不锁,和914的同学并没有关机,及时915的人大喊,测试不能用了.914的同学照样好好地在914用自己的厕所。
补坑: 如若将刚才添加的暂停代码快加锁修改为Resources会怎么样呢?
new Thread(()->{
while (true){
try {
Thread.sleep(1001);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (Resource.class){
System.out.println("大家休息5秒");
for (int i=1;i<6;i++){
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}).start();
resource是由Resource new出来的,最开始我也以为暂停方法对其他线程依然有效,其实不然,这里需要注意,我理解为Resource.class的锁和其对象的锁是分开的,互不影响。具体实现原理等我看完了知识点后会单独发一章(这里挖坑)。