Java synchronized关键字详解:从方法同步到单例模式实战
在Java多线程开发中,synchronized是保障线程安全的基础手段。理解其锁对象的绑定规则对于避免并发错误至关重要。
核心机制:synchronized如何锁定方法
synchronized关键字用于确保在同一时刻只有一个线程可以执行被修饰的代码块或方法。它的主要作用取决于应用在静态方法还是非静态方法上:
第一步:理解非静态方法的实例级锁
当synchronized修饰非静态方法时,它锁定的是当前对象实例(即this)。这意味着同一类的不同实例对象不会互相阻塞。
class Test {
public synchronized void method() {
// 只有同一个Test实例的method会被互斥访问
}
}
public class Sync implements Runnable {
private Test test;
public Sync(Test test) { this.test = test; }
public void run() { test.method(); }
public static void main(String[] args) throws Exception {
Test test1 = new Test();
Test test2 = new Test();
// 创建两个不同的对象实例,分别运行
new Thread(new Sync(test1)).start();
new Thread(new Sync(test2)).start();
}
}
- 目的:观察多实例间是否并发。
- 动作:使用
test1和test2创建两个独立的Sync线程。 - 结果:
test1和test2的method会同时执行。若想同步,必须传入同一个Test实例。
第二步:掌握静态方法的全局锁
当synchronized修饰静态方法时,它锁定的是Class类对象。无论通过哪种实例调用,甚至直接使用类名调用,都会受到互斥限制。
class Test {
public static synchronized void method() {}
}
建立Test实例后,无论是test.method()还是Test.method(),都是同步的,不存在多个实例的区分。
实战应用:解决单例模式的线程安全问题
传统的懒汉式单例(Lazy Singleton)在高并发环境下是不安全的。以下代码演示了问题所在及修复方法。
第1步:复现线程不安全的单例模式
如果不在getInstance中添加同步措施,多线程竞争会导致创建多个实例。
package test;
class Singleton {
private static Singleton sample;
private Singleton() {}
public static Singleton getInstance() {
if (sample == null) {
Thread.yield(); // 模拟高并发场景下的时间片切换
sample = new Singleton();
}
return sample;
}
}
public class MyThread extends Thread {
public void run() {
Singleton singleton = Singleton.getInstance();
System.out.println(singleton.hashCode());
}
public static void main(String[] args) {
Thread threads[] = new Thread[5];
for (int i = 0; i < threads.length; i++) {
threads[i] = new MyThread();
}
for (int i = 0; i < threads.length; i++) {
threads[i].start();
}
}
}
现象:程序运行多次输出的hashCode通常不一致,说明产生了多个Singleton实例,因为多个线程同时通过了if (sample == null)判断。
第2步:实施线程安全改造
最简单的修复方式是为getInstance加上synchronized:
public static synchronized Singleton getInstance() { ... }
或者采用“饿汉式”声明,直接在定义时初始化:
private static final Singleton sample = new Singleton();
// getInstance直接返回sample
注意事项与语法限制
在使用synchronized时,需特别注意以下语法规则和特性:
- 不可继承性:父类使用
synchronized的方法,子类重写时必须显式加上synchronized才能保持同步。例如:
class Child extends Parent { public synchronized void method() { super.method(); } - 接口限制:不能将
synchronized用于定义接口中的方法。 - 构造方法限制:构造方法本身不能使用
synchronized,但可以通过synchronized块来实现同步逻辑。 - 位置灵活性:
synchronized关键字可以放在public之前或之后,但不能放在返回类型之后。
正确:public synchronized void method();
正确:synchronized public void method();
错误:public void synchronized method(); - 变量限制:不能用于修饰类变量(字段)。
性能分析与总结
虽然synchronized能确保线程安全,但它是一把“重锁”。在Java中,非静态同步方法默认锁定this实例。如果在同一个对象中定义了多个synchronized非静态方法(如method1和method2),那么当一个线程正在执行其中一个方法时,该对象的其他所有同步方法都会被阻塞,直到该方法结束。
结论:这类似于数据库的表锁机制,粒度较大。如果在一个频繁调用的类中大量使用此方式,会导致严重的性能瓶颈。建议在实际开发中结合业务需求,考虑使用ReentrantLock或减小同步块的粒度来提升效率。
以上就是Java多线程编程中使用synchronized关键字同步类方法的详细内容,更多关于Java并发编程的资料请关注本站其它相关文章!
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









