synchronized(互斥锁/悲观锁),实现线程同步,让多个线程排队依次获取某个资源,保证数据不会出错,区别于乐观锁,常用在写操作较多的场景,如金融、交易等业务。
修饰方法
- 非静态方法 – 锁定的是方法的调用者,即类new出来的实例
class Data{
public synchronized void fun1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("1...");
}
public synchronized void fun2(){
System.out.println("2...");
}
}
public class Main {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
data.func1();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
data.func2();
},"B").start();
}
①锁定两个非静态方法,且使用的是同一个类的实例的情况下,则会触发synchronized,需要等func1执行完成后释放锁,func2才执行(锁调用者)
②锁定一个方法则不构成锁的竞争关系,所以func2会直接执行,无需等待func1释放锁
①输出结果:
1…
2…
②输出结果:
2…
1…
- 静态方法 – 锁定的是类
class Data{
public synchronized static void fun1(){
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("1...");
}
public synchronized static void fun2(){
System.out.println("2...");
}
}
public class Main {
public static void main(String[] args) {
Data data1 = new Data();
Data data2 = new Data();
new Thread(()->{
data1.func1();
},"A").start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
new Thread(()->{
data2.func2();
},"B").start();
}
①锁定两个静态方法,即使调用的是两个不同的实例,也会需要等待锁(锁类)
②如果指只锁定一个静态方法,则调用另一个方法不需要等待锁
①输出结果:
1…
2…
②输出结果:
2…
1…
修饰代码块 – 锁定的是传入对象
- 例1:传入(this)
class Data {
public void fun(){
synchronized (this){//锁对当前的实例化对象(this)起作用
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end...");
}
}
}
①public static void main(String[] args) {
Data data = new Data();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
data.fun();
},"A").start();
}
}
②public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Data data = new Data();
new Thread(() -> {
data.fun();
},"A").start();
}
}
传入对象为this时,会锁住当前类的实例化对象。
在①中,因为只有一个实例化对象,所以五个线程需要排队,输出结果是start…\n end………交替输出。
在②中,因为每次创建线程的同时也会new一个新的对象,所以不需要排队,输出结果是start….\n start….……直到最后一个end…。
- 例2:传入类(Data.class)
class Data {
public void fun(){
synchronized (Data.class){ //锁类
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end...");
}
}
}
①public static void main(String[] args) {
Data data = new Data();
for (int i = 0; i < 5; i++) {
new Thread(() -> {
data.fun();
},"A").start();
}
}
②public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
Data data = new Data();
new Thread(() -> {
data.fun();
},"A").start();
}
}
传入对象为Data.class时,锁的是传入类,新实例化的对象也属于这个类,所以①②都需要排队等待锁,输出结果是start…\n end………交替输出。
- 例3 :传入一个变量(Integer num)
class Data {
public void fun(Integer num){
//Integer num = 1; 这种情况和传参结果一致
synchronized (num){ /
System.out.println("start...");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("end...");
}
}
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
Data data = new Data();
new Thread(() -> {
Integer num = 1;
data.fun(num);
},"A").start();
}
}
传入对象为一个变量时,由于对象只有一个,所以线程还会去争夺锁。这里不难发现,synchronized修饰代码块的情况下,是根据锁的对象的是单个还是多个来确定是否需要排队的(一个则线程需要排队争夺锁,多个则线程都会拿到锁)。但需要注意的是,Integer的范围为-128~127时是同一个对象(从常量池里取),所以当Integer=128时,每次传入都会是一个新的对象,即也不需要排队。