`
dingjob
  • 浏览: 180903 次
  • 性别: Icon_minigender_1
  • 来自: 武汉
社区版块
存档分类
最新评论

JAVA 并发编程(一)

阅读更多

随着多核时代的到来,JAVA类库提供了更多的并发方面的处理,这里结合《Effective Java》做个总结:

 

1. 区分线程操作是并发还是为了通讯,不仅仅是并发的情况需要同步。

JAVA 对于32位以下(依赖于硬件)可以表示的类型,也就是除了double和long的,都是可以通过原子操作完成的,但是当一个线程改变了这个变量时,并不立即在另外一个线程里可以看到,这依赖于线程的通讯。

看如下例子:

/**
 * @description 如下这种写法,在我的虚拟机上可以正常停止,但是如果虚拟机做过优化,则不一定能正确结束
 * @author  job 
 * @date 2010-8-16 下午08:48:25
 * @version 1.0
 */
public class StopThread {
	public static Boolean stopRequest = Boolean.FALSE;
	public static void main(String[] args) throws InterruptedException {
		Thread backThread = new Thread(new Runnable() {
			public void run() {
				int i= 0;
				long start = System.nanoTime();
				while(!stopRequest){
					System.out.println(i);
					i++;
				}
				long totalTime = System.nanoTime()-start;
				System.out.println("time:"+totalTime);
			}
		});
		backThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequest = true;
	}
}

 

运行结果为 time:997494594

可以看到在我的虚拟机上可以正常的通讯,但是在有些虚拟机上会将循环代码优化为

if(!stopRequest){

     while(true)

          i++;

}

这个就叫活性失败。为什么主线程和子线程需要通讯呢,这个和JMM (JAVA的内存模型)有关,JAVA给不同的线程分配了不同的内存,叫工作内存,工作内存之间互相使不可见的,只能通过主存进行通讯。可以有如下两种方法来进行同步

 

2. synchronized 同步,这个是在不同线程之间通过 wait 和notify事件进行同步,效率不高。

 

 

class SyncStopThread {
	public static Boolean stopRequest = false;
	private static synchronized void requestStop(){
		stopRequest= true;
	}
	private static synchronized boolean stopRequest(){
		return stopRequest;
	}
	public static void main(String[] args) throws InterruptedException {
		Thread backThread = new Thread(new Runnable() {
			public void run() {
				int i= 0;
				long start = System.nanoTime();
				while(!stopRequest()){
					System.out.println(i);
					i++;
				}
				long totalTime = System.nanoTime()-start;
				System.out.println("time:"+totalTime);
			}
		});
		backThread.start();
		TimeUnit.SECONDS.sleep(1);
		requestStop() ;
	}
}

 

 

 

3. 使用volatile 进行不稳定变量的声明。

volatile的语义, 其实是告诉处理器, 不要将我放入工作内存, 请直接在主存操作我.因此, 当多核或多线程在访问该变量时, 都将直接操作主存, 这从本质上, 做到了变量共享.效率更高

 

 

class VolatileStopThread {
	public static volatile Boolean stopRequest = false;
	
	public static void main(String[] args) throws InterruptedException {
		Thread backThread = new Thread(new Runnable() {
			public void run() {
				int i= 0;
				long start = System.nanoTime();
				while(!stopRequest){
					System.out.println(i);
					i++;
				}
				long totalTime = System.nanoTime()-start;
				System.out.println("time:"+totalTime);
			}
		});
		backThread.start();
		TimeUnit.SECONDS.sleep(1);
		stopRequest =true ;
	}
}

 
注意:volatile 只对原子性的操作起作用,如果是序列号的增加 i++这种操作,是不能通过volatile 来避免并发问题的,两个线程有可能同时读到一个值,进行操作,然后获得了相同的序列号,这种就是 安全性失败。最好的方式是使用atomic的类库,这个下篇博客详细介绍。

 

4. 总结:对于线程之间共享变量,有几个原则可以借鉴

(1)尽量将可变变量保存在线程中

(2)线程安全通讯(安全发布)的几种方法:保存在静态域中,作为初始化的一部分;保存在volatile、final或者通过正常锁定的域中,也可以放到concurrentMap等并发集合中。

(3)基本上通过volatile  和 atomic 能够解决一般的并发问题。

(4)volatile  适用于仅仅需要通讯,不需要互相排斥操作的情况下:一般来说,子线程和主线程之间是需要通讯的,子线程和子线程对可变参数的处理都是要互相排斥的。

 

 

补充同事的一个关于volatile 和atomic的诠释:

volatile 只是将内容放入主存,也就是共享内存,而一般的如果不使用这个声明,那就是放入二级缓存,其它线程可能将这个变量已经更新到主存,就引起脏读,它不能解决 int 和long等占用两个字节的操作,说白了就是:volatile只保证线程同时对存储单元的可见性。但是不保证原子性。要实现原子性,就要使用AUTOMIC。它基于乐观锁,通过循环的方式来不断轮询看是不是有线程更新了这个值。

一般我们的场景都是IO密集型运算,所以才可以做一些并发编程的优化。要是CPU密集型的话,CPU一直很忙,那就没有优化余地了。AUTOMIC这个包也是基于这个考虑才使用乐观并发的方式的。
分享到:
评论
1 楼 阿布abu 2016-09-21  
你不在 里面 sleep  再看看呢

相关推荐

Global site tag (gtag.js) - Google Analytics