黑马程序员_JAVA多线程(一)

------- android培训java培训、期待与您交流! ----------

一、synchronized关键字

通常情况下,多线程对共享数据访问的时候需要通过加锁来保证操作的原子一致性。

synchronized的通用用法:

1)  同步方法:

synchronized boolean pushHeartBeatMsg2ManagerQueue(HeartBeatMsg beatMsg);

2)   同步块:

synchronized(data){
	//TODO
}

2)   同步类:

synchronized(SingleCloud.class){//进行同步,拿到的是类的同步监视器,在同一个JVM范围内持有这个类的锁                              
	if (null == instance){                                          
                   instance = new SinlgeCloud();                                  
	}                          
}       

4)    典型应用场景:生产者、消费者模型中,生产者线程将生产的消息投递到消息队列中(或者栈),消费者线程在后台扫描消息对立进行处理。代码示例如下:

生产者线程将消息放入队列中(或者栈),或者消费者从队列中取出消息,都需要加锁:

public class SyncStack {

	private List buffer = new ArrayList();

	public synchronized char pop(){
		char c ;
		while(buffer.size() == 0) {
			try{
				System.out.println(Thread.currentThread().getName()+" now wait===================>");
				this.wait() ; //释放锁
			}catch(InterruptedException e){
				e.printStackTrace();
			}
		}
		c = ((Character)buffer.get(buffer.size() - 1)).charValue();
		buffer.remove(buffer.size() - 1) ;
		return c ;
	}
	public synchronized void push(char c){
		this.notify() ;
		Character charObj = new Character(c) ;
		buffer.add(charObj) ;
	}
}

二、volatile关键字

volatile是轻量级的synchronizedvolatile变量具有修改可见性,但是不具备原子性,根本原因是volatile不是互斥的。

volatile无法代替synchronized,而且如果使用不当,它带来的负面作用将远远大于自身的优点。

volatile变量的使用约束:

1)    对变量的写操作不依赖于当前值;

2)    该变量没有包含在具有其它变量的可变式中

典型案例1:使用volatile无法保证线程安全

public class NumberRanger {
	private volatile int lower;
	private volatile int upper;

	public void setLowerValue(int lower){
		if (lower > this. upper){
			throw new IllegalArgumentException("The lower value is greater than upper");
		}
		this.lower = lower;
	}

	public void setUpperValue(int upper){
		if (upper < this. lower){
			throw new IllegalArgumentException("The upper value is less than lower");
		}
		this.upper = upper;
	}
}

尽管使用volatile修饰符,上面的例子在多线程调用的时候仍然会发生参数校验失效的情况,假设lower=7,upper=11,而同一时间线程A和线程B分别调用setLowerValue(10)setUpperValue8),仍然为发生(10,8)这样的无效边界。这个时候需要通过synchronized和临界资源,来实现线程安全,如下:

synchronized (data) {
	if (lower > this. upper){
		throw new IllegalArgumentException("The lower value is greater than upper");
	}
	this.lower = lower;
}
synchronized (data) {
	if (upper < this. lower){
		throw new IllegalArgumentException("The upper value is less than lower");
	}
	this.upper = upper;
}

案例二:产生节点内唯一的序列:

public final class IndexGeneratorTool{
	private static volatile long numberIndex;
    	public static long getNextSequence(){
        		return ++numberIndex;
    	} 
}

尽管numberIndexvolatile,但是 ++ numberIndex不是一个原子操作;它需要先获取numberIndex的最新值,然后在此基础之上执行++操作,最后写入。由此看以看出,在多线程的场合下,就会可能有两个线程可能会同时看到同一个numberIndex而同时执行修改操作导致产生脏数据。

如何正确使用volatile变量?

1)      状态标志位:用于状态通知,比如发生了某个重要事件,同时将自己的状态更新通知给使用该变量的线程(通常是只读操作)

代码样例:

volatile boolean shutdownFlag;

void stopRun(){
	shutdownFlag = true;
}

void run(){
	while(!shutdownFlag){
		//TODO
	}
}

stopRun的方法调用很可能是由其它线程负责,如果不使用volatile修饰shutdownFlag,需要对shutdownFlag做同步,这样的同步需要同时增加在while循环和stopRun方法中,非常复杂。使用volatile则不需要进行同步,因为shutdownFlag一旦发生改变,其新的值立即被更新到内存中,而如果不使用volatilesynchronized,线程外修改的shutdownFlag状态对于线程内的循环语句可能是不可见的,尤其是没有消息通知的场合之下,也许线程永远都不会停止,也许在延迟一段时间之后才停止。

2)      低开销的读-写操作:结合volatilesynchronized,可以实现低开销的读写锁。模拟Oracle数据库的Sequence实现:产生节点内唯一的序列号:

public final class Sequence{
	private volatile long sequence;
	public long getCurrentVal(){
		return sequence;
	}

	public synchronized long getNextVal(){//临界资源,保证同一时刻只有一个线程进行操作
		return ++sequence;
	}
}

小结:与锁相比,volatile变量是一种非常简单但同时又非常脆弱的同步机制,它在某些情况下将提供优于锁的读写性能和简易性。

三、wait()notify()方法

wait的用法如下:

//同步锁
synchronized (msgQueue){

     	while (msgQueue.isEmpty()){// 判断消息队列是否为空
    		try{
              		msgQueue.wait();// 释放锁,并等待被其他线程的唤醒
         		}catch (InterruptedException e){
			//TODO
         		}
     	}
     	otpMsg = msgQueue.pop();// 弹出一条消息
}

为了防止因为线程被意外唤醒而导致的条件检测失败,wait()语句往往放在while(flag)循环中来进行判断,即便发生等待被意外唤醒的情况,由于会再次对wait条件进行判断,也就是再对msgQueue.isEmpty()是否为假进行再判断,以排除其他条件导致程序被意外唤醒的情况!

notifynotifyAll():

当不确认是否用notify还是notifyAll()的时候,往往会更保险的使用notifyAll(),这也说明了上面为什么用while进行判断了。


 









 

 


------- android培训java培训、期待与您交流! ----------
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值