`
Tristan_S
  • 浏览: 361213 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

内存模型 - 多线程

 
阅读更多
--------概述---------
需要多任务处理的原因是 计算机的运算速度和它的存储和通讯子系统速度的差距太大,大部分时间都花在了磁盘IO,网络通信和数据访问上。
使用一些手段把处理器的运算能力压榨出来, 否则就会造成很大的浪费。

高速缓存  内存和CPU之间有几个数量级的差距,所以引入了高速缓存,用以通讯。
这就导致了缓存一致性的问题。

主内存,工作内存和Java内存区域中的堆栈方法区并不是同一个层次。如果要勉强对应起来
内存 -->  主内存   --> Java堆中的对象实例
寄存器/高速缓存  -->  工作内存 --> 对应了jvm栈中部分区域,



------栈堆方法区---------
栈:可以分为JVM栈(执行字节码) 本地方法栈(执行Native方法)
JVM栈: 线程私有的, 生命周期与线程相同,存储局部/本地变量表(基本数据类型,对象引用)

堆: 所有对象实例 和 数组  常量池, 成员变量(包括基本数据类型int)
常量池 http://www.cnblogs.com/devinzhang/archive/2012/01/25/2329463.html


方法区:也称永久代,不会被回收, 存储类信息,常量,静态变量
运行时常量池: 是方法区的一部分,String 的intern方法可以将数据放入
String str1="wang";
String str2=(new String("wang")).intern();
System.out.println(str1==str2); //true

使用方式
Object obj = new Object();
Object obj 反映到栈的本地变量表中, 作为一个reference类型数据出现
new Object()  反映到堆中,形成一块内存空间
对中还包含了能查找到此对象类型数据(对象类型,父类,接口,方法等),存放在方法区中

----------线程池------------
         如果访问服务器的客户端很多,那么服务器要不断的创建和销毁线程, 这样将严重影响服务器的性能,如果真的来一名学员,我们就安排一名新的 工作人员为之服务,这也是不可能的,那么公司岂不是要招很多工作人员.
应该是一名工作人员服务完一名学员,空闲下来后,一旦有新的学员要服务,
我们安排该工作人员,为之服务.
        线程池的概念于此类似,首先创建一些线程,他们的集合称为线程池,
当服务器接收到一个客户请求后,就从线程池中取出一个空闲的线程为之服
务,服务完成后不关闭该线程,而是将该线程还回到线程池中.
        在线程池的编程模式下,任务是交给整个线程池,而不是直接交给某个线
程,线程池拿到任务偶,他就在内部找有空闲的线程,再把任务交给内部某个
空闲的线程,这就是封装.记住,任务是交给整个线程池,但可以同时向一个线程池中提交多个任务.
Log-monitor 中不是开所有的线程去访问32个instance上的日志, 而是只开10个,然后等这些结束后,继续执行剩下的


---------AtomicInteger--------
i++ 不是线程安全的,如果多个线程从1加到100 结果会小于100 需要用到synchronized
在i已从内存中取到最新值, 但未与1进行运算, 此时其他线程已数次将运算结果赋值给i.
则当前线程结束时, 之前的数次运算结果都将被覆盖. (非原子性)

AtomicInteger 是线程安全的,
调用了volatile,volatile保证每次取a的值都不是从缓存中取的,而是从a真正对应的内存地址,但它不能保证原子性。(可见性 )

区别于synchronized这种悲观锁,导致其他所有需要锁线程挂起,等待持有锁的线程释放锁。
CAS (Compare And Swap)  是一种乐观锁的机制
CAS有3个操作数,内存值V,旧的预期值A,要修改的新值B。当且仅当预期值A和内存值V相同时,将内存值V修改为B,否则什么都不做。
private volatile int value;

public final int get() {
        return value;
    }

 public final int incrementAndGet() {
        for (;;) {
            int current = get();
            int next = current + 1;
            if (compareAndSet(current, next))
                return next;
        }
    }

在这里采用了CAS操作,每次从内存中读取数据然后将此数据和+1后的结果进行CAS操作,如果成功就返回结果,否则重试直到成功为止。
而compareAndSet利用JNI来完成CPU指令的操作。
public final boolean compareAndSet(int expect, int update) {   
    return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }


整体的过程就是这样子的,利用CPU的CAS指令,同时借助JNI来完成Java的非阻塞算法。其它原子操作都是利用类似的特性完成的。


-----------内存模型------------
http://www.myexception.cn/other/1017209.html

在java中,所有实例域、静态域和数组元素存储在堆内存中,堆内存在线程之间共享(本文使用“共享变量”这个术语代指实例域,静态域和数组元素)。局部变量(Local variables),方法定义参数(java语言规范称之为formal method parameters)和异常处理器参数(exception handler parameters)不会在线程之间共享,它们不会有内存可见性问题,也不受内存模型的影响。

线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
然后,线程B到主内存中去读取线程A之前已更新过的共享变量。

如上图所示,本地内存A和B有主内存中共享变量x的副本。假设初始时,这三个内存中的x值都为0。线程A在执行时,把更新后的x值(假设值为1)临时存放在自己的本地内存A中。当线程A和线程B需要通信时,线程A首先会把自己本地内存中修改后的x值刷新到主内存中,此时主内存中的x值变为了1。随后,线程B到主内存中去读取线程A更新后的x值,此时线程B的本地内存的x值也变为了1。


java内存模型JMM主要是为了规定线程和内存之间的一些关系.
系统存在一个主内存, java中所有实例变量都存储在主内存中,对于所有线程是共享的
.每条线程都有自己的工作内存, 工作内存由缓存和堆栈两部分组成, 缓存中保存的是主存中变量的拷贝,
缓存不总和主存同步,也就是缓存中变量的修改可能没有立刻写到
主存中,堆栈中保存的是线程的局部变量,线程之间无法相互直接访问堆栈中的变量.

线程的working memory工作内存是cpu的寄存器和高速缓存的抽象描述:现在的计算机,cpu在计算的时候,并不总是从内存读取数据,它的数据读取顺序优先级 是:寄存器-高速缓存-内存。线程耗费的是CPU,线程计算的时候,原始的数据来自内存,在计算过程中,有些数据可能被频繁读取,这些数据被存储在寄存器和高速缓存中,当线程计算完后,这些缓存的数据在适当的时候应该写回内存。当多个线程同时读写某个内存数据时,就会产生多线程并发问题,

原子性,有序性,可见性

原子性,对除了long和double之外的基本类型的简单操作都具有原子性。简单操作就是赋值或者return。比如”a = 1;”和 “return a;”这样的操作都具有原子性。但是在Java中,类似”a += b”这样的操作不具有原子性。
对long和double的简单操作不具有原子性。但是,一旦给这两个类型的属性加上volatile修饰符,对它们的简单操作就会具有原子性。


synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
volatile可以保证内存可见性,不能保证并发有序性。

AtomicInteger 中用volatile解决可见性问题, 用CAS来解决有序性问题


--------ThreadLocal---------
当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。  不存在变量共享的问题


-------线程安全的实现方法--------
1> 互斥同步 synchronized   Lock(不推荐)
2> 非阻塞同步 CAS Compare And Swap   
AtomicInteger
3> 无同步方案  不涉及共享数据, 一般的web应用
临时变量, ThreadLocal

总结 用同步的方式来解决多线程问题,是由于用到了共享变量,如果没有共享变量则无需用同步。

---------共享数据类型------------
1> 不可变 final修饰的基本数据类型

2> 绝对线程安全  定义: 调用者不需要任何额外的同步措施
没有绝对安全的,多线程频繁的对Vector进行add get remove 操作,也会出现数组越界异常。
需要用到同步块synchronized(vector){....}

3> 相对线程安全
Vector HashTable

4> 线程兼容 (线程不安全)
ArrayList  HashMap

package com.hp.hpsc.logservice.client;

import java.util.Vector;

public class TestVector {
	private static Vector<Integer> vector = new Vector<Integer>();
	
	public static void main(String[] args) {
		while(true){
			for (int i = 0; i < 10; i++) {
				vector.add(i);
			}
			
			Thread removeThread = new Thread(new Runnable(){
				@Override
				public void run() {
					for (int i = 0; i < vector.size(); i++) {
						vector.remove(i);
					}
				}
				
			});
			
			Thread printThread = new Thread(new Runnable(){
				@Override
				public void run() {
					for (int i = 0; i < vector.size(); i++) {
						System.out.println(Thread.currentThread().getName() +":" + vector.get(i));
					}
				}
				
			});
			
			removeThread.start();
			printThread.start();
			
			while (Thread.activeCount() > 20); 
		}
	}
}



异常
Exception in thread "Thread-1293" java.lang.ArrayIndexOutOfBoundsException: Array index out of range: 12
	at java.util.Vector.get(Vector.java:694)
	at com.hp.hpsc.logservice.client.TestVector$2.run(TestVector.java:28)
	at java.lang.Thread.run(Thread.java:619)
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics