Java笔记——并发

作者: cynthia猫 | 来源:发表于2019-05-05 11:36 被阅读33次

本笔记基于这本书:Core Java 10th中文版, 11th英文版

1 什么是线程

小球的例子

这是10th的例子

首先看一个没有使用多线程的程序

image.png

这个程序采用不断移动位置的方式实现小球在窗体内跳动的动画效果。
点击start,就弹出球开始跳,start调用move方法进行跳动,会循环运行1000次move。

如果运行这个程序在完成1000次弹跳之前就想结束了,于是你点击close按钮,但是此时球还在跳,在球自己结束弹跳之前,无法与程序进行交互。

使用线程给其他任务提供机会

将移动球的代码放置在一个独立的线程中。AWT的事件分派线程将一直地并行运行,以处理用户界面的事件。
由于每个现成都有机会得以运行,所以在球弹跳期间,点击close时,事件调度线程将有机会关注到这个事件,并处理“关闭”这一动作。

银行的例子

11th则给了另外一个例子,可能是为了便于说后面的同步。
这个示例在银行账户之间转账。
线程1:从账户0转账到账户1
线程2:从账户2转账到账户3
具体不细说了,就是可以用很多个进程来做不同账户之间转账这件事情。

2 中断线程

当对一个线程调用interrupt方法时,线程的中断状态将被置位。
但是,如果线程被阻塞,就无法检测中断状态。这是产生InterruptedException异常的地方。
被中断的线程可以决定如何响应中断。某些线程是如此重要以至于应该处理完异常后,继续执行,而不理会中断。

3 线程状态

New
Runnable
Blocked
Waiting
Timed waiting
Terminated
要确定一个线程的当前状态,可调用getState方法。

4 线程属性

线程优先级

默认情况下,一个线程继承它的父线程的优先级。可以用setPriority方法提高或降低任何一个线程的优先级。

每当线程调度器有机会选择新线程时,它首先选择具有较高优先级的线程。但是,线程优先级是高度依赖于系统的。当虚拟机依赖于宿主机平台的线程实现机制时,Java线程的优先级被映射到宿主机平台的优先级上。

例如,Oracle为Linux提供的Java虚拟机中,线程的优先级被忽略——所有线程具有相同的优先级。

守护线程 (不知道为啥本书第11版删掉了这部分)

t.setDaemon(true);

将线程转换为守护线程。它的唯一用途是为其他线程提供服务。

5 同步

大多数实际的多线程应用中,两个或两个以上的线程需要共享对统一数据的存取。
如果两个线程存取相同的对象,可能产生讹误。这样的情况称为竞争条件(race condition)。

锁对象

Java引入了ReentrantLock类,用来保护代码块,基本结构如下:

myLock.lock();
try
{
critical section
}
finally
{
myLock.unlock();
}

条件对象

线程进入临界区,却发现在某一条件满足之后它才能执行。要使用一个条件对象来管理那些已经获得了一个锁但是却不能做有用工作的线程。

一个锁对象可以有一个或多个相关的条件对象。可以用newCondition方法获得一个条件对象。

API java.util.concurrent.locks.Lock

Condition newCondition()

API java.util.concurrent.locks.Condition

void await()
void signalAll()
void signal()

synchronized关键字

先总结一下有关锁和条件的关键点:

  • 锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
  • 锁可以管理试图进入被保护代码段的线程。
  • 锁可以拥有一个或多个相关的条件对象。
  • 每个条件对象管理那些已经进入被保护的代码段但还不能运行的线程。

Lock和Condition接口为程序设计人员提供了高度的锁定控制。Java 从1.0 版本开始,每一个对象都有一个内部锁。如果一个方法用synchronized关键字声明,那么对象的锁将保护整个方法。要调用该方法,线程必须获得内部的对象所。
内部对象所只有一个相关条件。
wait方法添加一个线程到等待集中,notifyAll / notify 方法接触等待线程的阻塞状态。

public synchronized void method()
{
  method body
}

等价于

public void method()
  try
  {
    method body
  }
  finally {this.intrinsicLock.unlock();}
}

同步阻塞

每一个Java对象有一个锁。县城可以通过调用同步方法获得锁。还有另一种机制可以获得锁,通过进入一个同步阻塞。

synchronized(obj)
{
critical section
}

当线程进入如上形式的阻塞,它获得obj的锁。

监视器概念

锁和条件是线程同步的强大工具。但是,它们并不是面向对象的。
因此研究人员给出了监视器这一解决方案,可以再不需要程序员考虑如何加锁的情况下,就可以保证多线程的安全性。
监视器有如下特性:

  • 监视器是只包含私有域的类。
  • 每个监视器类的对象有一个相关的锁。
  • 使用该锁对所有的方法进行加锁。
  • 该锁可以有任意多个相关条件。

Java以不是很精确的方式采用了监视器概念。Java中的每一个对象有一个内部的锁和内部的条件。如果一个方法用synchronized关键字声明,那么,它表现的就像是一个监视器方法。通过调用wait/notifyAll/notify来访问条件变量。

Volatile域

volatile关键字为实例域的同步访问提供了一种免锁机制。如果声明一个域为volatile,那么编译器和虚拟机就知道该域是可能被另一个线程并发更新的。

final变量

还有一种情况可以安全地访问一个共享域,即这个域声明为final时。

原子性

Java中有很多方法可以以原子方式设置和增减值。

死锁

Java编程语言中没有任何东西可以避免或打破死锁现象。必须仔细设计程序,以确保不会出现死锁。

线程局部变量

有时可能要避免在线程间共享变量,ThreadLocal付之类为各个线程提供各自的实例。

锁测试与超时

线程在调用Lock方法来获得另一个线程所持有的锁的时候,很可能发生阻塞。tryLock方法试图申请一个锁,在成功获得锁后返回true,否则,立即返回false,而且线程可以立即离开去做其他事情。

读/写锁

java.util.concurrent.locks包定义了两个锁类,ReentrantLock类和ReentrantReadWriteLock类。如果很多线程从一个数据结构读取数据而很少线程修改其中数据的话,后者是十分有用的。

6 线程安全的集合

如果多线程要并发地修改一个数据结构,那么很容易会破坏这个数据结构。
可以通过提供锁来保护共享数据结构,但是选择线程安全的实现作为替代可以更容易。

阻塞队列

对于许多线程问题,可以通过使用一个或多个队列以优雅且安全的方式将其形式化。生产者线程向队列插入元素,消费者线程则取出它们。使用队列,可以安全地从一个线程向另一个线程传递数据。

当试图向队列添加元素而队列已满,或是想从队列移出元素而队列为空的时候,阻塞队列导致线程阻塞。在协调多个线程之间的合作时,阻塞队列是一个有用的工具。

相关文章

网友评论

    本文标题:Java笔记——并发

    本文链接:https://www.haomeiwen.com/subject/vlscoqtx.html