JAVA学渣之路--并发篇二

9 篇文章 0 订阅
  1. synchronized–共享资源竞争的简单解决方案
    在我们写多线程的程序时,经常会碰到多个线程需要访问同一资源,并且这个资源在当前状态,只能被一个线程访问,否则程序就会出错,这时候,我们一般的解决方式就是采用“序列化访问共享资源”的方案。意思说,在给定时刻只允许一个任务访问共享资源。
    通常时在代码前面加上一条锁语句来实现序列化访问共享资源,也就是说在一段时间内,只有一个任务可以运行这段代码。因为锁语句产生了一种互相排斥的效果,所以这种机制常常称为互斥量(mutex)。
    Java提供了synchronized的形式,来实现代码锁,为防止资源冲突提供了内置支持。当一个任务执行到synchronized关键字保护的代码片段时,先会去检查锁是否可用,如果锁可用,会获取锁,执行代码,然后释放锁。
    我们一般会将所有访问到共享资源的方法标记为synchronized,在一个任务处于synchronized方法的调用中,其余所有线程调用类中任意一个被标记为synchronized的方法的线程都将被阻塞。也就是说,在同一个对象内的synchronized的方法共享同一个锁,只要一个上了锁,那么所有的synchronized方法都将不可用。
    在当前线程已经拿到了synchronized的锁之后,将可以继续获取相应对象的其他的方法的synchronized的锁,JVM会去为当前这个线程计数,统计对象被加锁的次数,每当这个线程退出一个synchronzied方法时,当前的计数就会减一,直到为0时,锁才会被完全释放。此时,别的任务才可以使用该对象。
    synchronized的机制,意味着类中所有对于临界资源访问的操作都需要加上synchronized关键字,否则,只要有一个没有加上,都有可能导致错误。因为其他线程会访问到这个临界资源。忽略整个对象的锁。
    代码如下:
```
public class EvenGenerator extends IntGenerator {
	private int currentEvenValue = 0;
	public synchronized int next() {
		++currentEvenValue;              // Danger pointer here
		++currentEvenValue;
		return currentEvenValue;
	}
	public static void main(String [] args) {
		EvenChecker.test(new EvenGenerator());
	}
}
```
  1. Lock对象
    除了给对象的方法加上synchronized关键字之外,我们还可以使用Lock对象,使用Lock对象,较之synchronized的方式,代码缺乏优雅性,但是对于解决问题时,这种方式相较更加灵活。可以决定需要上Lock对象的代码段,不用整个流程都变成临界资源。
    代码如下:

    	private int currentEvenValue = 0;
    	private Lock lock = new ReentrantLock();
    	public int next() {
    		lock.lock();
    		try {
    			++currentEvenValue;
    			++currentEvenValue;
    			return currentEvenValue;
    		} finally {
    			lock.unlock();
    		}
    	}
    

    使用ReentrantLock的实例化对象Lock,然后在即将进入临界资源的地方使用lock()方法,这样这段代码就上了锁,然后这里必须使用try-finally子句,并且return语句必须放在try语句中,unlock方法暴露在finally子句中,这样就可以保证在return之前一定可以释放锁,并且在释放所之前,不会有其他线程进入临界资源区。
    使用Lock这样的方式虽然比synchronized方式要麻烦得多,但是,synchronized中报错时,会直接抛出一个异常,没有机会去 做清理工作,而这里在finally子句中,可以先做好清理工作,这样就可保证系统一直维护在正确的状态了。还有一点就是,用synchronized不能控制线程尝试获取锁,或者尝试获取锁一段时间,如果失败了,那么处理其他事物。如果想要实现上述过程,就需要用到了concurrent类库。
    代码如下:

    public void timed() {
    		boolean captured = false;
    		try {
    			captured = lock.tryLock(100, TimeUnit.MILLISECONDS);
    		} catch(InterruptedException e) {
    			throw new RuntimeException(e);
    		}
    		try {
    			System.out.println("tryLock(20, TimeUnit.SECONDS) :" + captured);
    		} finally {
    			if(captured) {
    				lock.unlock();
    			}
    		}
    	}
    
  2. volatile关键字
    在我们写程序时,有的时候,程序的中间变量才低级层次上可以发现,部分值是保存在本地处理器的缓存中的,这样的情况出现以为着,相同的任务,不同的线程有着不同的当前视图,也就是说一个线程对一个变量进行修改时,尽管这个变量是共用的,但是,可能由于暂时性的没有将缓存中的视图更新到主存中,这就会造成不可视,所以这时就出现了volatile关键字,volatile关键字保证了应用中的可视性,如果将一个域设置为了volatile的,那么当前这个域只要发生了写操作,那么所有的读操作都将可以看到这个修改。即使使用了本地缓存,volatile也会被写入到主存中。

  3. 原子类
    Java SE5引入了原子类,AtomicInteger,AtomicLong,AtomicReference等特殊的原子性变量类,一般更新操作方式为使用方法类似compareAndSet(expectedValue, updateValue),原子类的方法调用操作为原子性的,所以不用担心在中间切换线程。但是大多情况下我们还是习惯使用synchronized或者lock,两种方式较之原子类,我们更常用,也更安全点。
    代码如下:

    import java.util.Timer;
    import java.util.TimerTask;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.atomic.AtomicInteger;
    
    public class AtomicIntegerTest implements Runnable {
    	private AtomicInteger i = new AtomicInteger(0);
    	public int getValue() {
    		return i.get();
    	}
    	public void evenIncrement() {
    		i.addAndGet(2);
    	}
    	public void run() {
    		while(true) {
    			evenIncrement();
    		}
    	}
    	public static void main(String [] args) {
    		new Timer().schedule(new TimerTask() {
    			public void run() {
    				System.err.println("Aborting");
    				System.exit(0);
    			}
    		}, 5000);
    		ExecutorService exec = Executors.newCachedThreadPool();
    		AtomicIntegerTest ait = new AtomicIntegerTest();
    		exec.execute(ait);
    		while(true) {
    			int val = ait.getValue();
    			if(val % 2 != 0) {
    				System.out.println(val);
    				System.exit(0);
    			}
    		}
    	}
    }
    

    原子类属于java.util.concurrent中的类。

  4. 临界区
    有的时候,我们并不需要类中的整个方法都被设置为synchronized,我们需要的仅是其中一部分代码,这部分代码段我们称之为临界区(critical section ),一般我们可以使用同步块的方式写一段代码,这段代码就是一段临界区;
    代码如下:

    class PairManager2 extends PairManager {
    	public void increment() {
    		Pair temp;
    		synchronized(this) {
    			p.incrementX();
    			p.incrementY();
    			temp = getPair();
    		}
    		store(temp);
    	}
    }
    

    这里可以在当前对象上同步,也可以在其他对象上同步,就是值synchronized()中的参数是哪个对象,这里面填哪个对象,那么对应对象的所有synchronized临界区会共用一个锁。

  5. 线程本地存储
    这里仅引入一个ThreadLocal的概念,使用这个类创造的对象,那么在执行任务的线程中,会有一个相应对象的本地存储,不同线程不同的本地存储,不共享。
    代码如下:

private static ThreadLocal<Integer> value = new ThreadLocal<Integer>() {
		private Random rand = new Random(47);
		protected synchronized Integer initialValue() {
			return rand.nextInt(10000);
		}
	};

这里仅是声明一个Integer对象,当在一个任务里使用这个对象时,那么相应线程就会创造一个本地存储放置这个Integer对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值