线程 + java内存机制

这篇主要说说线程安全

首先 推荐一篇写的挺好的文章:http://hllvm.group.iteye.com/group/wiki/2877-synchronized-volatile

下面是内容:

所谓线程安全就是:控制多个线程对某个资源的访问和修改。

多线程中线程和线程之间不能传递数据:


现在了解下java内存模型(jMM):规定JVM有主内存 ------多线程共享(每个线程有自己的工作内存)

new一个对象的时候也就是被分配到主内


线程操作某对象的顺序:

  1. 从主内存复制变量副本到工作内存
  2. 执行代码,修改共享变量值
  3. 工作内存数据刷新到主内存

多线程有两个概念需要理解:

  1. 可见性
  2. 有序性

可见性:当一个共享变量在多线程的工作 内存有副本时,如果一个线程修改了这个共享变量,其他线程应该能看到这个被修改的值,这就是多线程的可见性

有序性:表现在两方面【java用synchronized关键字来保证】

读取:(引用变量)

线程引用变量(其实就是读取)时,不能直接从主内存中引用,如果工作内存没有该变量则:从主内存中拷贝一个副本到工作内存,完成后,线程引用该副本

当同一线程再度引用该字段时,

可能1.直接从工作内存中取副本

可能2,.重新从主内存获得副本

写(给变量赋值);

线程不能直接给主存中字段赋值,它将值指定给工作内存中的变量副本,完成后,这个变量副本同步到主内存中。




synchronized关键字

当一段代码会修改共享变量时,这段代码成为互斥区和临界区

用法:

synchronized(锁){

临界区代码

}

比如为保证银行账户的安全:

public synchronized void add(int number){

balance= balance + num;

}

public synchronized void withdraw(int number){

balance = balance - num;

}

刚才不是说了synchronized的用法是这样的吗:

Java代码  收藏代码
  1. synchronized(锁){  
  2.    临界区代码  
  3. 一个线程执行临界代码的步骤
  4. a.获得同步锁
  5. b.清空工作内存
  6. c.从主内存拷贝副本到工作内存
  7. d.执行代码
  8. e.将变量从工作内存写回到主内存
  9. f.释放锁

 


那么对于public synchronized void add(int num)这种情况,意味着什么呢?其实这种情况,
锁就是这个方法所在的对象。

同理,如果方法是public  static synchronized void add(int num),那么锁就是这个方法所在的class
        理论上,每个对象都可以做为锁,但一个对象做为锁时,应该被多个线程共享,这样才显得有意义,在并发环境下,一个没有共享的对象作为锁是没有意义的。假如有这样的代码:

Java代码  收藏代码
  1. public class ThreadTest{  
  2.   public void test(){  
  3.      Object lock=new Object();  
  4.      synchronized (lock){  
  5.         //do something  
  6.      }  
  7.   }  
  8. }  

 

上面的代码中:
lock变量作为一个锁存在根本没有意义,因为它根本不是共享对象,每个线程进来都会执行Object lock=new Object();每个线程都有自己的lock,根本不存在锁竞争。

----------------------------------------------------------------------------

有关锁的状态:

每个锁对象有两个队列:就绪队列+阻塞队列:

就绪队存储将要获得锁的线程

阻塞队列存储被阻塞的线程,当一个线程被唤醒(notify)后才会进入就绪队列,等待执行(cpu调用)


当线程a第一次执行account.add()方法时,jvm会检查锁对象account的就绪队列是否有线程在排队,如果有则表明account锁已经被占用了,

由于当线程a是第一次执行,就绪队列为空,所以线程a获得锁,执行account.add()方法,

如果恰好在这个时候线程b要执行account.withdraw方法,jvm检查到线程a正在占用锁,并且没有释放,所以线程b进入就绪队列,等得到锁才能执行




消费者/生产者 模式

一种典型的线程同步模型:

很多时候

1.并不是只要保证线程的互斥就够了

2.线程之间还需要进行沟通协作

所以才有了生产者消费者模式:


如何让线程主动放弃锁:用wait()方法,wait()是从object来的,所以任何对象有这个方法:


Object lock = new Object();

synchronized(lock){

balance = balance - num;

lock.wait();

}

如果一个线程获得了锁lock,进入同步块,执行lock.wait(),那么这线程会进入锁对象的阻塞队列,如果调用lock.notify()则会通知阻塞队列的线程进入就绪队列


申明一个盘子只能放一个鸡蛋

public class Plate{

List<Object> eggs = new ArrayList<Object>();
public synchronized  Object getEgg(){
if(eggs.size()==0){
try {
wait();//自己进入锁对象的阻塞队列
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Object egg = eggs.get(0);
eggs.clear();//清空盘子  
notify();//唤醒阻塞队列的某线程到就绪队列  
return egg;
}
public synchronized void putEgg(Object egg){
if(eggs.size()!=0){
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
eggs.add(egg);//往盘子里放鸡蛋
notify();//唤醒阻塞队列的某线程到就绪队列  
}

}


声明一个Plate对象为plate,被线程A和线程B共享,A专门放鸡蛋B专门拿鸡蛋。假设
1 开始,A调用plate.putEgg方法,此时eggs.size()为0,因此顺利将鸡蛋放到盘子,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列还没有线程。
2 又有一个
A线程对象调用plate.putEgg方法,此时eggs.size()不为0,调用wait()方法,自己进入了锁对象的阻塞队列。
3 此时,来了一个B线程对象,调用plate.getEgg方法,eggs.size()不为0,顺利的拿到了一个鸡蛋,还执行了notify()方法,唤醒锁的阻塞队列的线程,此时阻塞队列有一个A线程对象,唤醒后,它进入到就绪队列,就绪队列也就它一个,因此马上得到锁,开始往盘子里放鸡蛋,此时盘子是空的,因此放鸡蛋成功。
4 假设接着来了线程A,就重复2;假设来料线程B,就重复3。
 



======================================================

有关map

Hashmap(不安全)允许null key 和null  value

HashTable(线程安全)不允许null key


方法 :

clear():

remove(Object  key);

put(Object  key,Object value);

get(Object  key);

containskey(Object  key);

containsvalue(Object  value);

isEmpty();


两种基本的数据存储机构:数组,链表,哈希表(前面两者的改进)






===================================================================

java内存管理机制:



内存空间逻辑划分:

JVM会把申请的内存从逻辑上划分为三个区域:方法区,堆 ,栈

  • 方法区:默认64M,JVM会将加载的java类,类的结构(属性和方法),类静态成员
  • :默认64M, 保存对象的属性值
  • :默认1M,程序运行时每当有方法调用时,JVM会在栈中划分一块内存,供局部变量使用
  •       方法调用结束,JVM回收该内存



java数据类型:

JAVA内存管理 - 小白 - 小白的博客

1、基本数据类型:没封装指针的变量。
声明此类型变量,只会在栈中分配一块内存空间。

2、引用类型:就是底层封装指针的数据类型。
他们在内存中分配两块空间,
第一块内存分配在栈中,只存放内存地址,不存放具体数值,我们也把它叫指针类型的变量,
第二块内存分配在堆中,存放的是具体数值,如对象属性值等。
3、下面我们从一个例子来看一看:
public class Student {
String stuId;
String stuName;
int stuAge;
}

public class TestStudent {
public static void main(String[] args) {
Student zhouxingxing = new Student();
String name = new String("旺旺");
int a = 10;
char b = 'm';
zhouxingxing.stuId = "9527";
zhouxingxing.stuName = "周星星";
zhouxingxing.stuAge = 25;
}
}

(1)类当然是存放在方法区里面的。

(2) Student zhouxingxing = new Student();
这行代码就创建了两块内存空间,
第一个在栈中,名字叫zhouxingxing,它就相当于指针类型的变量,我们看到它并不存放学生的姓名、年龄等具体的数值,而是存放堆中第二块内存的地址,
第二块才存放具体的数值,如学生的编号、姓名、年龄等信息。

(3) int a = 10;
这是 基本数据类型 变量,具体的值就存放在栈stack中,并没有只指针的概念!

下图就是本例的内存布置图:
JAVA内存管理 - 小白 - 小白的博客

java值传递和引用传递:



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值