目录
前言
Java是一种面向对象的编程语言,以其强大的并发处理能力而闻名。在多线程编程中,保证线程安全是一个重要的问题。本文将详细探讨Java中的一个关键机制——synchronized
关键字,帮助你深刻理解其概念、使用方法及其背后的工作原理。
一、什么是synchronized
synchronized
是Java提供的一种内置同步机制,用于解决多线程环境下的并发问题。当多个线程同时访问共享资源时,可能会出现数据不一致的情况。synchronized
通过锁(monitor)的方式来确保同一时间只有一个线程可以访问被保护的代码块,从而避免数据的竞争问题。
二、synchronized的底层实现
Java中的synchronized
关键字是通过进入和退出Monitor(监视器)对象来实现的。每个对象都有一个隐式的监视器锁。当我们使用synchronized
同步方法或同步代码块时,线程必须先获得该锁才能进入同步区域。
在JVM内部,synchronized
是通过字节码指令monitorenter
和monitorexit
来实现的。当线程执行到synchronized
区域时,它会尝试获取对象的监视器锁。若成功,则进入同步区域;否则,线程会被阻塞,直到获得锁为止。
三、synchronized与其他同步机制的比较
除了synchronized
之外,Java还提供了其他一些高级的同步机制,如ReentrantLock
、Semaphore
等。以下是它们的一些比较:
可重入性:
synchronized
和ReentrantLock
都是可重入锁。可重入性意味着同一个线程可以多次进入同步代码块而不会被自己阻塞。灵活性:
ReentrantLock
比synchronized
更加灵活。它提供了更多的方法,例如lockInterruptibly()
、tryLock()
等,可以更精细地控制锁的行为。性能:在较早版本的Java中,
synchronized
的性能表现较差。但从Java 6开始,JVM对synchronized
做了诸多优化,如偏向锁、轻量级锁以及自旋锁等,使其性能有了显著提升。条件变量:
ReentrantLock
提供了Condition
类,可以实现多个等待队列,配合await()
和signal()
方法实现更加复杂的线程间通信。而synchronized
只能依赖于wait()
、notify()
和notifyAll()
方法来管理线程间的通信。
四、synchronized
的使用方式
关键字synchronized拥有锁重入的功能,也就是在使用synchronized时,当一个线程得到了一个对象的锁后,再次请求此对象时是可以再次得到该对象的锁。出现异常,锁自动释放。
1. synchronized的重入
package com.ctb.sync5;
/**
* synchronized的重入
*
* @author biao
*
* 2024年
*/
public class SyncDubbo1 {
public synchronized void method1(){
System.out.println("method1..");
method2();
}
public synchronized void method2(){
System.out.println("method2..");
method3();
}
public synchronized void method3(){
System.out.println("method3..");
}
public static void main(String[] args) {
final SyncDubbo1 sd = new SyncDubbo1();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
sd.method1();
}
});
t1.start();
}
}
结果:
注:synchronized的重入,在同时添加都synchronized锁后,在得到一个对象的锁后是可以再次得到该对象的锁的
package com.ctb.sync5;
/**
* synchronized的重入
*
* @author biao
*
* 2024年
*/
public class SyncDubbo2 {
static class Main {
public int i = 10;
public synchronized void operationSup(){
try {
i--;
System.out.println("Main print i = " + i);
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
static class Sub extends Main {
public synchronized void operationSub(){
try {
while(i > 0) {
i--;
System.out.println("Sub print i = " + i);
Thread.sleep(100);
this.operationSup();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
Sub sub = new Sub();
sub.operationSub();
}
});
t1.start();
}
}
注:也可以通过继承的方式进行synchronized的重入
2.synchronized的异常
对于web应用程序,异常释放锁的情况,如果不及时处理,很可能对你的应用程序业务逻辑产生严重的错误,比如你现在执行一个队列任务,很多对象都在去等待第一个对象正确执行完毕再去释放锁,但是第一个对象由于异常的出现,导致业务逻辑没有正常执行完毕。所以这一点一定要引起注意,在编写代码的时候,一定要考虑周全。
package com.ctb.sync5;
/**
* synchronized的异常
*
* @author biao
*
* 2024年
*/
public class SyncException {
private int i = 0;
public synchronized void operation(){
while(true){
try {
i++;
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " , i = " + i);
if(i == 10){
Integer.parseInt("a");
//throw new RuntimeException();
}
} catch (Exception e) {//InterruptedException
e.printStackTrace();
System.out.println("log info i = " + i);
//throw new RuntimeException();
//continue;
}
}
}
public static void main(String[] args) {
final SyncException se = new SyncException();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
se.operation();
}
},"t1");
t1.start();
}
}
结果:
注:在我们运行数据时,当它某条数据出现错误时,继续往下执行,并记录异常数据,后续根据日志去解决问题。
当然我们也可以在异常产生时进行停止数据的变动,
第一种:抛出运行时异常
public synchronized void operation(){
while(true){
try {
i++;
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " , i = " + i);
if(i == 10){
Integer.parseInt("a");
// throw new RuntimeException();
}
} catch (Exception e) {//InterruptedException
e.printStackTrace();
System.out.println("log info i = " + i);
throw new RuntimeException();
// continue;
}
}
}
结果:
第二种:InterruptedException
是 Java 中的一个受检查异常,表示一个线程被中断。当一个线程调用 Thread
类的 interrupt()
方法时,会给该线程设置一个中断标记,表示该线程被中断了。
public synchronized void operation(){
while(true){
try {
i++;
Thread.sleep(200);
System.out.println(Thread.currentThread().getName() + " , i = " + i);
if(i == 10){
Integer.parseInt("a");
// throw new RuntimeException();
}
} catch (InterruptedException e) {//InterruptedException
e.printStackTrace();
System.out.println("log info i = " + i);
// throw new RuntimeException();
// continue;
}
}
}
结果: