文章目录
- 1.存在的问题
- 2.使用同步解决问题
- 1) synchronized
- 2) volatile
- 3) 锁
- 总结
用多线程过程中,有可能出现 多个线程同时处理(获取或修改等)同一个数据,这个时候就 会发生数据不同步的问题, 因此出现了同步和锁来保证多个线程可以安全的处理同一个数据。
1.存在的问题
例如:火车售票窗口售票,假如我们有 2 个窗口(相当于开启了 2 个线程),同时卖 10 张票
两个窗口的操作流程都如下:
1)购票者到窗口
2)窗口访问票匣子获取余票数量
3)票匣子返回余票数量
4)售票窗口判断,若存在票(大于 0 )则卖给他
那么,假如票匣子就剩下 1 张票啦!但是不巧的是两个窗口同时查看余票数量,都发现还有 1 张票,**又都卖给了购票者……**这就是多线程存在的数据不同步问题_
示例代码:
public class Demo {
public static void main(String[] args) throws InterruptedException {
ThreadDemo threadDemo = new ThreadDemo();
new Thread(threadDemo,"售票窗口1").start();
new Thread(threadDemo,"售票窗口2").start();
}
}
class ThreadDemo implements Runnable {
private int ticketCount = 10;
@Override
public void run() {
String tName = Thread.currentThread().getName();
while (true) {
if (ticketCount <= 0) {
return;
}
try {
Thread.sleep(200);
System.out.println(tName + "成功卖了一张票!余票:" + (ticketCount-- - 1));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
}
输出结果:
售票窗口1成功卖了一张票!余票:9
售票窗口2成功卖了一张票!余票:8
售票窗口1成功卖了一张票!余票:7
售票窗口2成功卖了一张票!余票:6
售票窗口2成功卖了一张票!余票:5
售票窗口1成功卖了一张票!余票:4
售票窗口2成功卖了一张票!余票:3
售票窗口1成功卖了一张票!余票:2
售票窗口1成功卖了一张票!余票:1
售票窗口2成功卖了一张票!余票:0
售票窗口1成功卖了一张票!余票:-1
2.使用同步解决问题
同步(Synchronization) 是一种协调多个线程执行的机制,它能够确保在同一时刻只有一个线程访问共享资源。主要通过关键字 synchronized
和 volatile
以及 锁对象
等手段来实现同步。
1) synchronized
关键字 synchronized
用于修饰方法或代码块,保证在同一时刻只有一个线程能够执行被 synchronized
修饰的代码。以下是两种使用方式:
- 修饰方法
public synchronized void test() {
// 同步的代码块
}
- 修饰代码块
public void someMethod() {
// 非同步的代码块
synchronized (lockObject) {
// 同步的代码块
}
// 非同步的代码块
}
2) volatile
关键字 volatile
用于声明变量,保证变量的可见性。被 volatile
修饰的变量对所有线程可见,当一个线程修改了这个变量的值,其他线程能够立即看到修改后的值。
public class Demo {
private volatile int ticketCount = 10;
}
3) 锁
Java 提供了很多种锁,常用的有 synchronized
关键字、ReentrantLock
及 Read/Write Lock
等 。
-
ReentrantLock
它支持可重入锁,允许一个线程多次获取同一把锁。
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Demo { private final Lock lock = new ReentrantLock(); public void test() { lock.lock(); try { // 同步的代码块 } finally { lock.unlock(); } } }
ReentrantLock
提供了比synchronized
更丰富的功能,如可中断锁、公平锁、定时锁等 -
Read/Write Lock
ReadWriteLock
接口定义了读写锁,它包含两个锁,一个用于读操作,一个用于写操作。读写锁允许多个线程同时读取共享资源,但只允许一个线程进行写操作。ReentrantReadWriteLock
是ReadWriteLock
的一个实现类import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class Demo { private final ReadWriteLock readWriteLock = new ReentrantReadWriteLock(); public void read() { readWriteLock.readLock().lock(); try { // 读取共享资源的操作 } finally { readWriteLock.readLock().unlock(); } } public void write() { readWriteLock.writeLock().lock(); try { // 修改共享资源的操作 } finally { readWriteLock.writeLock().unlock(); } } }
读写锁适用于读操作远远多于写操作的场景,可以提高并发性
-
StampedLock
它是一种读写锁的变种,提供了乐观读锁,可以在读多写少的场景中提供更好的性能
import java.util.concurrent.locks.StampedLock; public class Demo { private final StampedLock stampedLock = new StampedLock(); public void read() { long stamp = stampedLock.tryOptimisticRead(); // 乐观读操作 if (!stampedLock.validate(stamp)) { // 有写操作发生,转为悲观读 stamp = stampedLock.readLock(); try { // 悲观读操作 } finally { stampedLock.unlockRead(stamp); } } } public void write() { long stamp = stampedLock.writeLock(); try { // 写操作 } finally { stampedLock.unlockWrite(stamp); } } }
StampedLock
提供了更细粒度的控制,并允许在不同的代码路径中执行不同的操作
总结
在多线程编程中,确保线程安全是至关重要的!通过合理使用 synchronized
关键字、volatile
关键字以及 ReentrantLock
等锁机制,可以有效地保护共享资源,避免数据不一致和竞态条件等问题。合理的同步机制不仅能够提高程序的性能,还能够确保程序的正确性。在实际开发中,根据具体场景选择合适的同步和锁机制是编写高效、安全多线程代码的关键。