互斥同步是最常见的一种高并发正确性保障手段。同步是指多个线程并发访问共享数据时,保证共享数据在同一时刻只被一个线程使用,而互斥是实现同步的一种手段。
在Java中,最基本的互斥同步手段是synchronized关键字。
synchronized使用规则:
在Java中同步锁是依赖于对象存在的。不同线程对于同步锁是互斥的。例如一个线程拥有了这个对象的同步锁,则另外一个线程是拿不到的,除非这个线程释放了自己拿到的同步锁。
1, 当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程访问该对象”的“synchronized方法”或者“synchronized代码块时将被阻塞。
2,当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程可以访问该对象的非同步代码块。
3,当一个线程访问“某对象”的“synchronized方法”或者“synchronized代码块”时,其他线程对该对象的其他synchronized方法或者代码块将被阻塞。
synchronized的两种使用方法:
1,同步代码块
class My_Thread implements Runnable{
public void run() {
synchronized (this) {//this 锁住this对象
for(int i = 0;i < 5;i++) {
System.out.println(Thread.currentThread().getName()+" --进行-- "+i);
}
}
}
}
public class Test4 {
public static void main(String[] args) {
My_Thread my_Thread = new My_Thread();
Thread thread = new Thread(my_Thread);
thread.setName("t1");
Thread thread1 = new Thread(my_Thread);
thread1.setName("t2");
thread.start();
thread1.start();
}
}
2,同步方法
class My_Thread implements Runnable{
public synchronized void run() {//锁的是当前对象
for(int i = 0;i < 5;i++) {
System.out.println(Thread.currentThread().getName()+" --进行-- "+i);
}
}
}
public class Test4 {
public static void main(String[] args) {
My_Thread my_Thread = new My_Thread();
Thread thread = new Thread(my_Thread);
thread.setName("t1");
Thread thread1 = new Thread(my_Thread);
thread1.setName("t2");
thread.start();
thread1.start();
}
}
synchronized原理
class My_Thread implements Runnable{
public void run() {//锁的是当前对象
synchronized (this) {
for(int i = 0;i < 5;i++) {
System.out.println(Thread.currentThread().getName()+" --进行-- "+i);
}
}
}
}
public class Test4 {
public static void main(String[] args) {
My_Thread my_Thread = new My_Thread();
Thread thread = new Thread(my_Thread);
thread.setName("t1");
Thread thread1 = new Thread(my_Thread);
thread1.setName("t2");
thread.start();
thread1.start();
}
}
我们对这段代码进行反编译
synchronized关键字经过编译之后,会在同步块前后分别形成monitorenter和monitorexit这两个字节码指令,这两个字节码都需要一个reference类型的参数来指明要锁定和解锁的对象。
在执行monitorenter时,首先要尝试获取对象的锁。如果这个对象没被锁定,或者当前线程已经拥有了这个对象的锁,把锁的计数器加一,相应的。执行monitorexit时会将锁计数器减1,当计数器为0时就释放锁。如果获取对象锁失败,那么当前线程就要阻塞等待,直到对象锁被另外一个线程释放为止。(Java线程是映射到操作系统的原生线程上的,如果要阻塞或者唤醒一个线程,都需要操作系统帮忙完成,这就需要从 用户态转换到核心态中,因此状态转换需要耗费很多的处理器转换时间)所以synchronized也被称作重量级锁。
将同步代码块换成同步方法
class My_Thread implements Runnable{
public synchronized void run() {//锁的是当前对象
for(int i = 0;i < 5;i++) {
System.out.println(Thread.currentThread().getName()+" --进行-- "+i);
}
}
}
我们再来进行反编译
我们会发现在同步方法中并没有出现monitorenter和monitorexit这两个字节码指令。
同步方法是在Class文件的方法表中将该方法的access_flags字段中的synchronized标志位置设为1,表示该方法是同步方法并使用调用该方法的对象或该方法所属的Class在JVM的内部对象表示做为锁对象。