1. synchronized简介
在学习知识前,我们先来看一个现象:
public class SynchronizedDemo implements Runnable {
private static int count = 0;
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(new SynchronizedDemo());
thread.start();
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("result: " + count);
}
@Override
public void run() {
for (int i = 0; i < 1000000; i++)
count++;
}
}
复制代码
开启了10个线程,每个线程都累加了1000000次,如果结果正确的话自然而然总数就应该是10 * 1000000 = 10000000。可就运行多次结果都不是这个数,而且每次运行结果都不一样。这是为什么了?有什么解决方案了?这就是我们今天要聊的事情。
在上一篇博文中我们已经了解了java内存模型的一些知识,并且已经知道出现线程安全的主要来源于JMM的设计,主要集中在主内存和线程的工作内存而导致的内存可见性问题,以及重排序导致的问题,进一步知道了happens-before规则。线程运行时拥有自己的栈空间,会在自己的栈空间运行,如果多线程间没有共享的数据也就是说多线程间并没有协作完成一件事情,那么,多线程就不能发挥优势,不能带来巨大的价值。那么共享数据的线程安全问题怎样处理?很自然而然的想法就是每一个线程依次去读写这个共享变量,这样就不会有任何数据安全的问题,因为每个线程所操作的都是当前最新的版本数据。那么,在java关键字synchronized就具有使每个线程依次排队操作共享变量的功能。很显然,这种同步机制效率很低,但synchronized是其他并发容器实现的基础,对它的理解也会大大提升对并发编程的感觉,从功利的角度来说,这也是面试高频的考点。好了,下面,就来具体说说这个关键字。
2. synchronized实现原理
在java代码中使用synchronized可是使用在代码块和方法中,根据synchronized用的位置可以有如表3.1这些使用场景:
使用位置 | 作用范围 | 被锁的对象 | 示例代码 |
---|---|---|---|
方法 | 实例方法 | 类的实例对象 | public synchronized void method() { .......} |
静态方法 | 类对象 | public static synchronized void method1() { .......} | |
代码块 | 实例对象 | 类的实例对象 | synchronized (this) { .......} |
class对象 | 类对象 | synchronized (SynchronizedScopeDemo.class) { .......} | |
任意实例对象object | 实例对象object | final String lock = "";synchronized (lock) { .......} |
如表所示synchronized可以用在方法上也可以使用在代码块中,方法是实例方法和静态方法分别锁的是该类的实例对象和该类的对象。而使用在代码块中根据锁的目标对象
也可以分为三种,具体的可以看表数据。这里的需要注意的是如果锁的是类对象的话,尽管new多个实例对象,依然会被锁住。synchronized的使用起来很简单,那么背后的原理以及实现机制是怎样的呢?
1 对象锁(monitor)机制
现在来进一步分析synchronized的具体底层实现,有如下一个简单的示例代码:
public class SynchronizedDemo {
public static void main(String[] args) {
synchronized (SynchronizedDemo.class) {
System.out.println("hello synchronized!");
}
}
}
复制代码
上述代码通过synchronized“锁住”当前类对象来进行同步,将java代码进行编译之后通过javap -v SynchronizedDemo .class来查看对应的main方法字节码如下:
public static void main(java.lang.String[]);
• descriptor: ([Ljava/lang/String;)V
• flags: ACC_PUBLIC, ACC_STATIC
• Code:
• stack=2, locals=3,