Java线程同步的实现方法详解
在多线程编程中,线程同步是必不可少的一个概念。线程同步指的是多个线程访问共享资源时的控制问题。Java提供了多种实现线程同步的方法,这篇文章将从多个方面对线程同步的实现方法进行详细阐述。
一、synchronized关键字
Java中的synchronized
关键字是实现线程同步的最基本的方法。synchronized
关键字可以将方法或代码块标记为同步的,从而保证同一时刻只有一个线程可以访问被保护的方法或代码块,避免多个线程同时对共享资源进行访问。
public class SyncTest implements Runnable {
private synchronized void myMethod() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " print " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
myMethod();
}
public static void main(String[] args) {
SyncTest test = new SyncTest();
Thread thread1 = new Thread(test);
Thread thread2 = new Thread(test);
thread1.start();
thread2.start();
}
}
上面的例子中,myMethod
方法被标记为synchronized
,因此在同一时刻只有一个线程可以执行myMethod
方法中的代码块。运行该程序可以看到两个线程交替输出数字。
二、使用Lock接口
Java 5引入了Lock
接口,Lock
接口提供了和synchronized
关键字相同的功能,但是可以更加灵活地控制同步代码块的粒度。对于需要更加灵活地控制同步代码块的情况,可以考虑使用Lock
接口。
public class LockTest implements Runnable {
private Lock lock = new ReentrantLock();
private void myMethod() {
lock.lock();
try {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + " print " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
@Override
public void run() {
myMethod();
}
public static void main(String[] args) {
LockTest test = new LockTest();
Thread thread1 = new Thread(test);
Thread thread2 = new Thread(test);
thread1.start();
thread2.start();
}
}
上面的例子中,使用ReentrantLock
作为锁对象,通过调用lock
方法和unlock
方法进行同步。与synchronized
关键字的方式相比,Lock
接口提供了更加灵活的同步粒度控制。
三、使用wait、notify和notifyAll方法
Java中的Object
类提供了wait
、notify
和notifyAll
三个方法,这三个方法可以用于实现线程间的通信和同步。wait
方法使得当前线程等待,直到其他线程调用notify
或notifyAll
方法唤醒它。notify
方法唤醒单个正在等待的线程,而notifyAll
方法唤醒所有正在等待的线程。
public class WaitNotifyTest {
private static final Object lock = new Object();
static class MyThread1 extends Thread {
@Override
public void run() {
synchronized (lock) {
System.out.println("Thread 1 is running...");
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Thread 1 is resumed...");
}
}
}
static class MyThread2 extends Thread {
@Override
public void run() {
synchronized (lock) {
System.out.println("Thread 2 is running...");
lock.notify();
System.out.println("Thread 2 is completed...");
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new MyThread1();
Thread thread2 = new MyThread2();
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
上面的例子中,使用wait
和notify
方法实现了两个线程之间的同步。Thread 1先获取了lock
对象的锁,然后调用wait
方法释放锁并等待,等待Thread 2调用lock
对象的notify
方法进行唤醒。Thread 2调用lock
对象的notify
方法唤醒了Thread 1。
四、使用Condition接口
Java 5引入了Condition
接口,Condition
接口是对wait
、notify
和notifyAll
方法的升级版,可以让线程更加精细地控制等待和通知的颗粒度。和Lock
接口一样,Condition
接口可以更加灵活地控制同步代码块的粒度。
public class ConditionTest {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
private void myMethod() throws InterruptedException {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " await...");
condition.await();
System.out.println(Thread.currentThread().getName() + " resumed...");
} finally {
lock.unlock();
}
}
public void signal() throws InterruptedException {
lock.lock();
try {
System.out.println("Signal...");
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionTest test = new ConditionTest();
Thread thread1 = new Thread(() -> {
try {
test.myMethod();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread thread2 = new Thread(() -> {
try {
test.signal();
} catch (InterruptedException e) {
e.printStackTrace();
}
});
thread1.start();
Thread.sleep(1000);
thread2.start();
}
}
上面的例子中,使用Condition
接口实现线程间的同步。myMethod
方法获取lock
锁对象并等待条件变量,通过调用condition.await
方法进行等待。signal
方法获取lock
锁对象并发出唤醒信号,通过调用condition.signal
方法进行唤醒。通过使用Condition
接口可以更加灵活地控制同步的粒度。
五、总结
本文对Java线程同步的实现方法进行了详细的阐述,包括synchronized
关键字、Lock
接口、wait
、notify
和notifyAll
方法以及Condition
接口。在多线程编程中,线程同步是一个非常重要的概念,合理地使用上述方法可以保证多个线程之间的正确协作。