java并发 共享资源的安全问题

  1. synchronized
    多个线程使用了同一个资源,java 提供了synchronized关键字来防止资源冲突。当任务要执行被synchronized所保护的代码片段时,它将先检查锁是否可用,然后获取锁,执行代码,释放锁。
public class SynchronizTest implements Runnable{
    //共享资源(临界资源)
    private int i=0;

    public synchronized void increase(){
        i++; //i++;操作并不具备原子性
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase(); 
        }
    }
    public static void main(String[] args) throws InterruptedException {
    	SynchronizTest instance=new SynchronizTest();
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

i++;自增是先读取 i 的值,再进行的+1操作,是分了两部分操作。所以其不具备线程安全,但经synchronized修饰的方法increase却能够保证i++的线程安全。他到底做了什么呢,我们先来分析下synchronized
synchronized能够修饰静态方法,实例方法,与代码块。当他修饰静态方法时

public class SynchronizTest implements Runnable{
    public static synchronized void increase(){
    	i++;
    }
    ...
   }

锁是其对应的Class对象相当于

 public static void increase(){
       synchronized (SynchronizTest.class) {
			i++;
		}
	}

当他修饰实例方法时

    public  synchronized void increase(){
    	i++;
    }

锁是其对应的实例对象相当于

    public  void increase(){
    	synchronized (this){
    		i++;
    	}
    }

以上例子说明了synchronized 关键字再指定了对象作为锁之后就具有了保证线程安全的功能,那么它是怎么实现的呢?,其实每个对象都具有一个对应的Monitor对象,synchronized 的引用正是指向的Monitor,monoter对象维护着两个队列,_WaitSet 和 _EntryList以及一个计数器count。_WaitSet里面存储的是被wait的线程。_EntryList维护着具有执行权,但在等待锁的线程,当被wait的线程经过notify后,_WaitSet里的某个线程将会添加到_EntryList中等待着锁。当有线程持有锁时count就会赋值为1,在具有锁的代码块中调用具有同一个锁的另一个方法,count会做自增操作。退出方法后count做自减操作,直到count为0就会释放锁。
synchronized的缺点不具备灵活性,开启锁以及关闭锁都不能受控制,并且出现异常时直接抛出异常不能做清理操作于是java 5引入了一个新的锁对象Lock在concurrent包中

public interface Lock {
	void lock();
	void lockInterruptibly() throws InterruptedException;
	boolean tryLock();
	boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
	void unlock();
	Condition newCondition();
}

Lock是一个接口,其定义着显示锁的基本方法,lock()加锁,unlock()释放锁tryLock()尝试获取锁,当锁被其他线程持有时返回false,tryLock(long time, TimeUnit unit)方法和tryLock()方法是类似的,只不过区别在于这个方法在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false,如下例子

public class SynchronizTest implements Runnable{
    //共享资源(临界资源)
    private static int i=0;
    Lock lock = new ReentrantLock();
    public  void increase(){
    	try {
    		lock.lock();
    		i++;
    	}finally {
    		lock.unlock();
    	}
    }
    @Override
    public void run() {
        for(int j=0;j<1000000;j++){
            increase(); 
        }
        
    }
    public static void main(String[] args) throws InterruptedException {
    	SynchronizTest instance=new SynchronizTest();
        Thread t1=new Thread(instance);
        Thread t2=new Thread(instance);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println(i);
    }
}

这样我们就可以显示的开启,关闭锁,以及在出异常后正确的在finally中清理某些数据。但你很可能犯这样的错误

    public  void increase(){
        Lock lock = new ReentrantLock();
    	try {
    		lock.lock();
    		i++;
    	}finally {
    		lock.unlock();
    	}
    }89

这将导致每次调用increase方法都会使用新的锁,这比synchronized 来说更容易犯错

  1. 本地线程
    共享变量能够被任何线程所改变,如上面的例子中static int i=0;所有的线程都能够改变i 的值,但如果你想保证线程之间的变量处理互不影响。那就可以用到ThreadLocal了,如下例子
public class ThreadLocalTest {
	
	public static ThreadLocal<String> tl = new ThreadLocal<String>();
	public static String str = "初始值";

	public static void main(String[] args) throws InterruptedException {
		
		ExecutorService es = Executors.newCachedThreadPool();
		es.execute(new Task("线程一的任务"));
		es.execute(new Task("线程二的任务"));
		es.shutdown();
		TimeUnit.MILLISECONDS.sleep(1);
		System.out.println(tl.get()+str);
	}

}

class Task implements Runnable{
	private String str = null ;
	
	Task(String str){
		this.str = str;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		System.out.println(Thread.currentThread().getName()+ThreadLocalTest.tl.get()+ThreadLocalTest.str);
		ThreadLocalTest.tl.set(str);
		ThreadLocalTest.str = str;
		System.out.println(Thread.currentThread().getName()+ThreadLocalTest.tl.get()+ThreadLocalTest.str);
	}
	
}

以上例子public static String str这个变量任何线程都能够影响它的值,它的值是不确定的,public static ThreadLocal tl 只能通过set,get方法访问它的值,对于每个线程来说,其他的线程不能影响它的值,那它是怎么实现的呢?

    public T get() {
    	//取得当前线程
        Thread t = Thread.currentThread();
        //更具当前线程获取到ThreadLocalMap      
        ThreadLocalMap map = getMap(t);
        //如果有这个map
        if (map != null) {
        	//根据ThreadLocal实例取得Entry 实例
        	//Entry又是ThreadLocalMap的静态内部类
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                //取得value
                T result = (T)e.value;
                return result;
            }
        }
        //如果当前线程还没有维护ThreadLocalMap,初始化一个并返回null
        return setInitialValue();
    }

以上是ThreadLocal的get方法,ThreadLocalMap是ThreadLoad的静态内部类,Thread里面维护着一个ThreadLocalMap,所以getMap可以通过Thread的实例t取得唯一对应的ThreadLocalMap

	// threadLocals正是Thread中ThreadLocal.ThreadLocalMap的变量名
    ThreadLocalMap getMap(Thread t) {
        return t.threadLocals;
    }

如果t.threadLocals取出的是null,当然的初始化一个给Thread

    private T setInitialValue() {
    	//values初始值为null
        T value = initialValue();
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
        return value;
    }

在ThreadLocalMap中也有一个内部类Entry,真正用于存储数据的

        static class Entry extends WeakReference<ThreadLocal<?>> {
            /** The value associated with this ThreadLocal. */
            Object value;

            Entry(ThreadLocal<?> k, Object v) {
                super(k);
                value = v;
            }
        }

Entry继承着一个弱引用,也就是说当ThreadLocal只存在于Entry时,ThreadLocal会被回收,WeakReference的get方法能够取得ThreadLocal实例,继续返回get方法中ThreadLocalMap.Entry e = map.getEntry(this);如果ThreadLocal存在,创建了并且没有被回收,这从Entry 中取出value。对于set,remove方法逻辑差不多,不再赘述

总结一下 ThreadLoacl : ThreadLocal虽说时一个共享的变量,但其内部申明着一个静态内部类ThreadLoaclMap,而每个线程对象都持有着这个内部类实例,所以Thread操作的实际上是属于当前线程下的变量,与共享变量无关了

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值