`

第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());
}
};


}
分享到:
评论

相关推荐

    第十一章 并发控制.pdf

    本文将深入探讨并发控制的概念,以及其在数据库系统中的具体实施策略,通过分析《第十一章 并发控制》的内容,我们将了解到事务执行的不同方式、并发控制机制、事务并发执行可能引发的问题及其解决方案。 ### 并发...

    数据库系统概论---第十一章并发控制.ppt

    数据库系统概论中,第十一章重点探讨了并发控制这一关键概念。并发控制是为了确保在多用户环境下,多个事务同时访问数据库时不会破坏数据的一致性和完整性。在数据库系统中,尤其是在多用户共享的环境中,如飞机定票...

    VC开发经验技巧 共分为21章 第二十一章

    第二十一章可能是整个系列的总结或聚焦于特定主题,但由于具体章节内容未给出,我们将基于一般性的VC开发经验进行详细的讲解。 1. **环境配置与项目设置**:良好的开发环境是高效编程的基础。VC支持多种编译器选项...

    第二十六章-并发_介绍1

    在本章的介绍中,我们将探讨一个简化的并发模拟器,该模拟器主要用于教学目的,能够模拟多线程环境下的x86汇编代码执行。这个模拟器并不包括完整的操作系统代码,而是专注于展示用户代码的并发执行。 **预备知识** ...

    C++并发编程实践 C++ Concurrency in Action

    第二章介绍了如何管理线程。线程是进程内的一个执行单元,可以独立于其他线程执行。C++中的线程可以通过`std::thread`类来创建和管理。 - **创建线程**:使用`std::thread`构造函数创建新线程,传入一个函数对象...

    Java并发编程的艺术

    第二章深入探讨了Java并发编程的底层实现原理。这一章不仅涵盖了CPU层面的知识,还涉及到了JVM内部的工作机制。例如,作者们详细解释了Java虚拟机是如何管理和调度线程的,以及这些线程如何与其他操作系统进程协调...

    Java编程思想笔记(全)

    第二十一章探讨了Java中的并发编程。并发是指多个线程同时执行的能力。本章介绍了Thread类、Runnable接口以及如何使用synchronized关键字来实现同步。此外,还会介绍高级并发工具类如ExecutorService、Semaphore等的...

    编程思想下篇

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

    Python中文指南,共十三章

    Python中文指南,共十三章,分别为第一章:安装运行、第二章:数据类型、第三章:数据结构、第四章:控制流程、第五章:学习函数、第六章:...并发编程、第十一章:代码美化、第十二章:虚拟环境、第十三章:绝佳工具

    Shiro学习教程源代码

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

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

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

    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并发编程的艺术1

    最后一章,即第十一章,通过实际案例展示了并发编程的应用场景,提供了问题排查和调试的技巧,帮助读者将理论知识应用于实际工作中。 本书适合有一定Java基础和开发经验的读者,无论是初学者还是有经验的开发者,都...

    JAVA高质量并发详解,多线程并发深入讲解

    - **第21章:FutureTask的使用** 描述`FutureTask`的用途,包括如何将`Callable`和`Runnable`转换为`FutureTask`,以及如何以`Runnable`的方式运行`FutureTask`。 - **第22章:CompletableFuture的应用** 讨论`...

    数据库系统概念第6版实践练习和习题的13-16章的答案(其他章见我的其他资源)

    本章可能涉及到关系规范化理论,如第一范式(1NF)、第二范式(2NF)和第三范式(3NF),以及BCNF(博科斯范式)。此外,还有可能讨论到如何通过ER图转换为关系模式,以及反规范化在特定场景下的应用。 第14章可能...

    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

Global site tag (gtag.js) - Google Analytics