目录
1.synchronized的特性
①互斥性
②可重入性
2.synchronized的使用示例
①修饰普通方法
②修饰静态方法
③修饰代码块
1.synchronized的特性
①互斥性
互斥性,就像是给门上锁了一样.
当A线程使用了被synchronized修饰的代码块并对其上锁,其他线程(B线程,C线程)想要使用这个代码块的时候就会调整为阻塞的状态.
当A线程执行完这个被synchronized修饰的代码块后,跳出这个代码块.此时,相当于解锁.
在解锁后,B线程与C线程就可以通过被随机调度来使用这个代码块.(因为线程间的调度是随机的,B,C线程都想要使用这个代码块,但是并不会遵循先来后到的顺序.在A使用的时候,他们都为阻塞状态.是会被操作系统在B,C中随机唤醒一个来使用)
②可重入性
被synchronized修饰就像被上锁了一样,有着可重入性这一特征.也被称为可重入锁,而可重入锁也可以叫做递归锁.
什么是可重入性呢?就是在线程A进入被synchronized修饰的代码块后,代码块中可能又调用了这个同一个方法(像是递归).此时代码块是上锁的状态,但因为synchronized是可重入性的锁.在检测到再次进入这个代码块的线程和之前对其上锁的线程是同一个的时候.就可以再次进入此代码块.
2.synchronized的使用示例
synchronized 本质上要修改指定对象的 "对象头". 从使用角度来看, synchronized 也势必要搭配一个具体的对象来使用.
被synchronized修饰的代码块,也被称为"同步代码块"
①修饰普通方法
此处ListNode类上的valAdd方法被synchronized修饰,当任意线程调用到这个方法的时候,被上锁的其实是使用这个方法的实例对象.
像是此处,线程0与线程1都分别调用了listNode0这个实例对象,再调用到valAdd方法.所以被上锁的其实是listnode0这个对象,此时的listnode0对象的属性与方法都是不可被其他线程调用的.
class ListNode{
int val;
ListNode next;
public ListNode(int val){
this.val = val;
}
public synchronized void valAdd(ListNode cur){//被synchronized修饰的普通方法
cur.val = cur.val + 1;
}
}
public class Test4 {
public static void main(String[] args) throws InterruptedException {
ListNode listNode0 = new ListNode(0);
Thread thread0 = new Thread(() -> {
listNode0.valAdd(listNode0);
});
Thread thread1 = new Thread(() -> {
listNode0.valAdd(listNode0);
});
thread0.start();
thread1.start();
thread0.join();
thread1.join();
System.out.println(listNode0.val);
}
}
②修饰静态方法
当synchronized修饰的是一个类方法时,被上锁的其实是Test4这个类对象.但他的上锁范围只有被synchronized修饰的类方法,其他没有被synchronized修饰的类方法是可以被其他线程调用的.
public class Test4 {
static int n = 0;
public static synchronized void add(){
n = n + 1;
}
public static void main(String[] args) throws InterruptedException {
Thread thread0 = new Thread(() -> {
add();
});
}
}
public class Test4 {
static int n = 0;
public static synchronized void add(){
n = n + 1;
}
public static void ADD(){//像这里,没有被synchronized修饰的类方法,其他线程也可以访问到
n = n + 1;
}
public static void main(String[] args) throws InterruptedException {
Thread thread0 = new Thread(() -> {
for(int i = 0; i < 10000;i++){
add();
}
});
Thread thread1 = new Thread(() -> {
for(int i = 0; i < 10000;i++){
ADD();
}
});
thread0.start();
thread1.start();
thread0.join();
thread1.join();
System.out.println(n);
}
}
这个n != 20000的结果也证明了这个观点
③修饰代码块
synchronized修饰一个代码块,括号里的是要被上锁的对象.即哪一个对象调用了这个方法并进入了这个代码块,哪一个对象就会被上锁
public synchronized void add(){
synchronized (this){
n = n + 1;
}
}
这里就是进入此代码块,就会将指定的类对象上锁.
public synchronized void add(){
synchronized (Test4.class){
n = n + 1;
}
}
使用synchronized的时候,首先要明白的是被上锁的对象是什么.只有线程间想给同一个对象上锁的时候才会造成线程的阻塞.
如果两个线程要给不同的对象上锁,他们是不会产生阻塞等待的.