目录
在并发编程领域,有效管理多个线程和访问共享资源至关重要。Java 的同步机制使我们能够编排线程交互,确保数据完整性并防止竞争条件。本文将深入探讨线程同步的复杂性。
同步
“synchronized”关键字仅适用于方法和块,不适用于类和变量。当多个线程尝试同时操作时,可能会出现数据不一致问题。为了缓解这个问题,使用了“synchronized”关键字。如果一个方法或块被声明为同步,则在给定对象上一次只允许一个线程执行它,从而解决数据不一致问题。
“synchronized”关键字的主要作用是解决数据不一致问题。但其主要缺点在于增加了线程的等待时间,导致性能问题。因此,除非有特殊要求,否则不建议使用“synchronized”关键字。
当线程想要在给定对象上执行同步方法时,它必须首先获得该对象的锁。一旦线程获得了锁,它就可以在对象上执行任何同步方法。方法执行完成后,线程自动释放锁。锁的获取和释放由 JVM 内部管理,不需要我们手动处理。
当一个线程在给定对象上执行同步方法时,不允许其他线程在同一对象上同时执行任何同步方法。但是,它们可以同时执行非同步方法。
class X {
synchronized m1()
synchronized m2()
m3()
}
如果线程 t1 尝试在对象 x上 执行 m1(),它将获取 x 的锁并开始执行 m1()。这是如果另外一个线程t2尝试执行 m1() 或线程 t3 尝试执行 m2(),则两者都将进入等待状态。但是如果线程 t4 尝试执行 m3(),它将立即执行。
锁的概念基于对象而不是方法。因此,每个 Java 对象都有两个区域:
- 非同步区域:该区域可以由任意数量的线程同时访问,例如对象状态保持不变的区域,如 read() 操作中。
- 同步区域:该区域一次只能由一个线程访问,特别是在涉及对象的状态发生变化时,比如更新、添加、删除等操作中。
同步示例代码:
class Display {
public synchronized void wish(String name) {
for (int i = 0; i < 10; i++) {
System.out.print("Good Morning:");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
}
System.out.println(name);
}
}
}
class MyThread extends Thread {
Display d;
String name;
MyThread(Display d, String name) {
this.d = d;
this.name = name;
}
public void run() {
d.wish(name);
}
}
class SynchronizedDemo {
public static void main(String[] args) {
Display d = new Display();
MyThread t1 = new MyThread(d, "Dhoni");
MyThread t2 = new MyThread(d, "Yuvraj");
t1.start();
t2.start();
}
}
此例中,Display类中的wish()方法是同步的,确保一次只有一个线程可以执行它。因此,每个线程(t1和t2)轮流执行wish()方法。
执行结果:
Good Morning:Dhoni
Good Morning:Dhoni
.
.
.
10 times
Good Morning:Yuvraj
Good Morning:Yuvraj
.
.
.
10 times
再看另外一个示例:
Display d1 = new Display ();
Display d2 = new Display ();
MyThread t1 = new MyThread (d1, "Dhoni" );
MyThread t2 = new MyThread (d2, "Yuvraj" );
t1.start();
t2.start();
此例中,创建了Display类的两个实例d1和d2。每个实例连同不同的名字分别传递到t1和t2线程中。这些线程负责在各自的Display对象上执行wish()方法。
我们可以看到尽管wish()方法是同步的,但是输出也有可能不连续,因为每个线程都是在不同的 Java 对象上操作(d1和d2)的。因此,在这种情况下,同步不能有效地确保正常输出。
所以只有当多个线程操作同一个Java对象时才会同步。如果线程操作不同的Java对象,则不会同步。
此外,Java 中的每个类都拥有一个唯一的锁,称为类级锁。当线程要执行静态同步方法时,它需要获取类级锁。一旦获得,线程就可以执行该类的任何静态同步方法。方法执行完毕后,锁会自动释放。
当一个线程执行静态同步方法时,其他线程不允许同时执行同一类的任何静态同步方法。但是,它们可以同时执行以下方法:
- static m3()(普通静态方法)
- synchronized m4()(普通同步方法)
- m5()(普通方法)
示例代码:
class X {
static synchronized m1()
static synchronized m2()
static m3()
synchronized m4()
m5()
}
如果一个线程t1尝试执行m1(),它会获取类级锁 ( CL(X))。如果另一个线程t2尝试执行m1(),它就会进入等待状态。同样,如果t3尝试执行m2(),也会进入等待状态。但是线程t4可以在没有任何争用的情况下执行m3()。同样线程t5可以执行m4()并且线程t6也可以无任何争用地执行m5()。
同步块
当只有几行代码需要同步时,不建议将整个方法声明为同步。相反,这些特定的代码行可以包含在同步块中。与同步方法相比,使用同步块的主要优点是减少线程等待时间,从而提高系统性能。
同步块可以通过以下方式声明:
获取当前对象(this)的锁:
synchronized(this) {
// Only the thread that acquires the lock of the current object can execute this area
}
获取特定对象的锁:
synchronized(b) {
// Only the thread that acquires the lock of the object 'b' can execute this area
}
获取类级锁:
synchronized(Display.class) {
// Only the thread that acquires the class-level lock of the "Display" class can execute this area
}
锁概念
锁概念适用于对象和类类型,但不适用于原生类(也叫基本数据类型)。由于需要引用类型,尝试将基本类型作为参数传递给同步块将导致编译错误。
一个线程可以同时从不同的对象获取锁。例如:
class X {
public synchronized void m1() {
// Thread holds lock of 'X' object
Y y = new Y();
synchronized(y) {
// Thread holds locks of 'X' and 'Y' objects
Z z = new Z();
synchronized(z) {
// Thread holds locks of 'X', 'Y', and 'Z' objects
}
}
}
}
X x = new X();
x.m1();
目录