这篇主要说说线程安全:



下面是内容:



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

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


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

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

java中实时释放内存 java线程内存释放_多线程

线程操作某对象的顺序:

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

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

  1. 可见性

    java中实时释放内存 java线程内存释放_多线程_02

  2. 有序性

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

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

读取:(引用变量)

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

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

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

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

写(给变量赋值);

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


java中实时释放内存 java线程内存释放_JVM_03


synchronized关键字

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

用法:

synchronized(锁){
临界区代码
}
比如为保证银行账户的安全:
public synchronized void add(int number){
balance= balance + num;
}
public synchronized void withdraw(int number){
balance = balance - num;
}

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


Java代码  

java中实时释放内存 java线程内存释放_多线程_04



  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代码  

java中实时释放内存 java线程内存释放_多线程_04


1. public class
2. public void
3. new
4. synchronized
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中实时释放内存 java线程内存释放_多线程_06



java中实时释放内存 java线程内存释放_java中实时释放内存_07


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

java内存管理机制:


java中实时释放内存 java线程内存释放_java中实时释放内存_08

内存空间逻辑划分:

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

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



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)类当然是存放在方法区里面的。



Student zhouxingxing = new Student();


这行代码就创建了两块内存空间,


第一个在栈中,名字叫zhouxingxing,它就相当于指针类型的变量,我们看到它并不存放学生的姓名、年龄等具体的数值,而是存放堆中第二块内存的地址,


第二块才存放具体的数值,如学生的编号、姓名、年龄等信息。




(3) int a = 10;


这是 基本数据类型



下图就是本例的内存布置图:





java值传递和引用传递: