`

第21章 并发

 
阅读更多
1.基本上所有的并非模式在解决线程冲突问题时,都是采用序列化访问共享资源的方法。这种加锁访问机制,常常称为“互斥量”。

2.共享资源一般是以对象形式存在的内存片段,但也可以是文件,输入输出端口,或者打印机等。

3.所有对象都自动含有单一的锁(也称为监视器)。当在对象上调用其任意synchronized方法时,此对象就会被加锁。此时,不管是其它线程不管是调用对象的该同步方法,还是调用其它的同步方法,必须等待该锁释放后才能调用。

4.在使用并发时,将域设置为private是重要的,否则synchronized不能防止其它任务直接访问域。

5.一个线程可以多次获得对象的锁。如果一个方法在同一个对象上调用了第二个方法,后者又调用了同一对象上的另一个方法,就会发生这种情况。JVM负责跟踪对象被加锁的次数。如果一个对象被解锁,其计数为0。在线程第一次给对象加锁的时候,计数变为1。每次线程在这个对象上获得了锁,计数都会增加。显然,只有首先获得了锁的线程才能允许继续获取多个锁。每当线程离开一个synchronized方法,计数减少,当计数为零的时候,锁被完全释放,此时别的线程就可以使用此资源。

6.针对每个类,也有一个锁(作为类的Class对象的一部分),所以synchronized static方法可以在类的范围内防止对静态数据的并发访问。

7.每个访问临界共享资源的方法都必须同步,否则它们就不会正确工作。

9.volatile关键字的用法
Volatile修饰的成员变量在每次被线程访问时,都强迫从共享内存中重读该成员变量的值。而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存。这样在任何时刻,两个不同的线程总是看到某个成员变量的同一个值。Java语言规范中指出:为了获得最佳速度,允许线程保存共享成员变量的私有拷贝,而且只当线程进入或者离开同步代码块时才与共享成员变量的原始值对比。这样当多个线程同时与某个对象交互时,就必须要注意到要让线程及时的得到共享成员变量的变化。

    而volatile关键字就是提示VM:对于这个成员变量不能保存它的私有拷贝,而应直接与共享成员变量交互。

    使用建议:在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。由于使用volatile屏蔽掉了VM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。


    个人总结: volatile只能保证可读性,不能保证原子性,即只要有一个线程修改它的时候就需要同步,再次提醒,第一选择应该用synchronized,这是最安全的方式,而其它任何方式都是由风险的。
public class TestThread {

private static volatile int  stopstr=0;

private static  void set(){
stopstr++;
stopstr++;
}

private static int get(){

return stopstr;
}

/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {

Thread th=new Thread(new Runnable() {

public void run() {

while(true){
set();
}

}
});

th.start();
TimeUnit.SECONDS.sleep(1);
while(true){
int i=get();
if(i%2!=0){
System.out.println(i);
break;
}
}
}
}

volatile一般最好不要用,影响性能;
volatile一般不能代替synchronised关键字,它只能保证可见性,不能保证互斥性。

不用volatile,也能保证可见性,例如:
public class TestThread {

public static void main(String[] args) throws InterruptedException {

A a=new A();
B b=new B(a);
C c=new C(a);
b.start();
c.start();
TimeUnit.SECONDS.sleep(5);
a.flag=true;

}
}


class A{
boolean flag=false;
}


class B extends Thread{
A a;
public B(A a){
this.a=a;
}
public void run(){
while(!a.flag){
System.out.println("B");
}
}
}


class C extends Thread{
A a;
public C(A a){
this.a=a;
}
public void run(){
while(!a.flag){
System.out.println("C");
}
}
}

从上面的运行结果可以看出,即使线程有副本,也能即使读到变量的更新。


10.原子性
    具有原子性的操作被称为原子操作。原子操作在操作完毕之前不会线程调度器中断。在Java中,对除了long和double之外的基本类型的简单操作都具有原子性。简单操作就是赋值或者return。比如”a = 1;”和 “return a;”这样的操作都具有原子性。但是在Java中,上面买碘片例子中的类似”a += b”这样的操作不具有原子性,

    所以如果add方法不是同步的就会出现难以预料的结果。在某些JVM中”a += b”可能要经过这样三个步骤:

   1. 取出a和b
   2. 计算a+b
   3. 将计算结果写入内存

    如果有两个线程t1,t2在进行这样的操作。t1在第二步做完之后还没来得及把数据写回内存就被线程调度器中断了,于是t2开始执行,t2执行完毕后t1又把没有完成的第三步做完。这个时候就出现了错误,相当于t2的计算结果被无视掉了。所以上面的买碘片例子在同步add方法之前,实际结果总是小于预期结果的,因为很多操作都被无视掉了。类似的,像”a++”这样的操作也都不具有原子性。所以在多线程的环境下一定要记得进行同步操作。
有一些并发大牛可以利用原子性避免同步而写出“免锁”的代码。Goetz开玩笑说:

    如果你能编写出一个牛逼的高性能的JVM,你就可以考虑考虑是否可以避免使用同步。

所以,在成为这样牛的大牛之前,还是老老实实使用同步吧。

10.原子类
没有使用原子类时:
public class AtomicityTest implements Runnable {
private volatile int i = 0;

public int getValue() {
return i;
}

private synchronized void evenIncrement() {
i++;
i++;
}

public void run() {
while (true)
evenIncrement();
}

public static void main(String[] args) {
ExecutorService exec = Executors.newCachedThreadPool();
AtomicityTest at = new AtomicityTest();
exec.execute(at);
while (true) {
int val = at.getValue();
if (val % 2 != 0) {
System.out.println(val);
System.exit(0);
}
}
}
}

使用原子类时:
public class AtomicIntegerTest implements Runnable {
private AtomicInteger i = new AtomicInteger(0);

public int getValue() {
return i.get();
}

private void evenIncrement() {
i.addAndGet(2);
}

public void run() {
while (true)
evenIncrement();
}

public static void main(String[] args) {
new Timer().schedule(new TimerTask() {
public void run() {
System.err.println("Aborting");
System.exit(0);
}
}, 5000); // Terminate after 5 seconds
ExecutorService exec = Executors.newCachedThreadPool();
AtomicIntegerTest ait = new AtomicIntegerTest();
exec.execute(ait);
while (true) {
int val = ait.getValue();
if (val % 2 != 0) {
System.out.println(val);
System.exit(0);
}
}
}
}

即对原子类操作,可以不用同步。对常规编程来说,它们很少会派上用场,但是涉及性能调优时,它们就大有用武之地了。

11. 在方法内部,用synchronized括起来的代码块,称为“临界区”,也被称为“同步控制块”。在进入临界区之前,必须获取括号中对象的锁。通过使用同步控制块,而不是对整个方法进行同步控制,可以使多个任务访问对象的时间性能得到大大提高。

运行例子后,性能排行:
同步控制块>lock锁>同步方法

12.synchronized必须给定一个在其上进行同步的对象,并且最合理的方式是,使用其方法正在被调用的当前对象(this)。

13.有时必须在另一个对象上同步,但是如果你要这么做,就必须确保所有相关的任务都是在同一个对象上同步的。如果按照下面的例子做是不对的:
class DualSynch {
private Object syncObject = new Object();

public synchronized void f() {
for (int i = 0; i < 5; i++) {
System.out.println("f()");
Thread.yield();
}
}

public void g() {
synchronized (syncObject) {
for (int i = 0; i < 5; i++) {
System.out.println("g()");
Thread.yield();
}
}
}
}

public class SyncObject {
public static void main(String[] args) {
final DualSynch ds = new DualSynch();
new Thread() {
public void run() {
ds.f();
}
}.start();
ds.g();
}
}


14.线程本地存储,
    ThreadLocal是什么呢?其实ThreadLocal并非是一个线程的本地实现版本,它并不是一个Thread,而是 threadlocalvariable(线程局部变量)。也许把它命名为ThreadLocalVar更加合适。线程局部变量 (ThreadLocal)其实的功用非常简单,就是为每一个使用该变量的线程都提供一个变量值的副本,是Java中一种较为特殊的线程绑定机制,是每一个线程都可以独立地改变自己的副本,而不会和其它线程的副本冲突。

    从线程的角度看,每个线程都保持一个对其线程局部变量副本的隐式引用,只要线程是活动的并且 ThreadLocal 实例是可访问的;在线程消失之后,其线程局部实例的所有副本都会被垃圾回收(除非存在对这些副本的其他引用)。

    通过ThreadLocal存取的数据,总是与当前线程相关,也就是说,JVM 为每个运行的线程,绑定了私有的本地实例存取空间,从而为多线程环境常出现的并发访问问题提供了一种隔离机制。

     ThreadLocal是如何做到为每一个线程维护变量的副本的呢?其实实现的思路很简单,在ThreadLocal类中有一个Map,用于存储每一个线程的变量的副本。

     概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。例子:

public class Student {
private int age = 0; // 年龄

public Student(int age){
this.age=age;
}

public int getAge() {
return this.age;
}

public void setAge(int age) {
this.age = age;
}
}


import java.util.Random;
import java.util.concurrent.TimeUnit;

public class ThreadLocalDemo implements Runnable {

public static void main(String[] agrs) {
System.out.println(Test6.s.getAge());//线程对共享资源操作前,打印一下共享资源
ThreadLocalDemo td = new ThreadLocalDemo();
Thread t1 = new Thread(td, "a");
Thread t2 = new Thread(td, "b");
t1.start();
t2.start();
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Test6.s.getAge());//线程对共享资源操作完毕后,再打印一下共享资源,发现前后是一样的没有任何改变
}

public void run() {
accessStudent();
}

/**
* 示例业务方法,用来测试
*/
public void accessStudent() {
// 获取当前线程的名字
String currentThreadName = Thread.currentThread().getName();
System.out.println(currentThreadName + " is running!");
// 产生一个随机数并打印
Random random = new Random();
int age = random.nextInt(100);
System.out
.println("thread " + currentThreadName + " set age to:" + age);
// 获取一个Student对象,并将随机数年龄插入到对象属性中
// Student student = getStudent();
Test6.value.get().setAge(age);
Thread.yield();
System.out.println("thread " + currentThreadName
+ " first read age is:" + Test6.value.get().getAge());
try {
Thread.sleep(500);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("thread " + currentThreadName
+ " second read age is:" + Test6.value.get().getAge());
}
}

class Test6{
public static Student s=new Student(88);
public static ThreadLocal<Student> value = new ThreadLocal<Student>(){
protected synchronized Student initialValue() {
//创建本地存储,一定要覆盖该方法,返回一个共享变量。因为每个线程在get的时候,总会调用该方法,返回共享变量作为本地存储
return new Student(s.getAge());
}
};


}
分享到:
评论

相关推荐

    Thinking in Java知识点总结

    Thinking in Java知识点总结,第21章 并发

    编程思想下篇

    由于上传文件大小限制该资源为上下篇 本资源为下篇 第1章 对象导论 1.1 抽象过程 1.2 每个对象都有一个接口 1.3 每个对象都提供服务 1.4 被隐藏的具体实现 1.5 复用具体实现 ...第21章 并发 第22章 图形化用户界面

    Shiro学习教程源代码

    包含以下内容的源码: 第二章 身份验证 第三章 授权 第四章 INI配置 ...第二十一章 授予身份及切换身份 第二十二章 集成验证码 第二十三章 多项目集中权限管理及分布式会话 第二十四章 在线会话管理

    Thinking in java4(中文高清版)-java的'圣经'

    类型信息 第15章 泛型 第16章 数组 第17章 容器深入研究 第18章 Java I/O系统 第19章 枚举类型 第20章 注解 第21章 并发 第22章 图形化用户界面 附录A 补充材料 可下载的补充材料 Thinking in C:Java的基础 Java...

    并发编程笔记20190526.docx

    第二章 线程的并发工具类 21 一、 Fork/Join框架的介绍 21 1、实现步骤: 22 2、工作窃取算法 22 3、分而治之 23 4、Fork/Join使用的标准范式 24 5、Fork/Join框架的异常处理 26 6、Fork/Join框架的实现原理 26 二、...

    Java并发编程实战

    第二部分 结构化并发应用程序 第6章 任务执行93 6.1 在线程中执行任务93 6.1.1 串行地执行任务94 6.1.2 显式地为任务创建线程94 6.1.3 无限制创建线程的不足95 6.2 Executor框架96 6.2.1 示例:基于Executor...

    最新Python深度之眼高级开发班 Python并发编程与高级开发技术实战课程 Python特优课程

    ├─第十一章类与对象.pdf ├─第十三章三大特性 (1).pdf ├─第十二章名称空间.pdf ├─第十五章异常处理.pdf ├─第十四章类的宿主.pdf (9)\04 第四阶段\第四阶段图文教程;目录中文件数:5个 ├─第二十章套接服务...

    JAVA核心知识点全集

    RabbitMQ、第十三章:Hbase、第十四章:MongoDB、第十五章:Cassandra、第十六章:设计模式、第十七章:负载均衡、第十八章:数据库、第十九章:一致性算法、第二十章:JAVA算法、第二十一章:数据结构、第二十二章...

    完整的数据库系统概论课件,新版,分为基础篇,设计应用开发篇,系统篇,中国人民大学信息学院

    数据库系统概论课件,新版,分为基础篇,设计应用开发篇,系统篇,中国人民大学...第十一章 并发控制 * 第十二章 数据库管理系统 第一至第十一章是本科专业的基本教程(书中有*号的部分除外) 第十二至第十七章是高级教程

    21天学会oracle

    21天学会oracle(电子教案) 第1章 Oracle安装配置.ppt 第2章 Oracle常用工具.ppt 第3章 SQL Plus和PL.ppt ...第21章 Oracle中的正则表达式.ppt 第22章 Oracle在Java开发中的.ppt 第23章 Oracle在C.ppt

    python详细学习教程.rar

    从零基础讲述Python如何使用,由浅入深,循序渐进。此文档仅用于学习,请误用于商用目的,具体章节内容如下: 第一章:编程基础 第二章:Python初探 第三章:变量类型和运算符 ...第二十一章:Python Scrapy网络爬虫

    并发数据结构与多核编程21-22秋季1

    第1节引言第2节 集合和链表第3节 并发推理第4节 细粒度锁第5节 乐观锁第6节 惰性链表第7节 无锁链表第8节 性能分析和比较第九章 并发队列和并发栈 3学时

    数据库系统概论 真题解析 讲义

    数据库系统概论 真题解析 讲义 目 录 第一章 绪论(1) 第二章 关系数据库(20) 第三章 关系数据库标准语言SQL (36) ...第十一章 并发控制(141) 第十二章 现代数据库(155)

    大型企业级分布式订单系统项目-课程大纲

    第一章 项目概述:授课思路、代码规范、云端部署 第二章 生单链路中的技术问题分析和代码落地 第三章 预支付中的技术问题分析和代码落地 第四章 支付成功后的履约...第二十一章 订单系统的流控体系和防雪崩体系设计实战

    Java程序设计(理论基础+实战案例)

    《Java程序设计》是面向计算机相关专业的一门专业基础课,涉及Java语言中面向对象编程、多线程处理、网络通信等内容,通过本课程的学习,学生能够了解 Java...第十一章 数据库编程 第十二章 并发编程 第十三章 项目案例

    Ice 分布式程序设计

    第 5 章 一个简单文件系统的 Slice 定义 第 6 章 客户端的 Slice-to-C++ 映射 第 7 章开发 C++ 文件系统客户 第 8 章 客户端的 Slice-to-Java 映射 第 9 章开发 Java 文件系统客户 第 10 章 服务器端的 Slice-to-C++...

    数据库系统概论课件&课堂练习

    广东工业大学计算机学院,顾国生老师的数据库课程课件,包括每章节的课堂练习。 教材:萨师煊,王珊:数据库系统概论(第四版) 高等教育出版社,2006.5 ...第十一章 并发控制 第十二章 数据库管理系统

    thinkinjava源码-Thinking-in-Java:ThinkingInJava源代码和练习题

    think in java 源码 Java编程思想(第四版) Thinking in Java (Forth Edition) 包含了很多书本中的源代码,因为从官网提供的...第21章 并发 第22章 图形化用户界面 水平有限,发现错误不适者,出门左拐找童主任。

    Java并发编程之美_部分21

    第 1 章并发编程线程基础在图1 -2 中 ,线程 A 己经持有了资源 2 ,它 同时还想申请资源线程 B 已经持有了资源l ,它 同时还想申请资源 2 , 所

Global site tag (gtag.js) - Google Analytics