Java多线程研究02-对象锁,synchronized关键字详解
Posted zczpeng
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java多线程研究02-对象锁,synchronized关键字详解相关的知识,希望对你有一定的参考价值。
对象锁的概念
通过图理解一下,多个线程要操作同一个对象的场景
图1:
图2:
图3:
解释:
● 钥匙是什么,可以理解为线程的执行资格,拥有cpu时间片等资源。
● 可是对象的‘锁芯’(对象独占权)只有一个,那么可以打开这把锁的多个‘钥匙’同一时间内只能有一把‘钥匙’进行操作;其他持有‘钥匙’的线程(或者没有持有钥匙的线程)都要进入等待状态;直到某把‘钥匙’从‘锁眼’中退出,操作系统会决定哪一把‘钥匙’重新插入‘锁芯’,这就是常说的操作系统的线程切换。
● 某一个线程拥有一个对象的‘锁’的‘钥匙’,并不代表这个线程的‘钥匙’是插入了‘锁芯’的(有这个对象的控制权);但是,有权抢占‘锁芯’控制权的线程,必定拥有这个对象的‘钥匙’。
●请一定注意wait(time)的用法。很多人的理解都是:‘等待一段时间time,然后该线程激活继续工作’;但是实际上time更准确的含义应该是:到时检查。而整个wait(time)更准确的理解应该是:释放这个线程独占的X对象的锁芯(独占权),以便其它可以抢占‘锁芯’(独占权)的线程能够进行抢占,但是本线程继续持有X对象的锁的钥匙,等待time的时间后,重新参与‘锁芯’抢占(虽然不一定能够抢占得到)。
两个线程操作同一个对象时的流程描述
1,线程1首先访问对象A,进入synchronized并拿到锁,拥有A的独占权。
2,线程2此时也访问对象A,但发现此时发现A的独占权已被其他线程占有,所以只能堵塞等待。
3,线程1对A的操作完成,归还钥匙并释独占资源,线程1对A的操作结束。
4,线程2发现A的独占权被释放,此时线程2由堵塞状态变为就绪状态,通过操作系统线程切换,线程2获得cpu执行资源(获得钥匙),并插入A的锁芯,拥有对A的独占权。
5,线程2执行完毕,归还钥匙释放A的独占权,线程2对A的操作结束。
通过代码来理解一下
package com.zczpeng.thread;
public class SynchronizeTest
private static final Object THRAD_LOCK = new Object();
public static void main(String[] args)
Thread threadA = new Thread(new Runnable()
@Override
public void run()
synchronized (SynchronizeTest.THRAD_LOCK)
System.out.println("线程A执行了");
);
Thread threadB = new Thread(new Runnable()
@Override
public void run()
synchronized (SynchronizeTest.THRAD_LOCK)
System.out.println("线程B执行了");
);
threadA.start();
threadB.start();
synchronized可标注的位置
在JAVA中synchronized关键字可以加载很多位置。可以在一个方法定义上加synchronized关键字、也可以在方法体中加synchronized关键字、还可以在static块中加synchronized关键字。以下的代码都是正确的:
// 代码片段1
static
synchronized(ThreadLock.class)
// 代码片段2
public synchronized void someMethod()
// 代码片段3
public synchronized static void someMethod()
// 代码片段4
public static void someMethod()
synchronized (ThreadLock.class)
// 代码片段5
public void someMethod()
synchronized (ThreadLock.class)
但是不同位置的synchronized的关键字,代表的含义是不一样的
● synchronized关键字加载在非静态方法上时:
其代表的含义和synchronized(this)的意义相同。即对所拥有这个方法的对象进行锁状态检查。
● synchronized关键字加载在静态方法上时:
其代表的含义和synchronized(Class.class)的意义相类似。即对所拥有这个方法的类进行锁状态检查
synchronized关键字的使用
在前面的文章中我们主要讲解的是线程中“对象锁”的工作原理和操作方式。在讲解synchronized关键字的时候,我们还提到了synchronized关键字可以标注的位置。我们经常可以看到如下代码,将synchronized关键字加载到代码的方法体上,然后说,这个操作是线程安全的。
/**
* 这个类的class对象进行检查。
*/
public static synchronized void doSomething()
/**
* 对这个类的实例化对象进行检查
*/
public synchronized void doOtherthing()
但事实上,一个对象是否是线程安全的除了添加synchronized关键字以外,更重要的还要看如何进行这个对象的操作。如下代码中,我们展示了在两个线程的doOtherthing方法(所谓的线程安全方法),去操作一个对象VALUE:
public class SyncThread implements Runnable
private Integer value;
private static Integer VALUE;
public SyncThread(int value)
this.value = value;
/**
* 对这个类的实例化对象进行检查
*/
private synchronized void doOtherthing()
VALUE= this.value;
System.out.println("当前VALUE的值"+VALUE);
@Override
public void run()
Thread currentThread = Thread.currentThread();
Long id = currentThread.getId();
this.doOtherthing();
public static void main(String[] args) throws Exception
Thread syncThread1 = new Thread(new SyncThread(10));
Thread syncThread2 = new Thread(new SyncThread(100));
syncThread1.start();
syncThread2.start();
从Debug的情况来看,可能出现静态对象VALUE的值出现了脏读的情况:
[Thread-1]当前VALUE的值:100
[Thread-0] 当前VALUE的值:100
以下是代码出现bug的原因:
● syncThread1对象和syncThread2对象是SyncThread类的两个不同实例。“private synchronized void doOtherthing()”方法中的synchronized关键字实际上进行同步检查目标是不一样的。
● 当然为了对这个类(SyncThread)的class对象进行同步检查,无需在静态方法上标注synchronized关键字,只要标注SyncThread的class对象进行同步检查。
private void doOtherthing()
synchronized (SyncThread.class)
VALUE = this.value;
System.out.println("当前VALUE的值"+VALUE);
所以,一个对象是否是线程安全的除了添加synchronized关键字以外,更重要的还要看如何进行这个对象的操作;标注了synchronized关键字的方法中,针对某个对象的操作不一定是线程安全的!
以上是关于Java多线程研究02-对象锁,synchronized关键字详解的主要内容,如果未能解决你的问题,请参考以下文章
深入研究 Java Synchronize 和 Lock 的区别与用法