使用正确的锁对象来保证线程安全

本文探讨Java中线程安全的实现方式,特别是如何通过synchronized关键字确保容器操作的线程安全性,并提供实例说明如何避免锁的误用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在java集合中,有些容器是线程安全的(Vector,ConcurrentLinkedQueue等),有些则不是(list等),对线程不安全的容器,可以利用类似

private static List<Task> taskQueue = Collections.synchronizedList(new LinkedList<Task>());的方法得到本身不是线程安全的容的线程安全的状态。

但是要注意的一点,无论是哪一种情况,线程安全仅仅指的是如果直接使用它提供的函数,比如:queue.add(obj); 或者 queue.poll(obj);,这样我们自己不需要做任何同步。
但如果是非原子操作,比如:
  if(!queue.isEmpty())  
     queue.poll(obj);  
   

这种非原子的操作,就不是线程安全的。
所以对于这种情况,我们还是需要自己同步:
   synchronized(queue)  
        if(!queue.isEmpty())  
          queue.poll(obj);  
         
     

在这种同步的过程中,经常会出现使用了不同的锁导致的错误情况:

下面是一个错误例子:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListHelper<E> {
	public List<E> list = Collections.synchronizedList(new ArrayList<E>());
	
	public synchronized boolean putIfAbsent(E x){
		boolean absent = !list.contains(x);
		if(absent)
			list.add(x);
		return absent;
	}
}
上面的例子中,虽然putIfAbsent方法使用了synchronized,但是synchronized使用的锁是ListHelper对象的内置锁,跟Collections.synchronizedList使用的锁是不一样的锁,所以会出现当一个线程进入putIfAbsent方法后,还是会有线程对list列表进行修改,这样在putIfAbsent方法中的非原子操作方法中,会出现线程不同步的情况。

要改正这种情况,就要使用同一锁,由于public List<E> list = Collections.synchronizedList(new ArrayList<E>());使用的锁是list对象(这等下会在下面写一个类来证明),所以在putIfAbsent方法中,我们要使用list对象来当锁:

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

public class ListHelper<E> {
	public List<E> list = Collections.synchronizedList(new ArrayList<E>());
	
	public  boolean putIfAbsent(E x){
		synchronized(list){
			boolean absent = !list.contains(x);
			if(absent)
				list.add(x);
			return absent;
		}
	}
}
使用同一个锁后,只要是进入了同步代码块,另一个线程想要再修改list就会等待释放锁后才执行,这就能保证线程同步。

下面来证明下public List<E> list = Collections.synchronizedList(new ArrayList<E>());使用的锁是list对象。

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;


public class Vector1 {
	
	public static void main(String[] args) {
		ListHelper lh = new ListHelper();
		new Thread(new Thread1(lh)).start();
		try {
			Thread.sleep(5000);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
		lh.list.add(2);
		System.out.println("+++++++++++++++++");
		
	}
}

class Thread1 implements Runnable{
	ListHelper lh;
	public Thread1(ListHelper lh){
		this.lh = lh;
	}

	@Override
	public void run() {
		lh.putIfAbsent(10);
	}
	
}

class ListHelper{
	public List<Integer> list = Collections.synchronizedList(new ArrayList<Integer>());
	
	public boolean putIfAbsent(Integer x){
		synchronized (list) {
			System.out.println("===============");
			try {
				Thread.sleep(10000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
			System.out.println("-------------------------");
			return true;
		}
	}
	
	
	
}

上面的执行结果是:

===============
-------------------------
+++++++++++++++++

由于子线程先执行了putIfAbsent方法,进入了同步代码块,这时list锁被子线程拿走了,所以即使主线程休眠时间先结束要执行lh.list.add(2);时,由于子线程还没执行完,不能拿到锁,所以这时主线程等待,直道子线程执行完后,才返回锁给主线程,才接着输入+++

如果把要拿锁才能执行的一行代码注释掉://lh.list.add(2);

然后再执行,输入结果如下:

===============
+++++++++++++++++
-------------------------


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值