参考 / References :https://blog.csdn.net/mulanlong/article/details/84566016
最近刷leetcode时刷到了concurrency题目,所以深入学习下synchronized
1.概念
synchronized可以修饰代码块,方法,静态类和类
2.修饰代码块:
public class SyncThread implements Runnable {
private static int count;
public SyncThread() {
count = 0;
}
@Override
public void run() {
synchronized (this) {
for(int i = 0; i < 5; ++i) {
try {
System.out.println(Thread.currentThread().getName() + " : " + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SyncThread thread = new SyncThread();
Thread thread1 = new Thread(thread, "1");
Thread thread2 = new Thread(thread, "2");
thread1.start();
thread2.start();
}
}
结果输出如下:
1 : 0
1 : 1
1 : 2
1 : 3
1 : 4
2 : 5
2 : 6
2 : 7
2 : 8
2 : 9
由于两个子线程都是访问同一个syncThread,所以必须等线程1访问完,释放对象锁后,线程2才能访问。
如果把代码改成访问两个不同的线程:
public static void main(String[] args) {
SyncThread thread = new SyncThread();
SyncThread threadd = new SyncThread();
Thread thread1 = new Thread(thread, "1");
Thread thread2 = new Thread(threadd, "2");
thread1.start();
thread2.start();
}
结果输出如下:
1 : 0
2 : 1
2 : 2
1 : 3
2 : 4
1 : 5
2 : 6
1 : 7
2 : 8
1 : 9
可以看到线程1与线程2并发执行了。synchronized只锁定对象,每个对象只有一个锁与之关联,所以修改后代码中的两把锁不会形成互斥。
synchronized(this):代表使用类生成的对象作为锁,同时以对象为阻塞。
synchronized(SyncThread.class): 代表使用类的字节码作为锁,所有的对象都公用一个锁,线程会阻塞。
修改后代码如下:
@Override
public void run() {
synchronized (SyncThread.class) {
for(int i = 0; i < 5; ++i) {
try {
System.out.println(Thread.currentThread().getName() + " : " + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
结果输出如下:
1 : 0
1 : 1
1 : 2
1 : 3
1 : 4
2 : 5
2 : 6
2 : 7
2 : 8
2 : 9
如果此时在此类中new一个对象锁,代码如下:
private Object lock = new Object();
@Override
public void run() {
synchronized (lock) {
for(int i = 0; i < 5; ++i) {
try {
System.out.println(Thread.currentThread().getName() + " : " + count++);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果如下:
2 : 0
1 : 1
1 : 2
2 : 3
1 : 5
2 : 4
1 : 7
2 : 6
1 : 8
2 : 8
可以看到由于new一个新的对象的时候,也同时new一个新的锁,所以两个线程并没有公用一把锁,因此线程会交替执行。如果线程分别来自两个对象,还会出现数据异常(指的是两个线程同时访问count导致数据还没来得及跟新)。
倘若上述代码中的lock是static修饰的呢?
结果如下:
2 : 0
2 : 1
2 : 2
2 : 3
2 : 4
1 : 5
1 : 6
1 : 7
1 : 8
1 : 9
由于static有如下特性:
① static 修饰的成员(字段/方法),随着所在类的加载而加载
当 JVM 把字节码加载进入 JVM 的时候,static 修饰的成员已经在内存中了
② 优先于对象的存在
对象是被手动通过 new 关键字创造出来的
③ static 修饰的成员被该类型的所有对象所共享
根据该类创建出来的任何对象,都可以访问 static 成员。
所以该static修饰的lock是所有对象共享的,因此必须等一个线程执行完,另一个线程才能执行。
3.可用volatile字段修饰private volatile int count;这样可以锁上全部,volatile修饰的关键字一旦修改会使得线程中的变量缓存失效,重新从数据中读取。
4.还可以对函数上锁:
private static int count;
public SyncThread() {
count = 0;
}
public synchronized int setCount() {
this.count++;
return count;
}
@Override
public void run() {
for(int i = 0; i < 5; ++i) {
try {
System.out.println(Thread.currentThread().getName() + " : " + setCount());
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SyncThread thread = new SyncThread();
SyncThread threadd = new SyncThread();
Thread thread1 = new Thread(thread, "1");
Thread thread2 = new Thread(threadd, "2");
thread1.start();
thread2.start();
}
结果如下:
1 : 1
2 : 2
1 : 3
2 : 4
2 : 5
1 : 6
2 : 7
1 : 8
2 : 9
1 : 10