最近想将java基础的一些东西都整理整理写下来这是对知识的总结也是一种乐趣已经拟好了提纲大概分为这几个主题 java线程安全java垃圾收集java并发包详细介绍java profile和jvm性能调优慢慢写吧本人jameswxx原创文章转载请注明出处我费了很多心血多谢了关于java线程安全网上有很多资料我只想从自己的角度总结对这方面的考虑有时候写东西是很痛苦的知道一些东西但想用文字说清楚却不是那么容易我认为要认识java线程安全必须了解两个主要的点java的内存模型java的线程同步机制特别是内存模型java的线程同步机制很大程度上都是基于内存模型而设定的后面我还会写java并发包的文章详细总结如何利用java并发包编写高效安全的多线程并发程序暂时写得比较仓促后面会慢慢补充完善
浅谈java内存模型
不同的平台内存模型是不一样的但是jvm的内存模型规范是统一的其实java的多线程并发问题最终都会反映在java的内存模型上所谓线程安全无非是要控制多个线程对某个资源的有序访问或修改总结java的内存模型要解决两个主要的问题可见性和有序性我们都知道计算机有高速缓存的存在处理器并不是每次处理数据都是取内存的JVM定义了自己的内存模型屏蔽了底层平台内存管理细节对于java开发人员要清楚在jvm内存模型的基础上如果解决多线程的可见性和有序性
那么何谓可见性?多个线程之间是不能互相传递数据通信的它们之间的沟通只能通过共享变量来进行Java内存模型(JMM)规定了jvm有主内存主内存是多个线程共享的当new一个对象的时候也是被分配在主内存中每个线程都有自己的工作内存工作内存存储了主存的某些对象的副本当然线程的工作内存大小是有限制的当线程操作某个对象时执行顺序如下
()从主存复制变量到当前工作内存(read and load)
()执行代码改变共享变量值(use and assign)
()用工作内存数据刷新主存相关内容(store and write)
JVM规范定义了线程对主存的操作指令readloaduseassignstorewrite当一个共享变量在多个线程的工作内存中都有副本时如果一个线程修改了这个共享变量那么其他线程应该能够看到这个被修改后的值这就是多线程的可见性问题
那么什么是有序性呢?线程在引用变量时不能直接从主内存中引用如果线程工作内存中没有该变量则会从主内存中拷贝一个副本到工作内存中这个过程为readload完成后线程会引用该副本当同一线程再度引用该字段时有可能重新从主存中获取变量副本(readloaduse)也有可能直接引用原来的副本(use)也就是说 readloaduse顺序可以由JVM实现系统决定
线程不能直接为主存中中字段赋值它会将值指定给工作内存中的变量副本(assign)完成后这个变量副本会同步到主存储区(storewrite)至于何时同步过去根据JVM实现系统决定有该字段则会从主内存中将该字段赋值到工作内存中这个过程为readload完成后线程会引用该变量副本当同一线程多次重复对字段赋值时比如
Java代码
for(int i=i<i++)
a++线程有可能只对工作内存中的副本进行赋值只到最后一次赋值后才同步到主存储区所以assignstoreweite顺序可以由JVM实现系统决定假设有一个共享变量x线程a执行x=x+从上面的描述中可以知道x=x+并不是一个原子操作它的执行过程如下
从主存中读取变量x副本到工作内存给x加 将x加后的值写回主 存
如果另外一个线程b执行x=x执行过程如下
从主存中读取变量x副本到工作内存给x减 将x减后的值写回主存
那么显然最终的x的值是不可靠的假设x现在为线程a加线程b减从表面上看似乎最终x还是为但是多线程情况下会有这种情况发生
线程a从主存读取x副本到工作内存工作内存中x值为 线程b从主存读取x副本到工作内存工作内存中x值为 线程a将工作内存中x加工作内存中x值为 线程a将x提交主存中主存中x为 线程b将工作内存中x值减工作内存中x值为 线程b将x提交到中主存中主存中x为
同样x有可能为如果x是一个银行账户线程a存款线程b扣款显然这样是有严重问题的要解决这个问题必须保证线程a和线程b是有序执行的并且每个线程执行的加或减是一个原子操作看看下面代码
Java代码
public class Account {
private int balance
public Account(int balance) { thisbalance = balance}
public int getBalance() { return balance}
public void add(int num) { balance = balance + num}
public void withdraw(int num) { balance = balance num}
public static void main(String[] args) throws InterruptedException { Account account = new Account()Thread a = new Thread(new AddThread(account ) add)Thread b = new Thread(new WithdrawThread(account ) withdraw)astart()bstart()ajoin()bjoin()Systemoutprintln(accountgetBalance())}
static class AddThread implements Runnable { Account accountint amount
public AddThread(Account account int amount) { thisaccount = accountthisamount = amount}
public void run() { for (int i = i < i++) { accountadd(amount)}
static class WithdrawThread implements Runnable { Account accountint amount
public WithdrawThread(Account account int amount) { thisaccount = accountthisamount = amount}
public void run() { for (int i = i < i++) { accountwithdraw(amount)}第一次执行结果为第二次执行结果为每次执行的结果都是不确定的因为线程的执行顺序是不可预见的这是java同步产生的根源synchronized关键字保证了多个线程对于同步块是互斥的synchronized作为一种同步手段解决java多线程的执行有序性和内存可见性而volatile关键字之解决多线程的内存可见性问题后面将会详细介绍