synchronized简介
Java平台中的任何一个对象都有一个与之关联的锁。这种锁被称为监视锁或者内部锁。 内部锁是一种排他锁,它能够保障原子性、可见性和有序性。
synchronized使用
synchronized关键字可以用来修饰方法及代码块,它有如下应用方式:
- 同步普通方法,锁的是当前类实例对象
- 同步静态方法,锁的是当前类的class对象
- 同步代码块,锁是括号里面的对象
同步普通方法
同步普通方法时,锁的是实例对象。我们先看下不加Synchronized修饰时情况:
public class SynchronizedDemo {
public void test(){
for (int i = 0; i < 3; i++) {
System.out.println("当前执行线程:" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedDemo sd = new SynchronizedDemo();
new Thread(() -> {
sd.test();
}, "线程1").start();
new Thread(() -> {
sd.test();
}, "线程2").start();
}
}
执行结果如下:
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程1
我们看到方法没有加同步时,两个线程是可以同时执行的。我们把方法加上synchronized修饰:
public synchronized void test(){
for (int i = 0; i < 3; i++) {
System.out.println("当前执行线程:" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
执行结果如下:
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程2
可以看到这边产生了锁竞争,谁先抢到谁先执行,我们需要注意的是,两个线程调用方法时必须是同一个对象。我们看下下面这种情况:
public class SynchronizedDemo {
public synchronized void test(){
for (int i = 0; i < 3; i++) {
System.out.println("当前执行线程:" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 实例对象sd1
SynchronizedDemo sd1 = new SynchronizedDemo();
// 实例对象sd2
SynchronizedDemo sd2 = new SynchronizedDemo();
new Thread(() -> {
// 调用实例sd1的方法
sd1.test();
}, "线程1").start();
new Thread(() -> {
// 调用实例sd2的方法
sd2.test();
}, "线程2").start();
}
}
执行结果如下:
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程1
当前执行线程:线程2
这边线程1和线程2分别调用了同一个类的不同对象,所以他们竞争的是不同的锁,当然可以同时执行喽。
同步静态方法
同步静态方法时,锁的是类对象(Class对象)。下面是静态方法加上synchronized的代码:
public class SynchronizedDemo {
public synchronized static void testStatic(){
for (int i = 0; i < 3; i++) {
System.out.println("当前执行线程:" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
SynchronizedDemo sd = new SynchronizedDemo();
new Thread(() -> {
sd.testStatic();
}, "线程1").start();
new Thread(() -> {
sd.testStatic();
}, "线程2").start();
}
}
执行结果显示同步生效了,我们再看下调用不同实例的情况:
public class SynchronizedDemo {
public synchronized static void testStatic(){
for (int i = 0; i < 3; i++) {
System.out.println("当前执行线程:" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
// 实例对象sd1
SynchronizedDemo sd1 = new SynchronizedDemo();
// 实例对象sd2
SynchronizedDemo sd2 = new SynchronizedDemo();
new Thread(() -> {
// 实例对象sd1的静态方法
sd1.testStatic();
}, "线程1").start();
new Thread(() -> {
// 实例对象sd2的静态方法
sd2.testStatic();
}, "线程2").start();
}
}
执行结果如下:
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程2
这边两个线程虽然调用是不同一个实例的方法,但别忘了,这边锁的是我们的类对象(Class对象),显然这边两个实例是属于同一个类对象的,所以它们需要竞争同一把锁。
同步代码块
我们看下同步代码块的使用方式:
public class SynchronizedDemo {
public void test(){
synchronized(this){
for (int i = 0; i < 3; i++) {
System.out.println("当前执行线程:" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SynchronizedDemo sd = new SynchronizedDemo();
new Thread(() -> {
sd.test();
}, "线程1").start();
new Thread(() -> {
sd.test();
}, "线程2").start();
}
}
执行结果如下:
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程
我们这边用到了synchronized(this),其实这边跟同步普通方法类似,锁住的是当前对象。当然我们也可以自定义一个锁对象:
public class SynchronizedDemo {
Object lock = new Object();
public void test(){
synchronized(lock){
for (int i = 0; i < 3; i++) {
System.out.println("当前执行线程:" + Thread.currentThread().getName());
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) {
SynchronizedDemo sd = new SynchronizedDemo();
new Thread(() -> {
sd.test();
}, "线程1").start();
new Thread(() -> {
sd.test();
}, "线程2").start();
}
}
效果是一样的:
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程1
当前执行线程:线程2
当前执行线程:线程2
当前执行线程:线程2
synchronized用法总结
对于synchronized的使用,我们总结如下:
- 对于同一个类的不同实例对象,调用他们的普通方法(synchronized修饰)时,由于不是同一把锁,所以不会产生竞争。
- 调用同一个类的静态方法(synchronized修饰)时,由于是同一把锁(类的Class对象),所以会产生竞争。
- 当一个线程正在调用实例的普通方法(synchronized修饰)时,若另一个线程调用它的其它非同步普通方法时,不需要获取锁,可以同时执行。
来源:CSDN
作者:xiaochao8803
链接:https://blog.csdn.net/ym572170/article/details/104799204