1.1. JAVA并发

1.1.1. 线程

线程与进程

  • 线程:轻量级、共享变量
  • 进程:拥有自己的一套变量

创建线程

创建线程有三种方法,其中 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的。

  • 实现 Runnable 接口
  • 实现 Callable 接口
  • 继承 Thread 类

需要实现 run() 方法。

A. 实现 Runnable 接口

需要实现 run() 方法,通过 Thread 调用 start() 方法来启动线程。

public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread thread = new Thread(instance);
    thread.start();
}
B. 实现 Callable 接口

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread thread = new Thread(ft);
    thread.start();
    System.out.println(ft.get());
}

中断线程

使用interrupt方法可以请求中断线程,当调用interrupt方法时,线程的中断状态会被置位。每一个线程都有boolean标志,每个线程都会不时地检查这个标志,以判断线程是否被中断。

线程状态

六种状态分别为NEW(新创建)、Runnable(可运行)、Blocked(被阻塞)、Waiting(等待)、Timed waiting(计时等待)、Terminated(被终止)。

  • 新创建线程:处于新创建阶段,程序还没有开始运行线程中的代码,还有一些基础工作要做。
  • 可运行状态:一旦调用了start方法,线程处于runnable状态,一个可运行的线程可能处于运行也可能处于没有运行状态,这取决于操作系统给线程提供的运行时间。
  • 被阻塞状态:当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有,则该线程进入阻塞状态。
  • 等待状态:当线程等待另一个线程通知调度器一个条件时,则该线程属于等待状态。例如调用Object.wait或Thread.join方法或等待util.concurrent中Lock或Condition时。
  • 计时等待:有几个超时参数,调用它们会导致线程进入计时等待,这一状态会一直保持到超时期满或者接收到合适的通知。Thread.sleep、Object.wait、Thread.join、Lock.tryLock、Condition.await
  • 被终止状态:线程因如下两个原因被终止。
    • 因为run方法正常退出而自然死亡
    • 因为一个没有捕获的异常而导致终止run方法而意外死亡。

线程属性

线程优先级、守护线程、线程组以及处理未捕获线程异常的处理器。

  • 线程优先级:每当线程调度器选择新线程时,首先会选择优先级高的线程。
    • setPriority
  • 守护线程:为其他线程提供服务,例如计时线程,为其他线程提供信号。当只剩下守护线程时,虚拟机就会退出。
    • setDaemon
  • 未捕获异常处理器:当线程受到非受查异常时线程终止,异常被传递到未捕获异常处理器上。

1.1.2. 线程同步

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

锁对象

有两种机制防止代码块受到并发访问的干扰,第一种是synchronized关键字,另一种是ReentrantLock类。

//ReentrantLock保护代码块
myLock.lock();
try{
    critical section
}finally{
    myLock.unlock();
}

这保证了任何时刻只有一个线程进入临界区。

Lock与ReentrantLock区别

  • Lock
  • ReentrantLock:公平锁,偏爱等待时间最长的线程。

条件对象

当线程进入临界区,却发现在某一条件满足之后它才能执行,这时使用一个条件对象来管理那些已经获得了一个锁但却不能做有用工作的线程。使用条件变量的await方法进入等待集。

需要注意的:当锁可用时,不能马上解除阻塞;而是等待另一个线程调用同一条件下的signalAll方法。如果没有其他线程激活等待的线程,它就永远不再运行了。这将造成死锁现象。

  • Condition newCondition()返回一个与锁相关的条件对象
  • void await() 将线程放到条件等待集中
  • void signalAll() 解除该条件的等待集中所有线程的阻塞状态
  • void signal() 从该条件等待集合中随机选择一个线程,解除其阻塞状态。

synchronized关键字

简化了锁的代码实现。

public synchronized void methd{...}
//等效于
public void method{
    this.intrinsiclock.lock();
    try{
        ...
    }finally{
        this.intrinsiclock.unlock();
    }
}

wait和notifyAll等效于await和signalAll

Volatile域

为实例域提供了一种免锁机制。

finnal变量

1.1.3. 可见性、原子性与有序性

在32位的机器上对long型变量进行加减操作存在并发隐患,到底是不是这样呢?

long类型64位,所以在32位的机器上,对long类型的数据操作通常需要多条指令组合出来,无法保证原子性,所以并发的时候会出问题。

results matching ""

    No results matching ""