Synchronized理解
上一篇已经对线程的创建方式进行了介绍
这次LK来聊一聊synchronized
首先synchronized是什么?
百度百科
- 代表这个方法加锁,相当于不管哪一个线程(例如线程A),运行到这个方法时,都要检查有没有其它线程B(或者C、 D等)正在用这个方法(或者该类的其他同步方法),有的话要等正在使用synchronized方法的线程B(或者C 、D)运行完这个方法后再运行此线程A,没有的话,锁定调用者,然后直接运行
- ava语言的关键字,可用来给对象和方法或者代码块加锁,当它锁定一个方法或者一个代码块的时候,同一时刻最多只有一个线程执行这段代码。
总结:synchronized是每个对象的内置锁,万物皆对象,一切对象都有自己的锁,内置锁,synchronized是它的具体使用方式。同一时刻只有一个线程访问或者代码块,说明synchronized是互斥锁。
接着聊一聊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 < 10000; i++) {
count++;
}
}
}
运行结果:
第一次:result:71020
第二次:result:89861
第三次:result:88757
...........
可以看到每次运行的结果都不同,为什么呢?
猜想:一个线程在对count++时,另一个线程在前一个线程执行的过程中获得了此时的count,在它基础上进行++操作,以此类推。
从java内存模型来看看产生此问题的原因
主内存中存在变量a,线程A获取到主内存中的变量a对变量a执行写操作,此时主内存没有及时更新线程a对变量a的改变,线程b获取到变量a的值也就没有改变。
先来理解一下两个概念
-
主内存
所有的线程所共享的
-
工作内存
每个线程自己有一个
总结:从上面可以看出问题出现在如何保证读写操作时数据的一致性。
来看看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 synchronized void run() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
运行结果:
result:82889
result:68751
result:70303
...........
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);
}
public void run() {
String a = "1";
// 类锁
synchronized (a) {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
}
运行结果:
result:100000
result:100000
result:100000
........
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() {
synchronized (SynchronizedDemo.class) {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
}
运行结果:
result:100000
result:100000
result:100000
........
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() {
synchronized (this) {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
}
运行结果:
result:54202
result:54024
result:63947
...
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() {
count();
}
//类锁
public static synchronized void count() {
for (int i = 0; i < 10000; i++) {
count++;
}
}
}
运行结果:
result:100000
result:100000
result:100000
...
总结:以上是synchronized 加锁方式,synchronized 给方法加锁,给静态代码块加锁。作为对象锁,每一个对象都有锁,对象改变锁也跟着改变,所以作为对象锁在上面例子中不能保证数据一致性。作为类锁,包括锁住静态方法,锁住字节码类对象,变量作为锁。
这些锁的对象一旦初始化,在内存中只会有一份,所以类锁可以保证数据一致性。
问题又来了,给对象加锁,那么对象锁又是什么,又在哪?
存在对象头中
对象头中的信息
-
Mark Word
存放的对象的Hashcode,分代年龄和锁标记位 -
Class Metadata Address(指向类的指针)
Java对象的类数据保存在方法区
-
Array Length(数组长度)
只有数组对象保存了这部分数据
jvm层面来理解锁竞争状态 -
偏向锁
每次线程进入时,markword会检查是不是同一个线程,是的话会让它直接进入,不是的话,其它线程开始抢锁,抢锁成功改变markword中线程id为当前线程id。抢锁失败,多次尝试后会膨胀为轻量级锁
至于抢锁过程涉及(CAS操作)以后会接着说。
-
轻量级锁
多个线程可以同时竞争锁,竞争不到就会产生自旋状态,一直尝试抢锁。
-
重量级锁
自旋多次如果仍未抢到锁,那么所有线程就会阻塞,等待当前线程释放锁,其它线程再去竞争。
总结:
synchronized就是重量级锁,其它线程只能等待当前线程释放锁,它才能去竞争获得锁。
synchronized常用来解决共享变量。