第2章 Java并行程序基础(二)
2.3 volatile 与 Java 内存模型(JMM)
- volatile对于保证操作的原子性是由非常大的帮助的(可见性)。但是需要注意的是,volatile并不能代替锁,它也无法保证一些复合操作的原子性。比如下面的例子,通过volatile是无法保证i++的原子性操 作的:
static volatile int i = 0;
public static class PlusTask implements Runable {
@Override
public void run() {
for (int k = 0; k < 10000; k++) {
i++;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread[] threads = new Thread[10];
for (int i = 0; i < 10; i++) {
threads[i] = new Thread(new PlusTask());
threads[i].start();
}
for (int i = 0; i < 10; i++) {
threads[i].join(); //等待所有线程结束
}
System.out.println(i);
}
- 上述代码的输出总是会小于100000,由于i++不是原子性的。
- 此外,volatile也能保证数据的可见性和有序性。下面再来看一个简单的例子:
public class NoVisibility {
private static boolean ready;
private static int number;
private static class ReaderThread extends Thread {
public void run() {
while (!ready);
System.out.println(number);
}
}
public static void main(String[] args) throws InterruptedException {
new ReaderThread().start();
Thread.sleep(1000);
number = 42;
ready = true;
Thread.sleep(1000);
}
}
- 在虚拟机的Client模式下,由于JIT并没有做足够的优化,在主线程修改ready变量的状态后,ReaderThread可以发现这个改动,并退出程序。但是在Server模式下,由于系统优化的结果,ReaderThread线程无法“看到”主线程中的修改,导致ReaderThread永远无法退出。
- 注意:可以使用Java虚拟机参数-server切换到Server模式。
2.4 分门别类的管理:线程组
- 在一个系统中,如果线程数量很多,而且功能分配比较明确,就可以将相同功能的线程放置在一个线程组里。
- 线程组的使用非常简单,如下:
public class ThreadGroupName implements Runnable {
public static void main(String[] args) {
ThreadGroup tg = new ThreadGroup("PrintGroup");
//使用Thread的构造函数,指定线程所属的线程组,将线程和线程组关联起来。
Thread t1 = new Thread(tg, new ThreadGroupName(), "T1");
Thread t2 = new Thread(tg, new ThreadGroupName(), "T2");
t1.start();
t2.start();
//获得活动线程的总数,估计值。
System.out.println(tg.activeCount());
//打印这个线程组中所有的线程信息。
tg.list();
}
@Override
public void run() {
String groupAndName = Thread.currentThread().getThreadGroup().getName() + "-" + Thread.currentThread().getName();
while (true) {
System.out.println("I am " + groupAndName);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStaceTrace();
}
}
}
}
2.5 驻守后台:守护线程(Daemon)
- 守护线程是一种特殊的线程,它是系统的守护者,在后台默默地完成一些系统性的服务,比如垃圾回收线程、JIT线程就可以理解为守护线程。
- 与之对应的是用户线程,用户线程可以认为是系统的工作线程,它会完成这个程序应该要完成的业务操作。
- 当一个java应用内,只有守护线程时,Java虚拟机就会自然退出。
- 下面简单地看一下守护线程的使用:
public class DaemonDemo {
public static class DaemonT extends Thread {
public void run() {
while (true) {
System.out.println("I am alive");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread t = new DaemonT();
t.setDaemon(true); //将线程t设置为守护线程。必须在start()之前设置。
t.start();
Thread.sleep(2000);
}
}
- 由于t被设置为守护线程,系统中只有主线程main为用户线程,因此在main线程休眠2秒后退出时,整个程序也随之结束。但如果不把线程t设置为守护线程,main线程结束后,t线程还会不停地打印,永远不会结束。
2.6 先干重要的事:线程优先级
- 在Java中,使用1到10表示线程优先级。一般可以使用内置的三个静态标量表示:
public final static int MIN_PRIORITY = 1;
public final static int MIN_PRIORITY = 5;
public final static int MIN_PRIORITY = 10;
- 数字越大则优先级越高,但有效范围在1到10之间。
- 下面的代码展示了优先级的作用。高优先级的线程倾向于更快地完成。
public class PriorityDemo {
public static class HightPriority extends Thread {
static int count = 0;
public void run() {
while (true) {
synchronized (PriorityDemo.class) {
count++;
if (count > 10000000) {
System.out.println("HightPriority is complete");
break;
}
}
}
}
}
public static class LowPriority extends Thread {
static int count = 0;
public void run() {
while (true) {
synchronized (PriorityDemo.class) {
count++;
if (count > 10000000) {
System.out.println("LowPriority is complete");
break;
}
}
}
}
}
public static void main(String[] args) {
Thread high = new HightPriority();
LowPriority low = new new LowPriority();
high.setPriority(Thread.MAX_PRIORITY);
low.setPriority(Thread.MIN_PRIORITY);
low.start();
high.start();
}
}
- 在对count累加前,我们使用synchronized产生了一次资源竞争。目的是使得优先级的差异表现得更为明显。
2.7 线程安全的概念与synchronized
- 关键字synchronized的作用是实现线程间的同步。它的工作是对同步的代码加锁,使得每一次,只能有一个线程进入同步块,从而保证线程间的安全性。
- 关键字synchronized可以有多种用法。
- 指定加锁对象:对给定对象加锁,进入同步代码前要获得给定对象的锁。
- 直接作用于实例方法:相当于对当前实例加锁,进入同步代码前要获得当前实例的锁。
- 直接作用于静态方法:相当于对当前类加锁,进入同步代码前要获得当前类的锁。
- 下述代码,将synchronized作用于一个给定对象instance,因此,每次当线程进入被synchronized包裹的代码段,就都会要求请求instance实例的锁。如果当前有其他线程正持有这把锁,那么新到的线程就必须等待。这样,就保证了每次只能有一个线程执行i++操 作。
public class AccountingSync implements Runnable {
static AccountingSync instance = new AccountingSync();
static int i = 0;
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
synchronized(instance) {
i++;
}
}
}
}
- 当然,上述代码也可以写成如下形式,两者是等价的:
public class AccountingSync2 implements Runnable {
static AccountingSync2 instance = new AccountingSync2();
static int i = 0;
public synchronized void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
increase();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(instance);
Thread t2 = new Thread(instance);
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
- 上述代码中,synchronized关键字作用于一个实例方法。这就是说在进入increase()方法前,线程必须获得当前对象实例的锁。在本例中就是instance对象。这里使用Runnable接口创建两个线程,并且这两个线程都指向同一个Runnable接口实例(instance)这样才能保证两个线程在工作时,能够关注到同一对象锁上去,从而保证线程安全。
- 一种错误的同步方式如下:
public class AccountingSyncBad implements Runnable {
static int i = 0;
public synchronized void increase() {
i++;
}
@Override
public void run() {
for (int j = 0; j < 10000000; j++) {
increase();
}
}
public static void main(String[] args) {
Thread t1 = new Thread(new AccountingSyncBad());
Thread t2 = new Thread(new AccountingSyncBad());
t1.start();t2.start();
t1.join();t2.join();
System.out.println(i);
}
}
- 上述代码中,这两个线程的Runnable实例并不是同一个对象。使用的是两把不同的锁。因此,线程安全是无法保证的。
- 但是我们只要简单地修改上述代码,就能使其正确执行。那就是使用synchronized的第三种用法,将其作用于静态方法。
public static synchronized void increase() {
i++;
}
- 这样即使两个线程指向不同的Runable对象,但由于方法块需要请求的是当前类的锁,而非当前实例,因此,线程间还是可以正确同步。
- 除了用于线程同步、确保线程安全外,synchronized还可以保证线程间的可见性和有序性。从可见性的角度上讲,synchronized可以完全替代volatile的功能,只是使用上没有那么方便。就有序性而言,由于synchronized限制每次只有一个线程可以访问同步块,因此,无论同步块内的代码如何被乱序执行,只要保证串行语义一致,那么执行结果总是一样的。而其他访问线程,又必须在获得锁后方能进入代码块读取数据,因此,它们看到的最终结果并不取决于代码的执行过程,从而有序性问题自然得到了解决。
第2章 Java并行程序基础(二)的更多相关文章
- JAVA并行程序基础二
JAVA并行程序基础二 线程组 当一个系统中,如果线程较多并且功能分配比较明确,可以将相同功能的线程放入同一个线程组里. activeCount()可获得活动线程的总数,由于线程是动态的只能获取一个估 ...
- 第2章 Java并行程序基础(三)
2.8 程序中的幽灵:隐蔽的错误 2.8.1 无提示的错误案例 以求两个整数的平均值为例.请看下面代码: int v1 = 1073741827; int v2 = 1431655768; Syste ...
- 第2章 Java并行程序基础(一)
2.1 有关线程你必须知道的事 进程是系统进行资源分配和调度的基本单位,是程序的基本执行实体. 线程就是轻量级进程,是程序执行的最小单位. 线程的生命周期,如图2.3所示. 线程的所有状态都在Thre ...
- Java并发程序设计(二)Java并行程序基础
Java并行程序基础 一.线程的生命周期 其中blocked和waiting的区别: 作者:赵老师链接:https://www.zhihu.com/question/27654579/answer/1 ...
- JAVA并行程序基础
JAVA并行程序基础 一.有关线程你必须知道的事 进程与线程 在等待面向线程设计的计算机结构中,进程是线程的容器.我们都知道,程序是对于指令.数据及其组织形式的描述,而进程是程序的实体. 线程是轻量级 ...
- JAVA并行程序基础一
JAVA并行程序基础一 线程的状态 初始线程:线程的基本操作 1. 新建线程 新建线程只需要使用new关键字创建一个线程对象,并且用start() ,线程start()之后会执行run()方法 不要直 ...
- Java并行程序基础。
并发,就是用多个执行器(线程)来完成一个任务(大任务)来处理业务(提高效率)的方法.而在这个过程中,会涉及到一些问题,所以学的就是解决这些问题的方法. 线程的基本操作: 1.创建线程:只需要new一个 ...
- 到头来还是逃不开Java - Java13程序基础
java程序基础 没有特殊说明,我的所有学习笔记都是从廖老师那里摘抄过来的,侵删 引言 兜兜转转到了大四,学过了C,C++,C#,Java,Python,学一门丢一门,到了最后还是要把Java捡起来. ...
- Spring MVC + Spring + Mybitis开发Java Web程序基础
Spring MVC + Spring + Mybitis是除了SSH外的另外一种常见的web框架组合. Java web开发和普通的Java应用程序开发是不太一样的,下面是一个Java web开发在 ...
随机推荐
- Java手写数组栈
public class ArrayStack{ private String[] items; //数组 private int count; //栈内元素 private int n; //栈大小 ...
- 动态规划最短路径LintcodeNO110
动态规划最短路径LintcodeNO110 简单的dp题,没啥好说的... class Solution { public: /** * @param grid: a list of lists of ...
- 【原】C#两个类互相包含类对象可以吗?
在阅读大牛代码的时候发现了这个问题,之所以标题以问号开头,是因为我还没搞懂这个问题.权当记录,以希望以后搞懂. 部分图片转贴吧:http://tieba.baidu.com/p/6015910029 ...
- HTTP,来一个详细的学习。
HTTP 认识 HTTP 首先你听的最多的应该就是 HTTP 是一种 超文本传输协议(Hypertext Transfer Protocol),这你一定能说出来,但是这样还不够,假如你是大厂面试官,这 ...
- 根据输入参数,判定时间范围CheckTimeSpan
对于C#的开发的网页程式,一些企业或者工厂可能会运用这些程式去查询一些资料,考虑到查询的资料太多,假如一个月的资料就有上万条数据,在对于查询资料的SQL语句后时间栏位运用Between.....AND ...
- redis订阅发布简单实现
适用场景 业务流程遇到大量异步操作,并且业务不是很复杂 业务的健壮型要求不高 对即时场景要求不高 原理介绍 redis官网文档:https://redis.io/topics/notification ...
- HTML中使用Vue+Dhtmlxgantt制作任务进度图
HTML中使用Vue+Dhtmlxgantt制作任务进度图 Dhtmlxgantt官网: https://dhtmlx.com/docs/products/dhtmlxGantt/ 参考文章 甘特图配 ...
- dp - 求符合题意的序列的个数
The sequence of integers a1,a2,…,ak is called a good array if a1=k−1 and a1>0. For example, the s ...
- 换装WIN10(windows)那点儿事,换装操作系统一文通,玩转安装操作系统
目录 1. 按 2. win10对电脑配置的要求 3. 原版镜像下载 4. 制作U盘系统盘 5. 硬盘分区调整 6. 设置开机时从U盘启动 7. 安装win10 8. 如何激活WIN10 9. 如何给 ...
- git工作中总结
# .克隆到本地 git clone url git clone -b 分支 url # 注意:克隆完成后,要删除.git隐藏文件夹 # .修改代码 # .生成master git init git ...