2.3 volatile 与 Java 内存模型(JMM)

  • volatile对于保证操作的原子性是由非常大的帮助的(可见性)。但是需要注意的是,volatile并不能代替锁,它也无法保证一些复合操作的原子性。比如下面的例子,通过volatile是无法保证i++的原子性操 作的:
  1. static volatile int i = 0;
  2. public static class PlusTask implements Runable {
  3. @Override
  4. public void run() {
  5. for (int k = 0; k < 10000; k++) {
  6. i++;
  7. }
  8. }
  9. }
  10. public static void main(String[] args) throws InterruptedException {
  11. Thread[] threads = new Thread[10];
  12. for (int i = 0; i < 10; i++) {
  13. threads[i] = new Thread(new PlusTask());
  14. threads[i].start();
  15. }
  16. for (int i = 0; i < 10; i++) {
  17. threads[i].join(); //等待所有线程结束
  18. }
  19. System.out.println(i);
  20. }
  • 上述代码的输出总是会小于100000,由于i++不是原子性的。
  • 此外,volatile也能保证数据的可见性和有序性。下面再来看一个简单的例子:
  1. public class NoVisibility {
  2. private static boolean ready;
  3. private static int number;
  4. private static class ReaderThread extends Thread {
  5. public void run() {
  6. while (!ready);
  7. System.out.println(number);
  8. }
  9. }
  10. public static void main(String[] args) throws InterruptedException {
  11. new ReaderThread().start();
  12. Thread.sleep(1000);
  13. number = 42;
  14. ready = true;
  15. Thread.sleep(1000);
  16. }
  17. }
  • 在虚拟机的Client模式下,由于JIT并没有做足够的优化,在主线程修改ready变量的状态后,ReaderThread可以发现这个改动,并退出程序。但是在Server模式下,由于系统优化的结果,ReaderThread线程无法“看到”主线程中的修改,导致ReaderThread永远无法退出。
  • 注意:可以使用Java虚拟机参数-server切换到Server模式。

2.4 分门别类的管理:线程组

  • 在一个系统中,如果线程数量很多,而且功能分配比较明确,就可以将相同功能的线程放置在一个线程组里。
  • 线程组的使用非常简单,如下:
  1. public class ThreadGroupName implements Runnable {
  2. public static void main(String[] args) {
  3. ThreadGroup tg = new ThreadGroup("PrintGroup");
  4. //使用Thread的构造函数,指定线程所属的线程组,将线程和线程组关联起来。
  5. Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
  6. Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
  7. t1.start();
  8. t2.start();
  9. //获得活动线程的总数,估计值。
  10. System.out.println(tg.activeCount());
  11. //打印这个线程组中所有的线程信息。
  12. tg.list();
  13. }
  14. @Override
  15. public void run() {
  16. String groupAndName = Thread.currentThread().getThreadGroup().getName() + "-" + Thread.currentThread().getName();
  17. while (true) {
  18. System.out.println("I am " + groupAndName);
  19. try {
  20. Thread.sleep(3000);
  21. } catch (InterruptedException e) {
  22. e.printStaceTrace();
  23. }
  24. }
  25. }
  26. }

2.5 驻守后台:守护线程(Daemon)

  • 守护线程是一种特殊的线程,它是系统的守护者,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程。
  • 与之对应的是用户线程,用户线程可以认为是系统的工作线程,它会完成这个程序应该要完成的业务操作。
  • 当一个java应用内,只有守护线程时,Java虚拟机就会自然退出。
  • 下面简单地看一下守护线程的使用:
  1. public class DaemonDemo {
  2. public static class DaemonT extends Thread {
  3. public void run() {
  4. while (true) {
  5. System.out.println("I am alive");
  6. try {
  7. Thread.sleep(1000);
  8. } catch (InterruptedException e) {
  9. e.printStackTrace();
  10. }
  11. }
  12. }
  13. }
  14. public static void main(String[] args) throws InterruptedException {
  15. Thread t = new DaemonT();
  16. t.setDaemon(true); //将线程t设置为守护线程。必须在start()之前设置。
  17. t.start();
  18. Thread.sleep(2000);
  19. }
  20. }
  • 由于t被设置为守护线程,系统中只有主线程main为用户线程,因此在main线程休眠2秒后退出时,整个程序也随之结束。但如果不把线程t设置为守护线程,main线程结束后,t线程还会不停地打印,永远不会结束。

2.6 先干重要的事:线程优先级

  • 在Java中,使用1到10表示线程优先级。一般可以使用内置的三个静态标量表示:
  1. public final static int MIN_PRIORITY = 1;
  2. public final static int MIN_PRIORITY = 5;
  3. public final static int MIN_PRIORITY = 10;
  • 数字越大则优先级越高,但有效范围在1到10之间。
  • 下面的代码展示了优先级的作用。高优先级的线程倾向于更快地完成。
  1. public class PriorityDemo {
  2. public static class HightPriority extends Thread {
  3. static int count = 0;
  4. public void run() {
  5. while (true) {
  6. synchronized (PriorityDemo.class) {
  7. count++;
  8. if (count > 10000000) {
  9. System.out.println("HightPriority is complete");
  10. break;
  11. }
  12. }
  13. }
  14. }
  15. }
  16. public static class LowPriority extends Thread {
  17. static int count = 0;
  18. public void run() {
  19. while (true) {
  20. synchronized (PriorityDemo.class) {
  21. count++;
  22. if (count > 10000000) {
  23. System.out.println("LowPriority is complete");
  24. break;
  25. }
  26. }
  27. }
  28. }
  29. }
  30. public static void main(String[] args) {
  31. Thread high = new HightPriority();
  32. LowPriority low = new new LowPriority();
  33. high.setPriority(Thread.MAX_PRIORITY);
  34. low.setPriority(Thread.MIN_PRIORITY);
  35. low.start();
  36. high.start();
  37. }
  38. }
  • 在对count累加前,我们使用synchronized产生了一次资源竞争。目的是使得优先级的差异表现得更为明显。

2.7 线程安全的概念与synchronized

  • 关键字synchronized的作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性。
  • 关键字synchronized可以有多种用法。
    • 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
    • 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
    • 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
  • 下述代码,将synchronized作用于一个给定对象instance,因此,每次当线程进入被synchronized包裹的代码段,就都会要求请求instance实例的锁。如果当前有其他线程正持有这把锁,那么新到的线程就必须等待。这样,就保证了每次只能有一个线程执行i++操 作。
  1. public class AccountingSync implements Runnable {
  2. static AccountingSync instance = new AccountingSync();
  3. static int i = 0;
  4. @Override
  5. public void run() {
  6. for (int j = 0; j < 10000000; j++) {
  7. synchronized(instance) {
  8. i++;
  9. }
  10. }
  11. }
  12. }
  • 当然,上述代码也可以写成如下形式,两者是等价的:
  1. public class AccountingSync2 implements Runnable {
  2. static AccountingSync2 instance = new AccountingSync2();
  3. static int i = 0;
  4. public synchronized void increase() {
  5. i++;
  6. }
  7. @Override
  8. public void run() {
  9. for (int j = 0; j < 10000000; j++) {
  10. increase();
  11. }
  12. }
  13. public static void main(String[] args) {
  14. Thread t1 = new Thread(instance);
  15. Thread t2 = new Thread(instance);
  16. t1.start();t2.start();
  17. t1.join();t2.join();
  18. System.out.println(i);
  19. }
  20. }
  • 上述代码中,synchronized关键字作用于一个实例方法。这就是说在进入increase()方法前,线程必须获得当前对象实例的锁。在本例中就是instance对象。这里使用Runnable接口创建两个线程,并且这两个线程都指向同一个Runnable接口实例(instance)这样才能保证两个线程在工作时,能够关注到同一对象锁上去,从而保证线程安全。
  • 一种错误的同步方式如下:
  1. public class AccountingSyncBad implements Runnable {
  2. static int i = 0;
  3. public synchronized void increase() {
  4. i++;
  5. }
  6. @Override
  7. public void run() {
  8. for (int j = 0; j < 10000000; j++) {
  9. increase();
  10. }
  11. }
  12. public static void main(String[] args) {
  13. Thread t1 = new Thread(new AccountingSyncBad());
  14. Thread t2 = new Thread(new AccountingSyncBad());
  15. t1.start();t2.start();
  16. t1.join();t2.join();
  17. System.out.println(i);
  18. }
  19. }
  • 上述代码中,这两个线程的Runnable实例并不是同一个对象。使用的是两把不同的锁。因此,线程安全是无法保证的。
  • 但是我们只要简单地修改上述代码,就能使其正确执行。那就是使用synchronized的第三种用法,将其作用于静态方法。
  1. public static synchronized void increase() {
  2. i++;
  3. }
  • 这样即使两个线程指向不同的Runable对象,但由于方法块需要请求的是当前类的锁,而非当前实例,因此,线程间还是可以正确同步。
  • 除了用于线程同步、确保线程安全外,synchronized还可以保证线程间的可见性和有序性。从可见性的角度上讲,synchronized可以完全替代volatile的功能,只是使用上没有那么方便。就有序性而言,由于synchronized限制每次只有一个线程可以访问同步块,因此,无论同步块内的代码如何被乱序执行,只要保证串行语义一致,那么执行结果总是一样的。而其他访问线程,又必须在获得锁后方能进入代码块读取数据,因此,它们看到的最终结果并不取决于代码的执行过程,从而有序性问题自然得到了解决。

第2章 Java并行程序基础(二)的更多相关文章

  1. JAVA并行程序基础二

    JAVA并行程序基础二 线程组 当一个系统中,如果线程较多并且功能分配比较明确,可以将相同功能的线程放入同一个线程组里. activeCount()可获得活动线程的总数,由于线程是动态的只能获取一个估 ...

  2. 第2章 Java并行程序基础(三)

    2.8 程序中的幽灵:隐蔽的错误 2.8.1 无提示的错误案例 以求两个整数的平均值为例.请看下面代码: int v1 = 1073741827; int v2 = 1431655768; Syste ...

  3. 第2章 Java并行程序基础(一)

    2.1 有关线程你必须知道的事 进程是系统进行资源分配和调度的基本单位,是程序的基本执行实体. 线程就是轻量级进程,是程序执行的最小单位. 线程的生命周期,如图2.3所示. 线程的所有状态都在Thre ...

  4. Java并发程序设计(二)Java并行程序基础

    Java并行程序基础 一.线程的生命周期 其中blocked和waiting的区别: 作者:赵老师链接:https://www.zhihu.com/question/27654579/answer/1 ...

  5. JAVA并行程序基础

    JAVA并行程序基础 一.有关线程你必须知道的事 进程与线程 在等待面向线程设计的计算机结构中,进程是线程的容器.我们都知道,程序是对于指令.数据及其组织形式的描述,而进程是程序的实体. 线程是轻量级 ...

  6. JAVA并行程序基础一

    JAVA并行程序基础一 线程的状态 初始线程:线程的基本操作 1. 新建线程 新建线程只需要使用new关键字创建一个线程对象,并且用start() ,线程start()之后会执行run()方法 不要直 ...

  7. Java并行程序基础。

    并发,就是用多个执行器(线程)来完成一个任务(大任务)来处理业务(提高效率)的方法.而在这个过程中,会涉及到一些问题,所以学的就是解决这些问题的方法. 线程的基本操作: 1.创建线程:只需要new一个 ...

  8. 到头来还是逃不开Java - Java13程序基础

    java程序基础 没有特殊说明,我的所有学习笔记都是从廖老师那里摘抄过来的,侵删 引言 兜兜转转到了大四,学过了C,C++,C#,Java,Python,学一门丢一门,到了最后还是要把Java捡起来. ...

  9. Spring MVC + Spring + Mybitis开发Java Web程序基础

    Spring MVC + Spring + Mybitis是除了SSH外的另外一种常见的web框架组合. Java web开发和普通的Java应用程序开发是不太一样的,下面是一个Java web开发在 ...

随机推荐

  1. 带 sin, cos 的线段树 - 牛客

    链接:https://www.nowcoder.com/acm/contest/160/D来源:牛客网 题目描述给出一个长度为n的整数序列a1,a2,...,an,进行m次操作,操作分为两类.操作1: ...

  2. oracle的一些简单语法

    1.创建主键自增: --创建序列 create sequence seq_tb_user minvalue nomaxvalue start with increment by nocycle --一 ...

  3. CAS是什么

    CAS是什么? 比较并交换 例子1: public class ABADemo1 { public static void main(String[] args) { AtomicInteger at ...

  4. python3装饰器-进阶

    一.wraps 作用:优化装饰器 from functools import wraps # 导入wraps def wrapper(f): @wraps(f) # wraps的语法糖 def inn ...

  5. Scrapy的基本使用

    爬取:http://quotes.toscrape.com 单页面 # -*- coding: utf-8 -*- import scrapy class QuoteSpider(scrapy.Spi ...

  6. [bzoj2326] [洛谷P3216] [HNOI2011] 数学作业

    想法 最初的想法就是记录当前 \(%m\) 值为cur,到下一个数时 \(cur=cur \times 10^x + i\) n这么大,那就矩阵乘法呗. 矩阵乘法使用的要点就是有一个转移矩阵会不停的用 ...

  7. Error:Cannot build artifact 'XXX:war exploded' because it is included into a circular dependency (artifact 'XXXX:war exploded', artifact 'XXX:war exploded') Idea启动项目报错解决方案

    在Idea中使用Maven创建父子工程,第一个Model的那个项目可以很好的运行,在创建一个Model运行时报这个错.原因是tomcat部署了多个Web项目,可能最开始是两个项目的配置文件混用用,最后 ...

  8. [新详细]让Keil5续签到2032年的办法,不可商用

    # 使用方法和以前的版本一样,MDK 或者C51等均适用,供学习与参考.更多需要到这里购买→ → Keil官网:[http://www.keil.com/](http://www.keil.com/) ...

  9. git reset --hard HEAD^ 在cmd中执行报错

    报错: D:\git-root\test>git reset --hard HEAD^ More? More? fatal: ambiguous argument 'HEAD ': unknow ...

  10. SEO 搜索 形成一个关联