juc-12

需要解决的问题

实例不一致 资源问题

提升性能

部分:Immutable 整体:Read-Write Lock模式

提高响应性

尽可能缩小临界区的范围,降低线程冲突的概率,这样就可以抑制性能的下降。

Thread-Per-Message

多线程的评价标准:四个指标

  • 安全性
  • 生存性
  • 可复用性
  • 性能

死锁

juc-11

线程的优先级

Java的优先级只在特定的Java平台运行环境起作用

Java的优先级只在特定的运行环境(JVM的实现和版本,以及操作系统)有效,Java规范中提到”优先级高的线程先执行”, 并没有写”优先”
的具体体系,因此没有太多的意义.

便是优先级的静态字段

Java线程的优先级是整数值(int)

  • Thread.MIN_PRIORITY: 最低
  • Thread.NORM_PRIORITY:默认
  • Thread.MAX_PRIORITY:最高

设置优先级的方法

setPriority方法用于设置优先级,是Thread类的实例方法.

获取优先级的方法

getPriority方法用于获取优先级,是Thread类的实例方法.

juc-10

关于java.lang.ThreadLocal类

java.lang.ThreadLocal就是储物间

java.lang.ThreadLocal与泛型

juc-09

java.util.concurrent包与线程同步

java.util.concurrent.CountDownLatch类

java.util.concurrent.CyclicBarrier类

juc-08

中断状态与InterruptedException异常的相互转换

中断状态->InterruptedException异常的转换

InterruptedException异常->中断状态的转换

InterruptedException异常->InterruptedException异常的转换

juc-07

理解InterruptedException异常

可能会花费时间,但可以取消

当习惯Java多线程设计之后,我们会留意方法后面是否加了throws InterruptedException. 如果方法加了,则表明该方法(
或该方法进一步调用的方法中)可能会抛出InterruptedException异常.

这里包含两层意思:

  • “花费时间”的方法
  • “可以取消”的方法 换言之,加了throws InterruptedException的方法可能会花费时间,当可以取消.

加了throws InterruptedException的方法

  • java.lang.Object类的wait方法
  • java.lang.Thread类的sleep方法
  • java.lang.Thread类的join方法

花费时间的方法

可以取消的方法

sleep方法和interrupt方法

wait方法和interrupt方法

join方法和interrupt方法

interrupt方法只是改变中断状态

isInterrupted方法: 检查中断状态

Thread.interrupted方法: 检查并清除中断状态

不可用使用Thread类的stop方法

juc-06

集合类与多线程

管理多个实例的接口或类统称为集合(collection)。

Java中的大部分集合都是非线程安全的。因此,在多线程操作集合的时候,需要去查看API文档, 确认要用的类或接口是否线程安全的。

例子1:非线程安全的java.util.ArrayList类

该例子中ArrayList(及迭代器)在被多个线程同时读写而失去安全性时,便会抛出ConcurrentModificationException异常。
该运行时(runtime)的异常用于表示“执行并发修改了”。

异常不过是调查Bug根本原因的提示而已,所以编写编程不能依赖于抛出的异常。

集合类与多线程-例子
例子

例子2: 利用Collections.synchronized方法所进行的同步

对例子1的改造,使得其具有安全性。

例子2

  • 通过Collections.synchronizedList方法同步ArrayList实例
  • 通过使用list同步后的读线程
    • “写”线程是显示调用add方法和remove方法,故可以不做调整。
1
2
3
4
5
synchronized(list){
for(int n : list){
System.out.println(n);
}
}

例子3: 使用写时复制(copy-on-write)的java.util.concurrent.CopyOnWriteArrayList类

例子2使用Collections.synchronized进行同步。
这里使用CopyOnWriteArrayList类通过copy-on-write避免读写冲突。

1
2
3
4
5
6
7
8
public class Main {
public static void main(String[] args) {
//这里使用Copy-on-write
final List<Integer> list = new CopyOnWriteArrayList<Integer>();
new WriterThread(list).start();
new ReaderThread(list).start();
}
}

程序如果频繁“写”操作,使用copy-on-write会比较耗时,如果写操作比较少,读操作比较多,是比较适合使用的。

juc-05

final

final类

表示该类无法扩展。也就是说,无法创建final类的子类。 由于无法创建final类的子类,所以final类中声明的方法也不会被重写。

final方法

表示该方法不会被子类的方法重写。 如果在静态方法的声明上加上final,则表示该方法不会被子类的方法隐藏(hide)。如果试图重写或隐藏final
方法,编译时会提示错误。

在设计模式的Template Method中,有时候模板方法会声明为final方法。

final字段

表示该字段只能赋值一次。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
final字段赋值有两种方式:
1.字段声明的时候赋值
class Something{
final int value = 123;
}

2.字段在构造方法中赋值
class Something{
final int value;
Something{
this.value = 123;
}
}

final静态字段赋值两种方法:
1.字段声明时赋值
class Something{
static final int value = 123;
}
2.静态代码块中赋值
class Something{
static final int value;
static {
value = 123;
}

注意: final字段不可用setValue这样的setter方法再次赋值.

final与创建线程安全的实例

从创建线程安全的角度来说,将字段声明为final是非常重要的。

final变量和final参数

局部变量和方法的参数也可用声明为final。final变量只可以赋值一次。 而final参数不可以赋值,因为在调用方法时,已经对其赋值了。

juc-04

java.util.concurrent包和计数信号量

计数信号量和Semaphore类

Single Threaded Execution模式用于确保某个区域“只能有一个线程”执行。下面我们将这种模式扩展。确保某个区域“最多只能由N个线程”
执行。这时就要用计数信号量来控制线程数量。

假设能够使用的资源个数为N个,而需要这些资源的线程个数又多于N个。此时就会导致竞态。

java.util.concurrent包中提供了表示信号量的Semaphore类。

资源的许可个数(permits)将通过Semaphore的构造函数来指定。

Semaphore的acquire方法用于确保存在可用资源。当存在可用资源的时候,线程会立即从acquire方法返回,同时信号量内部的资源
个数会减1。如果没有可用资源,线程则阻塞在acquire方法内,知道出现可用资源。

Semaphore的release方法用于释放资源。释放资源后,信号量内部的资源个数会增加1。另外,如果acquire中存在等待的线程,那么其中一个线程
会被唤醒,并从acquire方法返回。

使用Semaphore类的示例程序

使用例子

例子说明

juc-03

关于synchronized

synchronized语法&Before/After模式

1
2
3
synchronized void method(){}

synchronized (obj){}

其实可以看做是“{”处获得锁,“}”处释放锁。

假设,存在一个获得锁的lock方法,或释放锁的unlock方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
显示处理锁的方法:
void method(){
lock();
...
unlock();
}

如果在lock方法和unlock方法之间存在return,那么锁将无法被释放。
void method(){
lock();
if(条件表达式){
return; //这里执行了return,unlock()就不会被调用。
}
unlock();
}

void method(){
lock();
doMethod(); //如果该方法抛出异常,unlock()就不会被调用
unlock();
}

上述问题并不仅仅在于return语句,异常处理也存在这样的问题,调用的方法(或该方法调用的方法) 抛出异常时候,锁也就无法被释放。

避免异常导致的问题,必须使用finally语句

1
2
3
4
5
6
7
8
void method(){
lock();
try{
...
}finally{
unlock();
}
}

synchronized在保护什么

synchronized就像门上的锁。当你看到门上的锁时,我们还应该确认其他的门和窗户是不是都锁好了。
只要是访问多个线程共享的字段的方法,就需要使用synchronized进行保护。

该以什么单位来保护呢?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
public class Gate {
//已经通过的人数
private int counter = 0;
//最后一个通行者的“姓名”
private String name = "Nodody";
//最后一个通行者的出生地
private String address = "Nowhere";

/**
* 添加:synchronized
* 表示通过门
* @param myname
* @param myaddress
*/
public synchronized void pass(String myname, String myaddress) {
this.counter++;
this.name = name;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.address = address;
}
}

比如上面的代码,已经在pass方法上加了synchronized方法。 如果set和get方法都同时加上synchronized并不能保证安全
img.png

原子操作

synchronized方法只允许一个线程同时执行。如果某个线程正在执行synchronized方法,
其他线程就无法进入该方法。也就是说,从多线程的角度来看,这个synchronized方法执行
的操作是“不可分割的操作”。这种不可分割的操作通常称为“原子atomic”操作。

atom是物理学中的“原子”,本意为不可分割的物体。

long与double的操作不是原子的

Java中定义了一些原子的操作。 例如char,int等基本类型的赋值和引用都是原子的;引用类型的赋值和引用操作也是原子的。
因此,就算不使用synchronized也不会被分割。

例外:long和double的赋值和引用操作并不是原子的。 最简单的方法就是在synchronized方法中执行;或字段上加上volatile关键字。

总结:

  • 基本类型,引用类型的赋值和引用是原子操作
  • long和double的赋值和引用都是非原子操作
  • long或double在线程间共享时,需要将其放入synchronized中操作,或声明为volatile。