Java之多线程的同步和死锁
设计模式中的单例模式的懒汉方式会存在多线程的安全问题;通过以下测试代码可以看到两个线程中得到的并不是同一个单例对象;
@Test
public void unsafeSingleInstanceTest() throws InterruptedException {
AtomicReference<UnSafeSingleInstance> s1 = new AtomicReference<>();
AtomicReference<UnSafeSingleInstance> s2 = new AtomicReference<>();
var t1 = new Thread(()->{
s1.set(UnSafeSingleInstance.getInstance());
});
var t2 = new Thread(()->{
s2.set(UnSafeSingleInstance.getInstance());
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("(s1 == s2) = " + (s1.get() == s2.get()));
System.out.println("s1 = " + s1.get());
System.out.println("s2 = " + s2.get());
// (s1 == s2) = false
// s1 = UnSafeSingleInstance@43a25848
// s2 = UnSafeSingleInstance@3ac3fd8b
}
class UnSafeSingleInstance{
private static UnSafeSingleInstance instance = null;
private UnSafeSingleInstance(){}
public static UnSafeSingleInstance getInstance() {
if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
instance = new UnSafeSingleInstance();
}
return instance;
}
}
我们可以通过在getInstance方法中使用synchronized添加同步代码块,同时进行双重的null检查避免无谓的锁占用;
@Test
public void safeSingleInstanceTest() throws InterruptedException {
AtomicReference<SafeSingleInstance> s1 = new AtomicReference<>();
AtomicReference<SafeSingleInstance> s2 = new AtomicReference<>();
var t1 = new Thread(()->{
s1.set(SafeSingleInstance.getInstance());
});
var t2 = new Thread(()->{
s2.set(SafeSingleInstance.getInstance());
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("(s1 == s2) = " + (s1.get() == s2.get()));
System.out.println("s1 = " + s1.get());
System.out.println("s2 = " + s2.get());
// (s1 == s2) = true
// s1 = SafeSingleInstance@2133c8f8
// s2 = SafeSingleInstance@2133c8f8
}
class SafeSingleInstance{
private static SafeSingleInstance instance = null;
private SafeSingleInstance(){}
public static SafeSingleInstance getInstance() {
if(instance == null){
synchronized (SafeSingleInstance.class){
if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
instance = new SafeSingleInstance();
}
}
}
return instance;
}
}
除了使用synchronized之外,也可以使用JUC提供的Lock来实现线程的同步;
@Test
public void safeSingleInstanceWithLockTest() throws InterruptedException {
AtomicReference<SafeSingleInstanceWithLock> s1 = new AtomicReference<>();
AtomicReference<SafeSingleInstanceWithLock> s2 = new AtomicReference<>();
var t1 = new Thread(()->{
s1.set(SafeSingleInstanceWithLock.getInstance());
});
var t2 = new Thread(()->{
s2.set(SafeSingleInstanceWithLock.getInstance());
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("(s1 == s2) = " + (s1.get() == s2.get()));
System.out.println("s1 = " + s1.get());
System.out.println("s2 = " + s2.get());
// (s1 == s2) = true
// s1 = SafeSingleInstanceWithLock@43a25848
// s2 = SafeSingleInstanceWithLock@43a25848
}
class SafeSingleInstanceWithLock{
private static volatile SafeSingleInstanceWithLock instance = null;
private static Lock lock = new ReentrantLock();
private SafeSingleInstanceWithLock(){}
public static SafeSingleInstanceWithLock getInstance() {
if(instance == null){
lock.lock();
if(instance == null){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
instance = new SafeSingleInstanceWithLock();
}
lock.unlock();
}
return instance;
}
}
多线程的同步可能会出现死锁;死锁的出现是由于发生死锁的两个线程持有彼此需要的锁资源,导致双方都无法执行只能等待;
以下测试代码以构成一个商品的两个组件为例,组装商品我们可以从APart开始,也可以从BPart开始;
@Test
public void deadThreadTest() throws InterruptedException {
APart a = new APart();
BPart b = new BPart();
var aPartWorker =new Thread(){
@Override
public void run() {
a.doWork(b);
}
};
var bpartWorker =new Thread(){
@Override
public void run() {
b.doWork(a);
}
};
aPartWorker.start();
bpartWorker.start();
aPartWorker.join();
bpartWorker.join();
// APart do self step.
// BPart do self step.
// waiting a.doSelfStep.
// waiting b.doSelfStep.
}
class APart{
public synchronized void doWork(BPart b) {
doSelfStep();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("waiting b.doSelfStep.");
b.doSelfStep();
}
public synchronized void doSelfStep(){
System.out.println(this.getClass().getName() + " do self step.");
}
}
class BPart{
public synchronized void doWork(APart a) {
doSelfStep();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("waiting a.doSelfStep.");
a.doSelfStep();
}
public synchronized void doSelfStep(){
System.out.println(this.getClass().getName() + " do self step.");
}
}
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!