欢迎来到专业的卓越文库网平台! 工作总结 工作计划 述职报告 心得体会 疫情防控 思想汇报 事迹材料 教案设计
当前位置:首页 > 范文大全 > 公文范文 > 正文

java安全线程(全文完整)

时间:2022-08-30 15:00:06 来源:网友投稿

下面是小编为大家整理的java安全线程(全文完整),供大家参考。

java安全线程(全文完整)

 

 最近想将 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 一个对象的时候, 也是被分配在主内存中, 每个线程都有自己的工作内存, 工作内存存储了主存的某些对象的副本, 当然线程的工作内存大小是有限制的。

 当线程操作某个对象时, 执行顺序如下:

  (1)

 从主存复制变量到当前工作内存 (read and load)

  (2)

 执行代码, 改变共享变量值 (use and assign)

  (3)

 用工作内存数据刷新主存相关内容 (store and write)

  JVM 规范定义了线程对主存的操作指令:

 read, load, use, assign, store, write。当一个共享变量在多个线程的工作内存中都有副本时, 如果一个线程修改了这个共享变量, 那么其他线程应该能够看到这个被修改后的值, 这就是多线程的可见性问题。

 那么, 什么是有序性呢 ? 线程在引用变量时不能直接从主内存中引用,如果线程工作内存中没有该变量, 则会从主内存中拷贝一个副本到工作内存中,这个过程为 read-load, 完成后线程会引用该副本。

 当同一线程再度引用该字段时, 有可能重新从主存中获取变量副本(read-load-use) , 也有可能直接引用原来的副本 (use) , 也就是说 read, load, use 顺序可以由 JVM 实现系统决定。

 线程不能直接为主存中中字段赋值, 它会将值指定给工作内存中的变量副本(assign) , 完成后这个变量副本会同步到主存储区(store- write) , 至于何时同步过去, 根据 JVM 实现系统决定. 有该字段, 则会从主内存中将该字段赋值到工作内存中, 这个过程为 read-load, 完成后线程会引用该变量副本, 当同一线程多次重复对字段赋值时, 比如:

 Java 代码

  1.

 for(int i=0; i<12.

  a++;

 线程有可能只对工作内存储区, 所以 assign, stor享变量 x, 线程 a 执行 x操作, 它的执行过程如下1 从主存中读取变量 x 副2 给 x 加 1 3 将 x 加 1 后的值写回主如果另外一个线程 b 执行1 从主存中读取变量 x 副2 给 x 减 1 3 将 x 减 1 后的值写回主那么显然, 最终的 x 的值减 1, 从表面上看, 似乎最1:

 线程 a 从主存读取 x2:

 线程 b 从主存读取 x3:

 线程 a 将工作内存中4:

 线程 a 将 x 提交主存5:

 线程 b 将工作内存中6:

 线程 b 将 x 提交到中同样, x 有可能为 11, 如这样是有严重问题的, 要并且每个线程执行的加 1Java 代码

  1.

 public class Ac2.

  3.

 private int4.

  5.

 public Acco6.

 this. ba7.

 }

  8.

  9.

 public int 10.

  return 11.

  }

  12.

 0; i++)

  存中的副本进行赋值, 只到最后一次赋值后re, weite 顺序可以由 JVM 实现系统决定。x=x+1。

 从上面的描述中可以知道 x=x+1 并下:

 副本到工作内存 主 存 行 x=x-1, 执行过程如下:

 副本到工作内存 主存

 值是不可靠的。

 假设 x 现在为 10, 线程 a 加最终 x 还是为 10, 但是多线程情况下会有这副本到工作内存, 工作内存中 x 值为 10 副本到工作内存, 工作内存中 x 值为 10 x 加 1, 工作内存中 x 值为 11 存中, 主存中 x 为 11 x 值减 1, 工作内存中 x 值为 9 主存中, 主存中 x 为 9

 如果 x 是一个银行账户, 线程 a 存款, 线程要解决这个问题, 必须保证线程a和线程b 是1 或减 1 是一个原子操作。

 看看下面代码:ccount {

  balance;

  ount(int balance)

 {

 alance = balance;

  getBalance()

 {

 balance;

  后才同步到主存假设有一个共并不是一个原子加 1, 线程 b这种情况发生:

 程 b 扣款, 显然是有序执行的,

 13.

  public void add(int num)

 {

 14.

  balance = balance + num;

  15.

  }

  16.

 17.

  public void withdraw(int num)

 {

 18.

  balance = balance - num;

  19.

  }

  20.

 21.

  public static void main(String[] args)

 throws InterruptedException {

 22.

  Account account = new Account(1000) ;

  23.

  Thread a = new Thread(new AddThread(account,

 20) ,

 "add") ;

  24.

  Thread b = new Thread(new WithdrawThread(account,

 20) ,

 "withdraw") ;

  25.

  a. start() ;

  26.

  b. start() ;

  27.

  a. join() ;

  28.

  b. join() ;

  29.

  System. out. println(account. getBalance() ) ;

  30.

  }

  31.

 32.

  static class AddThread implements Runnable {

 33.

  Account account;

  34.

  int

  amount;

  35.

 36.

  public AddThread(Account account,

 int amount)

 {

 37.

  this. account = account;

  38.

  this. amount = amount;

  39.

  }

  40.

 41.

  public void run()

 {

 42.

  for (int i = 0;

 i < 200000;

 i++)

 {

 43.

  account. add(amount) ;

  44.

  }

  45.

  }

  46.

  }

  47.

 48.

  static class WithdrawThread implements Runnable {

 49.

  Account account;

  50.

  int

  amount;

  51.

 52.

  public WithdrawThread(Account account,

 int amount)

 {

 53.

  this. account = account;

 54.

  thi55.

  }

  56.

 57.

  public 58.

  for59.

 60.

  }

  61.

  }

  62.

  }

  63. }

 第一次执行结果为 10200的, 因为线程的执行顺序synchronized 关键字保证一种同步手段, 解决 jav字之解决多线程的内存可

 synchronized 关键字

 上面说了, java序性的保证手段之一。

 当界区, 为了保证共享变量如下:

 Java 代码

  1.

 synchronized(锁2.

  临界区代码3.

 }

  为了保证银行账户的安全Java 代码

  1.

 public synchron2.

  balance = s. amount = amount;

  void run()

 {

 r (int i = 0;

 i < 100000;

 i++)

 {

  account. withdraw(amount) ;

  0, 第二次执行结果为 1060, 每次执行的结序是不可预见的。

 这是 java 同步产生的根证了多个线程对于同步块是互斥的, synchva 多线程的执行有序性和内存可见性, 而可见性问题。

 后面将会详细介绍。

  a 用 synchronized 关键字做为多线程并发当一段代码会修改共享变量, 这一段代码成量的正确性, synchronized 标示了临界区锁) {

 码

 全, 可以操作账户的方法如下:

 nized void add(int num)

 {

 balance + num;

  结果都是不确定根源,hronized 作为volatile 关键发环境的执行有成为互斥区或临。

 典型的用法

 3.

 }

  4.

 public synchron5.

  balance = 6.

 }

 刚才不是说了 synchroniJava 代码

  1.

 synchronized(锁2.

 临界区代码

 3.

 }

 那么对于 public synchr其实这种情况, 锁就是这synchronized void add

 理论上, 每个对程共享, 这样才显得有意意义的。

 假如有这样的代Java 代码

  1.

 public class Th2.

 public void t3.

  Object loc4.

  synchroniz5.

 //do so6.

  }

  7.

 }

  8.

 }

 lock 变量作为一个锁存在进来都会执行 Object lo在锁竞争。

 每个锁对象都有列存储了 将要获得锁的线nized void withdraw(int num)

 {

 balance - num;

  ized 的用法是这样的吗:

 锁) {

 ronized void add(int num) 这种情况, 意这个方法所在的对象。

 同理, 如果方法是 pud(int num) , 那么锁就是这个方法所在的 c对象都可以做为锁, 但一个对象做为锁时,意义, 在并发环境下, 一个没有共享的对象代码:

 hreadTest{

 est() {

 ck=new Object() ;

  ed (lock) {

 omething

 在根本没有意义, 因为它根本不是共享对象ock=new Object() ; 每个线程都有自己的 lo有两个队列, 一个是就绪队列, 一个是阻塞线程, 阻塞队列存储了被阻塞的线程, 当一意味着什么呢?ublic

 static class。

 应该被多个线象作为锁是没有象, 每个线程ock, 根本不存塞队列, 就绪队一个被线程被唤

 醒 (notify) 后, 才会进次执行 account. add 方法线程在等待, 如果有则表account 的就绪队列为空恰好在这个时候, 线程 b了锁还没有释放, 所以线以执行。

 一个线程执行临界区代码1 获得同步锁 2 清空工作内存 3 从主存拷贝变量副本到4 对这些变量计算 5 将变量从工作内存写回6 释放锁 可见, synchronized 既保见性。

 生产者/消费者模式

 生产者/消费者不是光保证多个线程对某有协作的。

 假设有这样一种放一颗鸡蛋, A 专门往盘鸡蛋, B 专门从盘子里拿其实盘子就是一个互斥区是主动放弃锁, B 等待时如何让线程主动释放锁 很简单, 调用锁的 wait(都有这个方法。

 看这个代Java 代码

  1.

 Object lock=new2.

  synchronized3.

  balance 4.

  //这里放5.

  lock. wai6.

 }

 如果一个线程获得了锁 l进入到就绪队列, 等待 cpu 的调度。

 当一开法时, jvm 会检查锁对象 account 的就绪队表明 account 的锁已经被占用了 , 由于是第空, 所以线程 a 获得了锁, 执行 account. ab 要执行 account. withdraw 方法, 因为线线程 b 要进入 account 的就绪队列, 等到得码过程如下:

 到工作内存 回到主存 保证了多线程的并发有序性, 又保证了多线者模式其实是一种很经典的线程同步模型,某共享资源操作的互斥性就够了 , 往往多个种情况, 有一个桌子, 桌子上面有一个盘子盘子里放鸡蛋, 如果盘子里有鸡蛋, 则一直拿鸡蛋, 如果盘子里没鸡蛋, 则等待直到盘区, 每次往盘子放鸡蛋应该都是互斥的, A时还要提醒 A 放鸡蛋。

 () 方法就好。

 wait 方法是从 Object 来的,代码片段:

 w Object() ; //声明了一个对象作为锁

 d (lock)

 {

 = balance - num;

  放弃了同步锁, 好不容易得到, 又放弃了

 t() ;

  lock, 进入了同步块, 执行 lock. wait() ,开始线程 a 第一队列是否已经有第一次运行,add 方法。

 如果线程 a 已经获得得到锁后才可线程的内存可很多时候, 并个线程之间都是子, 盘子里只能直等到盘子里没盘子里有鸡蛋。的等待其实就所以任意对象

 那么这个线程

 会进入到 lock 的阻塞队线程进入就绪队列。

 声明一个盘子, 只能放一

  Java 代码

  1.

 import java. uti2.

 import java. uti3.

  4.

 public class Pl5.

  6.

 List<Object7.

  8.

 public sync9.

 if (egg10.

  try11.

 12.

  }

 c13.

  }

  14.

  }

  15.

 16.

  Object 17.

  eggs. cl18.

  notify(19.

  System.20.

  return 21.

  }

  22.

 23.

  public sync24.

  if (egg25.

  try26.

 27.

  }

 c28.

  }

  29.

  }

  30.

  eggs. ad31.

  notify(32.

  System.33.

  }

  34.

  35.

  static clas36.

  private37.

  private队列。

 如果调用 lock. notify() 则会通知阻一个鸡蛋 l. ArrayList;

  l. List;

  ate {

 > eggs = new ArrayList<Object>() ;

 chronized Object getEgg()

 {

 gs. size()

 == 0)

 {

 y {

  wait() ;

  catch (Inter...

推荐访问:java安全线程 线程 完整 全文

猜你喜欢