同步,加锁保证线程安全,可以分为对象锁和类锁
1.对象锁
synchronized写在非静态方法上或者synchronized(this)块
锁的只是单个对象,如果有两个对象同时操作,那还是线程不安全的
比如下面代码,结果都小于2000000
public class Sync {
private static int in = 0;
public synchronized void get(int name) {
// synchronized (Sync.class) {
in++;
System.out.println(in);
// }
}
}
class T {
@SneakyThrows
public static void main(String[] args) {
Sync sync = new Sync();
Sync sync2 = new Sync();
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
sync.get(2);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
sync2.get(1);
}
});
thread.start();
thread2.start();
}
}
修改为使用同一个对象,结果是正确的2000000
class T {
@SneakyThrows
public static void main(String[] args) {
Sync sync = new Sync();
// Sync sync2 = new Sync();
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
sync.get(2);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
sync.get(1);
}
});
thread.start();
thread2.start();
}
}
再举一个项目中会遇到的场景,这样也是无法做到线程安全的
测试的时候使用了jmeter,1000个线程轮询1000次,结果为996859
@PostMapping("/test")
public void test(HttpServletRequest request) {
Sync sync = new Sync();
sync.get(2);
}
public class Sync {
private static int in = 0;
public synchronized void get(int name) {
// synchronized (Sync.class) {
in++;
System.out.println(in);
}
// }
}
而如果将代码修改为下面这样,就可以做到线程安全,原理猜测是Controller是单例的,this指当前对象,而当前对象只存在一个单例对象,所以是同步的
@PostMapping("/test")
public void test(HttpServletRequest request) {
this.get(2);
}
private static int in = 0;
public synchronized void get(int name) {
// synchronized (this) {
in++;
System.out.println(in);
// }
}
为验证上述猜测,写代码如下,发现是线程不安全的,debug了两种写法,Controller中的this一直是同一个对象,所以上述猜测是正确的。对象锁关键点在于锁的是哪个“对象”
@PostMapping("/test")
public void test(HttpServletRequest request) {
WssController wssController = new WssController();
wssController.get(2);
}
private static int in = 0;
public synchronized void get(int name) {
// synchronized (this) {
in++;
System.out.println(in);
// }
}
另外,代码里使用synchronized(this)会不同程度地提升执行效率,因为发生同步阻塞的只是代码块,在该代码块之外的代码不会阻塞,可以最小范围内对线程不安全的代码进行同步
代码里同时存在多个synchronized方法或者代码块时,这些代码之间是同步阻塞的,因为获得的是对象锁,锁住的是整个对象
2.类锁
synchronized写在静态方法或者用synchronized(class)块的方式
锁的是类,也就是该类的所有对象同时操作,也是线程安全的
比如下面代码结果为正确的2000000
public class Sync {
private static int in = 0;
public void get(int name) {
synchronized (Sync.class) {
in++;
System.out.println(in);
}
}
}
class T {
@SneakyThrows
public static void main(String[] args) {
Sync sync = new Sync();
Sync sync2 = new Sync();
Thread thread = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
sync.get(2);
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 1000000; i++) {
sync2.get(1);
}
});
thread.start();
thread2.start();
}
}
当对象锁和类锁同时存在时,不会发生阻塞,因为是两种锁
另外,锁的对象不同时不会发生阻塞,对象锁的范围只是单个对象