1、 共享变量

要使多个线程在一个程序中有用,它们必须有某种方法可以互相通信或共享它们的结果。

让线程共享其结果的最简单方法是使用共享变量。它们还应该使用同步来确保值从一个线程正确传播到另一个线程,以及防止当一个线程正在更新一些相关数据项时,另一个线程看到不一致的中间结果。

线程基础中计算素数的示例使用了一个共享布尔变量,用于表示指定的时间段已经过去了。这说明了在线程间共享数据最简单的形式是:轮询共享变量以查看另一个线程是否已经完成执行某项任务。

2、存在于同一个内存空间中的所有线程

正如前面讨论过的,线程与进程有许多共同点,不同的是线程与同一进程中的其它线程共享相同的进程上下文,包括内存。这非常便利,但也有重大责任。只要访问共享变量(静态或实例字段),线程就可以方便地互相交换数据,但线程还必须确保它们以受控的方式访问共享变量,以免它们互相干扰对方的更改。

任何线程可以访问所有其作用域内的变量,就象主线程可以访问该变量一样。素数示例使用了一个公用实例字段,叫做 finished,用于表示已经过了指定的时间。当计时器过期时,一个线程会写这个字段;另一个线程会定期读取这个字段,以检查它是否应该停止。注:这个字段被声明成 volatile,这对于这个程序的正确运行非常重要。在本章的后面,我们将看到原因。

3、受控访问的同步

为了确保可以在线程之间以受控方式共享数据,Java 语言提供了两个关键字:synchronized 和 volatile。

Synchronized 有两个重要含义:它确保了一次只有一个线程可以执行代码的受保护部分(互斥,mutual exclusion 或者说 mutex),而且它确保了一个线程更改的数据对于其它线程是可见的(更改的可见性)。

如果没有同步,数据很容易就处于不一致状态。例如,如果一个线程正在更新两个相关值(比如,粒子的位置和速率),而另一个线程正在读取这两个值,有可能在第一个线程只写了一个值,还没有写另一个值的时候,调度第二个线程运行,这样它就会看到一个旧值和一个新值。同步让我们可以定义必须原子地运行的代码块,这样对于其他线程而言,它们要么都执行,要么都不执行。

同步的原子执行或互斥方面类似于其它操作环境中的临界段的概念。

4、确保共享数据更改的可见性

同步可以让我们确保线程看到一致的内存视图。

处理器可以使用高速缓存加速对内存的访问(或者编译器可以将值存储到寄存器中以便进行更快的访问)。在一些多处理器体系结构上,如果在一个处理器的高速缓存中修改了内存位置,没有必要让其它处理器看到这一修改,直到刷新了写入器的高速缓存并且使读取器的高速缓存无效。

这表示在这样的系统上,对于同一变量,在两个不同处理器上执行的两个线程可能会看到两个不同的值!这听起来很吓人,但它却很常见。它只是表示在访问其它线程使用或修改的数据时,必须遵循某些规则。

Volatile 比同步更简单,只适合于控制对基本变量(整数、布尔变量等)的单个实例的访问。当一个变量被声明成 volatile,任何对该变量的写操作都会绕过高速缓存,直接写入主内存,而任何对该变量的读取也都绕过高速缓存,直接取自主内存。这表示所有线程在任何时候看到的 volatile 变量值都相同。

如果没有正确的同步,线程可能会看到旧的变量值,或者引起其它形式的数据损坏。

5、用锁保护的原子代码块

Volatile 对于确保每个线程看到最新的变量值非常有用,但有时我们需要保护比较大的代码片段,如涉及更新多个变量的片段。

同步使用监控器(monitor)或锁的概念,以协调对特定代码块的访问。

每个 Java 对象都有一个相关的锁。同一时间只能有一个线程持有 Java 锁。当线程进入 synchronized 代码块时,线程会阻塞并等待,直到锁可用,当它可用时,就会获得这个锁,然后执行代码块。当控制退出受保护的代码块时,即到达了代码块末尾或者抛出了没有在 synchronized 块中捕获的异常时,它就会释放该锁。

这样,每次只有一个线程可以执行受给定监控器保护的代码块。从其它线程的角度看,该代码块可以看作是原子的,它要么全部执行,要么根本不执行。

6、简单的同步示例

使用 synchronized 块可以让您将一组相关更新作为一个集合来执行,而不必担心其它线程中断或看到计算的中间结果。以下示例代码将打印“1 0”或“0 1”。如果没有同步,它还会打印“1 1”(或“0 0”,随便您信不信)。

public class SyncExample {

private static lockObject = new Object();

private static class Thread1 extends Thread {

public void run() {

synchronized (lockObject) {

x = y = 0;

System.out.println(x);

}

}

}

private static class Thread2 extends Thread {

public void run() {

synchronized (lockObject) {

x = y = 1;

System.out.println(y);

}

}

}

public static void main(String[] args) {

new Thread1().run();

new Thread2().run();

}

}

在这两个线程中都必须使用同步,以便使这个程序正确工作。

7、Java 锁定

Java 锁定合并了一种互斥形式。每次只有一个线程可以持有锁。锁用于保护代码块或整个方法,必须记住是锁的身份保护了代码块,而不是代码块本身,这一点很重要。一个锁可以保护许多代码块或方法。

反之,仅仅因为代码块由锁保护并不表示两个线程不能同时执行该代码块。它只表示如果两个线程正在等待相同的锁,则它们不能同时执行该代码。

在以下示例中,两个线程可以同时不受限制地执行 setLastAccess() 中的 synchronized 块,因为每个线程有一个不同的 thingie 值。因此,synchronized 代码块受到两个正在执行的线程中不同锁的保护。

public class SyncExample {

public static class Thingie {

private Date lastAccess;

public synchronized void setLastAccess(Date date) {

this.lastAccess = date;

}

}

public static class MyThread extends Thread {

private Thingie thingie;

public MyThread(Thingie thingie) {

this.thingie = thingie;

}

public void run() {

thingie.setLastAccess(new Date());

}

}

public static void main() {

Thingie thingie1 = new Thingie(),

thingie2 = new Thingie();

new MyThread(thingie1).start();

new MyThread(thingie2).start();

}

}

8、同步的方法

创建 synchronized 块的最简单方法是将方法声明成 synchronized。这表示在进入方法主体之前,调用者必须获得锁:

public class Point {

public synchronized void setXY(int x, int y) {

this.x = x;

this.y = y;

}

}

对于普通的 synchronized方法,这个锁是一个对象,将针对它调用方法。对于静态 synchronized 方法,这个锁是与 Class 对象相关的监控器,在该对象中声明了方法。

仅仅因为 setXY() 被声明成 synchronized 并不表示两个不同的线程不能同时执行 setXY(),只要它们调用不同的 Point 实例的 setXY() 就可同时执行。对于一个 Point 实例,一次只能有一个线程执行 setXY(),或 Point 的任何其它 synchronized 方法。

9、同步的块

synchronized 块的语法比 synchronized 方法稍微复杂一点,因为还需要显式地指定锁要保护哪个块。Point 的以下版本等价于前一页中显示的版本:

public class Point {

public void setXY(int x, int y) {

synchronized (this) {

this.x = x;

this.y = y;

}

}

}

使用 this 引用作为锁很常见,但这并不是必需的。这表示该代码块将与这个类中的 synchronized 方法使用同一个锁。

由于同步防止了多个线程同时执行一个代码块,因此性能上就有问题,即使是在单处理器系统上。最好在尽可能最小的需要保护的代码块上使用同步。

访问局部(基于堆栈的)变量从来不需要受到保护,因为它们只能被自己所属的线程访问。

10、大多数类并没有同步

因为同步会带来小小的性能损失,大多数通用类,如 java.util 中的 Collection 类,不在内部使用同步。这表示在没有附加同步的情况下,不能在多个线程中使用诸如 HashMap 这样的类。

java 访问线程_java线程简介(共享对数据的访问)相关推荐

  1. java中我爱你_Java线程学习(转)

    编写具有多线程能力的程序经常会用到的方法有: run(),start(),wait(),notify(),notifyAll(),sleep(),yield(),join() 还有一个重要的关键字:s ...

  2. java+向前进一_Java 线程基础

    前言 线程并发系列文章: 熟练掌握线程原理与使用是程序员进阶的必经之路,网上很多关于Java线程的知识,比如多线程之间变量的可见性.操作的原子性,进而扩展出的Volatile.锁(CAS/Synchr ...

  3. 抽象类java启动线程_java 线程复习笔记

    1. 线程基本概念 1.1 线程与进程 当一个程序进入内存运行时,即变成一个进程.进程是系统进行资源分配和调度的一个独立单元. 线程是进程的组成部分,一个进程可以拥有多个线程,一个线程必须有一个父进程 ...

  4. 简述java的线程_JAVA线程简述

    一.先谈谈并行与并发: 并行:是指两个或多个事件在同一时刻发生. 并发:是指连个或多个事件在同一时间间隔内发生. 二.接下来我们进入正题,讨论一下线程和进程: 1)什么是线程和进程? 进程:是一个具有 ...

  5. java多线程示例_Java线程示例

    java多线程示例 Welcome to the Java Thread Example. Process and Thread are two basic units of execution. C ...

  6. java多线程抽奖_java 线程池、多线程并发实战(生产者消费者模型 1 vs 10) 附案例源码...

    导读 前二天写了一篇<Java 多线程并发编程>点我直达,放国庆,在家闲着没事,继续写剩下的东西,开干! 线程池 为什么要使用线程池 例如web服务器.数据库服务器.文件服务器或邮件服务器 ...

  7. java 优化线程_JAVA线程优化

    Java并发编程:线程池的使用 在前面的文章中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题: 如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了, ...

  8. java怎么看具体被挂起的线程_Java线程的挂起、恢复和终止

    有时,线程的挂起是很有用的.例如,一个独立的线程可以用来显示当日的时间.如果用户不希望用时钟,线程被挂起.在任何情形下,挂起线程是很简单的,一旦挂起,重新启动线程也是一件简单的事. 挂起,终止和恢复线 ...

  9. java runnable 异常_JAVA 线程中的异常捕获

    在java多线程程序中,所有线程都不允许抛出未捕获的checked exception(比如sleep时的InterruptedException),也就是说各个线程需要自己把自己的checked e ...

最新文章

  1. pytorch2——Pytorch基础数据结构——张量(深度之眼)
  2. 1.5w字,30图带你彻底掌握 AQS!
  3. android应用程序的混淆打包
  4. Linux 文本界面转到图形界面
  5. 【BFS】【并查集】【Tarjan】【LCA】Gym - 101173H - Hangar Hurdles
  6. 再谈变分自编码器VAE:从贝叶斯观点出发
  7. fileitem方法_FileItem的常用方法
  8. 厉害了!谷歌新发布的半监督学习算法降低4倍错误率
  9. redis数据结构存储Linked List设计细节(redis的设计与实现笔记)
  10. 生产者消费者实现用c语言用物品1,物品2等来代替,用C语言编写程序:生产者和消费者之间实现同步与互斥问题...
  11. Linux-lsof
  12. 使用Origin绘制折线图(入门)
  13. lpop 原子_这个知识孩子掌握了吗?初中化学原子的结构知识梳理
  14. 使用CrossApp实现版本更新管理(iOS端给AppStore链接,android端下载apk并替换)
  15. matlab帧差法测速,matlab帧差法物体检测
  16. matlab中函数迭代法,Matlab 数值计算----斯特芬森加速迭代法
  17. (含代码)基于51最小系统的流水灯+焊接制作
  18. strom 在linux下部署、基本命令
  19. tesseract的使用
  20. 2017-本命年里发生的那些事

热门文章

  1. 对反向传播算法(Back-Propagation)的推导与一点理解
  2. 在ZC702上运行Linux(4)-编译和使用U-Boot Linux
  3. Opencv多通道分离函数split()和多通道合并函数merge的使用
  4. 洛谷P4114 Qtree1(树链剖分+线段树)
  5. Win10的UWP之标题栏的返回键(一)
  6. 数据库和MySQL相关面试题目
  7. 局部变量,静态局部变量,全局变量,静态全局变量在内存中的存放区别(转)...
  8. StackExchange.Redis通用封装类分享(转)
  9. web前端性能意义、关注重点、测试方案、优化技巧
  10. java+jtextfield+取值_[求助]JTextfield 取值问题!