Synchronized是Java语言的关键字,可以使用它来修饰方法或者代码块,Synchronized能够保证被它修饰的方法或代码块在同一时刻最多只有一个线程执行该段代码,这里我将介绍由它来分别修饰静态方法,实例方法,以及代码块是如何使用的。
1、修饰静态方法
Synchronized修饰静态方法它的锁就是当前的class对象,那么如果当该类有多个Synchronized修饰的静态方法时,一次只有一个线程能执行其中的一个Synchronized修饰的静态方法,同时Synchronized还是可重入锁,在一个线程拿到了锁,它可以再去调用该类中其他Synchronized修饰的静态方法。
示例代码:
public class TestStaticSync {
public static void main(String[] args) {
test1();
// test2();
}
/**
* 两个线程同时抢占执行同一个方法
*/
public static void test1() {
//启用两个线程
ExecutorService executorService = Executors.newFixedThreadPool(2);
//执行加法
executorService.execute(() -> {
TestStaticSync.add(1, 1);
});
//在执行加法
executorService.execute(() -> {
TestStaticSync.add(2, 2);
});
executorService.shutdown();
}
/**
* synchronized 的可重入演示
*/
public static void test2() {
//启用两个线程
ExecutorService executorService = Executors.newFixedThreadPool(2);
//执行加法 在执行减法
executorService.execute(() -> {
TestStaticSync.add(1, 1);
TestStaticSync.sub(1, 1);
});
//执行减法 在执行加法
executorService.execute(() -> {
TestStaticSync.sub(2, 2);
TestStaticSync.add(2, 2);
});
executorService.shutdown();
}
synchronized public static void add(int a, int b) {
//模拟耗时
sleep();
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行加法:" + a + "+" + b + "=" + (a + b));
}
synchronized public static void sub(int a, int b) {
//模拟耗时
sleep();
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行减法:" + a + "-" + b + "=" + (a - b));
}
public static void sleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个线程同时执行同一个类的静态方法,查看效果,我们可以看到它是在一个线程执行完成了才进行下一个的,执行test1方法输出:
pool-1-thread-1在1616168594406执行加法:1+1=2
pool-1-thread-2在1616168595410执行加法:2+2=4
两个线程同时执行两个静态方法,查看效果,我们可以看到它是顺序的执行完成的,哪怕两个线程中的调用的方法顺序不一样,也是等一个线程执行完了,下一个才能开始执行,执行test2方法输出:
pool-1-thread-1在1616168620580执行加法:1+1=2
pool-1-thread-1在1616168621583执行减法:1-1=0
pool-1-thread-2在1616168622588执行减法:2-2=0
pool-1-thread-2在1616168623593执行加法:2+2=4
2、修饰实例方法
Synchronized修饰实例方法它的锁就是this当前的实例,同时类中也可以有多个由Synchronized修饰的实例方法,和修饰静态方法一样,对同一个实例,一次只有有一个线程来执行一个由Synchronized修饰的实例方法,同时也是可以重入的。
public class TestInstantiateSync {
public static void main(String[] args) {
test1();
// test2();
}
/**
* 两个线程同时抢占执行同一个实例方法
*/
public static void test1() {
TestInstantiateSync testSync = new TestInstantiateSync();
//启用两个线程
ExecutorService executorService = Executors.newFixedThreadPool(2);
//执行加法
executorService.execute(() -> {
testSync.add(1, 1);
testSync.sub(1, 1);
});
//在执行加法
executorService.execute(() -> {
testSync.sub(2, 2);
testSync.add(2, 2);
});
executorService.shutdown();
}
/**
* 两个线程同时执行两个实例对象的方法
*/
public static void test2() {
ExecutorService executorService = Executors.newFixedThreadPool(2);
TestInstantiateSync testSync1 = new TestInstantiateSync();
TestInstantiateSync testSync2 = new TestInstantiateSync();
executorService.execute(() -> {
testSync1.add(1, 1);
testSync1.sub(1, 1);
});
executorService.execute(() -> {
testSync2.sub(2, 2);
testSync2.add(2, 2);
});
executorService.shutdown();
}
synchronized public void add(int a, int b) {
//模拟耗时
sleep();
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行加法:" + a + "+" + b + "=" + (a + b));
}
synchronized public void sub(int a, int b) {
//模拟耗时
sleep();
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行减法:" + a + "-" + b + "=" + (a - b));
}
public static void sleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个线程同时执行同一个实例的两个由synchronized修饰的方法,查看效果,我们可以看到它是在一个线程执行完成了才进行下一个的,同时它重入了,执行test1方法输出:
pool-1-thread-1在1616169847582执行加法:1+1=2
pool-1-thread-1在1616169848584执行减法:1-1=0
pool-1-thread-2在1616169849587执行减法:2-2=0
pool-1-thread-2在1616169850587执行加法:2+2=4
两个线程同时执行两个不同实例的方法synchronized修饰的方法,查看效果,可以看到两个线程是同时执行的,所以不同的实例他们的锁是不一样的,执行test2方法输出:
pool-1-thread-1在1616169940129执行加法:1+1=2
pool-1-thread-2在1616169940129执行减法:2-2=0
pool-1-thread-2在1616169941131执行加法:2+2=4
pool-1-thread-1在1616169941131执行减法:1-1=0
2、修饰代码块
Synchronized修饰代码块设置指定的锁,可以设置一个成员变量为锁,当该类每次实例化时,也会生成新的锁,也就是不同的实例用的锁是不一样的。
思考🤔:为什么我要使用new String(“lock”)的对象为锁,而不是直接赋值的方式?使用直接赋值的方式的字符串为锁有什么效果?提示:JVM内存分配
public class TestCodeSync {
private String lock = new String("lock");
// private String lock = "lock";
public static void main(String[] args) {
// test1();
test2();
}
public static void test1() {
//启用两个线程
ExecutorService executorService = Executors.newFixedThreadPool(2);
TestCodeSync testSync = new TestCodeSync();
executorService.execute(() -> {
testSync.add(1, 1);
testSync.sub(1, 1);
});
executorService.execute(() -> {
testSync.sub(3, 2);
testSync.add(1, 0);
});
executorService.shutdown();
}
public static void test2() {
ExecutorService executorService = Executors.newFixedThreadPool(2);
TestCodeSync testSync1 = new TestCodeSync();
TestCodeSync testSync2 = new TestCodeSync();
executorService.execute(() -> {
testSync1.add(1, 1);
testSync1.sub(1, 1);
});
executorService.execute(() -> {
testSync2.sub(3, 2);
testSync2.add(1, 0);
});
executorService.shutdown();
}
public void add(int a, int b) {
synchronized (lock) {
sleep();
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行加法:" + a + "+" + b + "=" + (a + b));
}
}
public void sub(int a, int b) {
synchronized (lock) {
sleep();
System.out.println(Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "执行减法:" + a + "-" + b + "=" + (a - b));
}
}
public static void sleep() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
两个线程同时执行同一个实例的两个由synchronized修饰代码块的方法,查看效果,我们可以看到它是在一个线程执行完成了才进行下一个的,同时它重入了,执行test1方法输出:
pool-1-thread-1在1616254845316执行加法:1+1=2
pool-1-thread-1在1616254846319执行减法:1-1=0
pool-1-thread-2在1616254847324执行减法:3-2=1
pool-1-thread-2在1616254848325执行加法:1+0=1
两个线程同时执行两个不同实例的方法synchronized修饰代码块的方法,查看效果,可以看到两个线程是同时执行的,所以不同的实例他们的锁是不一样的,执行test2方法输出:
pool-1-thread-1在1616255036160执行加法:1+1=2
pool-1-thread-2在1616255036160执行减法:3-2=1
pool-1-thread-1在1616255037162执行减法:1-1=0
pool-1-thread-2在1616255037162执行加法:1+0=1
总结:Synchronized修饰静态方法时,锁就是当前的class,这个锁就是一个类锁,属于非常重的锁,因为其他的线程访问该类的其他的Synchronized修饰的静态方法时将会被阻塞;Synchronized修饰实例方法时,锁就是当前的实例对象,不同的实例锁是不一样的,对不同的实例不会存在锁的竞争,相对静态方法来说较轻;Synchronized修饰代码块时,只有执行到修饰的代码块时才会被阻塞,可以更细粒度的控制锁,不同的实例也可以拥有不同的锁(非静态常量作为锁),相对实例化的Synchronized修饰的方法性能更好。