Appearance
大纲
- 线程基础概念
- Java 线程
- 生命周期
- 创建线程
- 线程的调用方法
- Synchronized 和 Lock
线程

1、基础概念
进程和线程
进程
在计算机i中,程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。
进程就是用来加载指令、管理内存、管理 IO 的。当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。
线程
一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给CPU执行,一个进程之内可以分为一到多个线程。
总结:
线程是程序执行的最小单位,它是进程的一部分,共享进程的资源。
进程是操作系统分配资源和调度的基本单位,每个进程至少包含一个线程。
区别:进程有独立的内存空间,而线程共享进程的内存空间;进程间通信(IPC)成本较高,上下文切换较慢,线程间通信和切换成本较低;线程是实现多任务的最小单位,进程是拥有资源的最小单位。

并行和并发
- 并行:两个及两个以上的作业在同一 时刻 执行。
- 并发:两个及两个以上的作业在同一 时间段 内执行。
最关键的点是:是否是 同时 执行
解释2:并行和并发有什么区别?
在单核CPU的情况下:
- 单核CPU下线程实际还是串行执行的
- 操作系统中有一个组件叫做任务调度器,将cpu的时间片(windows下时间片最小约为15毫秒)分给不同的程序使用,只是由于cpu在线程间(时间片很短)的切换非常快,人类感觉是同时运行的。
- 总结为一句话就是:微观串行,宏观并行
一般会将这种线程轮流使用 CPU的做法 称为并发(concurrent)
在多核 CPU 的情况下:
每个核(core)都可以调度运行线程,这时候线程可以是并行的。

总结
现在都是多核CPU,在多核CPU下
- 并发是同一时间应对多件事情的能力,多个线程轮流使用一个或多个CPU
- 并行是同一时间动手做多件事情的能力,4核CPU同时执行4个线程
同步和异步
同步和异步的区别
- 同步:发出一个调用之后,在没有得到结果之前, 该调用就不可以返回,一直等待。
- 异步:调用在发出之后,不用等待返回结果,该调用直接返回。
2、什么是Java 线程
在 JDK 1.2 及以后,Java 线程基于原生线程(Native Threads)实现, JVM 直接使用操作系统原生的内核级线程(内核线程)来实现 Java 线程,由操作系统内核进行线程的调度和管理。
用一句话概括 Java 线程和操作系统线程的关系:现在的 Java 线程的本质其实就是操作系统的线程。
在 Windows 和 Linux 等主流操作系统中,Java 线程采用的是一对一的线程模型,一个 Java 线程对应一个系统内核线程。
3、线程的生命周期及五种基本状态
五种基本状态:新建,就绪,阻塞,运行,死亡

关于Java中线程的生命周期,首先看一下下面这张较为经典的图:

上图中基本上囊括了Java中多线程各重要知识点。掌握了上图中的各知识点,Java中的多线程也就基本上掌握了。主要包括:
Java线程具有五中基本状态
- 新建状态(New)
- 当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
- 就绪状态(Runnable)
- 当调用线程对象的start()方法(t.start();),线程即进入就绪状态。
- 处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;
- 运行状态(Running)
- 当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。
- 注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中
- 阻塞状态(Blocked)
- 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才 有机会再次被CPU调用以进入到运行状态。
- 根据阻塞产生的原因不同,阻塞状态又可以分为三种
- 1.等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
- 2.同步阻塞 : 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
- 3.其他阻塞 : 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead)
- 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
Java多线程的就绪、运行和死亡状态
就绪状态转换为运行状态:当此线程得到处理器资源;
运行状态转换为就绪状态:当此线程主动调用yield()方法或在运行过程中失去处理器资源。
运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。
此处需要特别注意的是:当调用线程的yield()方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绪状态中的哪个线程具有一定的随机性,因此,可能会出现A线程调用了yield()方法后,接下来CPU仍然调度了A线程的情况。
在 Java 的 Thread 类中,有一个 State 的枚举
/**
* A thread state. A thread can be in one of the following states:
* <ul>
* <li>{@link #NEW}<br>
* A thread that has not yet started is in this state.
* </li>
* <li>{@link #RUNNABLE}<br>
* A thread executing in the Java virtual machine is in this state.
* </li>
* <li>{@link #BLOCKED}<br>
* A thread that is blocked waiting for a monitor lock
* is in this state.
* </li>
* <li>{@link #WAITING}<br>
* A thread that is waiting indefinitely for another thread to
* perform a particular action is in this state.
* </li>
* <li>{@link #TIMED_WAITING}<br>
* A thread that is waiting for another thread to perform an action
* for up to a specified waiting time is in this state.
* </li>
* <li>{@link #TERMINATED}<br>
* A thread that has exited is in this state.
* </li>
* </ul>
*
* <p>
* A thread can be in only one state at a given point in time.
* These states are virtual machine states which do not reflect
* any operating system thread states.
*
* @since 1.5
* @see #getState
*/
public enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {@link Object#wait() Object.wait}.
*/
BLOCKED,
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* <ul>
* <li>{@link Object#wait() Object.wait} with no timeout</li>
* <li>{@link #join() Thread.join} with no timeout</li>
* <li>{@link LockSupport#park() LockSupport.park}</li>
* </ul>
*
* <p>A thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called <tt>Object.wait()</tt>
* on an object is waiting for another thread to call
* <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
* that object. A thread that has called <tt>Thread.join()</tt>
* is waiting for a specified thread to terminate.
*/
WAITING,
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* <ul>
* <li>{@link #sleep Thread.sleep}</li>
* <li>{@link Object#wait(long) Object.wait} with timeout</li>
* <li>{@link #join(long) Thread.join} with timeout</li>
* <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
* <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
* </ul>
*/
TIMED_WAITING,
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;
}分析流程图

面试题:1.线程包括哪些状态
新建(NEW)、可运行(RUNNABLE)、阻塞(BLOCKED)、等待(WAITING)、时间等待(TIMED_WALTING)、终止(TERMINATED)
面试题:2.线程状态之间是如何变化的
- 创建线程对象是新建状态
- 调用了stat()方法转变为可执行状态
- 线程获取到了CPU的执行权,执行结束是终止状态
- 在可执行状态的过程中,如果没有获取 CPU 的执行权,可能会切换其他状态
- 如果没有获取锁(synchronized 或Iock)进入阻塞状态,获得锁再切换为可执行状态
- 如果线程调用了wait()方法进入等待状态,其他线程调用notify()唤醒后可切换为可执行状态
- 如果线程调用了sleep(50)方法,进入计时等待状态,到时间后可切换为可执行状态
4、创建线程
线程创建 🚩
在Java中,有三种常见的线程创建方式:使用Thread类、实现Runnable接口和实现Callable接口。
- Thread类:Thread类是 Java 提供的一个线程类,我们可以通过继承Thread类来创建线程。通过重写Thread类的run()方法来定义线程的执行逻辑。
java
class MyThread extends Thread {
@Override
public void run() {
// 线程的执行逻辑
}
}
// 创建线程
MyThread myThread = new MyThread();
myThread.start();- Runnable 接口:Runnable接口是一个函数式接口,我们可以通过实现Runnable接口来创建线程。需要注意的是,Runnable接口并不是一个线程类,而是一个任务,需要通过Thread类来创建线程并执行任务。
java
class MyRunnable implements Runnable {
@Override
public void run() {
// 线程的执行逻辑
}
}
// 创建线程
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();- Callable接口:Callable接口也是一个函数式接口,与Runnable接口类似,可以通过实现Callable接口来创建线程。不同的是,Callable接口的call()方法可以返回一个结果,并且可以抛出异常。
java
class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
// 线程的执行逻辑
return 42; // 返回一个结果
}
}
// 创建线程
MyCallable myCallable = new MyCallable();
ExecutorService executorService = Executors.newSingleThreadExecutor();
Future<Integer> future = executorService.submit(myCallable);- 使用Thread类创建线程是最直接的方式,但是由于Java不支持多继承,所以如果已经有一个父类,就不能再直接使用Thread类创建线程。
- 实现Runnable接口是一种更加灵活的方式,可以避免单继承的限制,还可以共享数据。
- Callable接口与Runnable接口类似,但可以返回一个结果,并且可以抛出异常。可以通过ExecutorService的submit()方法来执行Callable任务,并返回一个Future对象,可以通过该对象获取任务的结果。
补充:
Callable + FutureTask
Callable接口与Runnable接口类似,但它可以返回执行结果,并且可以抛出异常。使用Callable时通常配合FutureTask或线程池(ExecutorService)来使用。
示例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableFutureTaskExample {
public static void main(String[] args) {
// 创建Callable对象
Callable<Integer> callableTask = new Callable<Integer>() {
@Override
public Integer call() throws Exception {
System.out.println("开始计算...");
Thread.sleep(2000); // 模拟耗时计算过程
return 123; // 返回计算结果
}
};
// 将Callable与FutureTask关联
FutureTask<Integer> futureTask = new FutureTask<>(callableTask);
// 创建线程执行FutureTask
Thread thread = new Thread(futureTask);
thread.start();
// 执行其他任务...
System.out.println("执行其他任务...");
try {
// 获取计算结果,如果计算未完成则阻塞等待
Integer result = futureTask.get();
System.out.println("计算结果: " + result);
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
}
}FutureTask是Future接口的一个实现,它封装了Callable任务的执行。
通过将Callable实例传递给FutureTask的构造器, 然后,创建一个新的线程来执行FutureTask。
一般在项目中会使用线程池创建线程执行任务
使用线程池执行Runnable任务的示例:
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ThreadPoolExample {
public static void main(String[] args) {
// 创建固定大小的线程池
ExecutorService executor = Executors.newFixedThreadPool(2);
// 提交Runnable任务
executor.submit(new Runnable() {
@Override
public void run() {
System.out.println("Asynchronous task");
}
});
// 另一种提交Runnable任务的方式,使用Java 8的Lambda表达式
executor.submit(() -> {
System.out.println("Asynchronous task with lambda");
});
// 关闭线程池
executor.shutdown();
}
}在这个示例中,我们创建了一个固定大小为2的线程池,并提交了两个Runnable任务。使用线程池的好处是可以重用线程,减少线程创建和销毁的开销,以及可以控制并发线程的数量。
共有四种方式可以创建线程,分别是:
- 继承Thread类
- 实现 runnable 接口
- 实现 Callable 接口
- 线程池创建线程(项目中使用方式)
面试题: runnable和callable有什么区别?
参考回答:
- 1.Runnable接口 run 方法没有返回值
- 2.Callable接口 call 方法有返回值,是个泛型,和Future、FutureTaski配合可以用来获取异步执行的结果
- 3.Callable接口的 call() 方法允许抛出异常;而Runnable接口的runO方法的异常只能在内部消化,不能继续上抛
面试题:线程的run() 和start() 有什么区别?
- start():用来启动线程,通过该线程调用run方法执行run方法中所定义的逻辑代码。start方法只能被调用一次。
- run():封装了要被线程执行的代码,可以被调用多次。
写法简化
写法简化(Java 8)
- 方式一:
java
Thread thread = new Thread(){
@Override
public void run() {
System.out.println("thread run ...");
}
};
thread.start();简化后:
java
Thread thread = new Thread(() -> System.out.println("thread run ..."));
thread.start();- 方式二:
java
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("runnable run ...");
}
});
thread.start();简化后:
java
Thread thread = new Thread(() -> System.out.println("runnable run ..."));
thread.start();- 方式三:
java
Callable<Integer> callable = new Callable() {
@Override
public Object call() throws Exception {
System.out.println("callable run ...");
return 521;
}
};
FutureTask futureTask = new FutureTask(callable);
Thread thread = new Thread(futureTask);
thread.start();简化后:
java
Thread thread = new Thread(new FutureTask(() -> {
System.out.println("callable run ...");
return 521;
}));
thread.start();常见面试题
顺序执行
常见的一道面试题:新建T1、T2、T3三个线程,如何保证它们按顺序执行?
要保证三个线程T1、T2、T3按顺序执行,即先执行T1,T1执行完毕后执行T2,T2执行完毕后执行T3,可以使用多种方法来实现。这里提供三种常见的方法:
方法1:使用join()方法
join()方法可以使当前线程等待另一个线程完成后再继续执行。
public class ThreadOrder {
public static void main(String[] args) {
Thread t1 = new Thread(() -> System.out.println("T1 is running"));
Thread t2 = new Thread(() -> System.out.println("T2 is running"));
Thread t3 = new Thread(() -> System.out.println("T3 is running"));
try {
t1.start();
t1.join(); // 等待t1执行完毕
t2.start();
t2.join(); // 等待t2执行完毕
t3.start();
t3.join(); // 等待t3执行完毕
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}方法2:使用wait()和notify()/notifyAll()方法
通过对象监视器的wait()和notify()方法来控制线程的执行顺序。这种方法相对复杂,需要确保wait()和notify()调用在同步块中。
public class ThreadOrder {
public static void main(String[] args) {
final Object lock = new Object();
Thread t1 = new Thread(() -> {
synchronized (lock) {
System.out.println("T1 is running");
lock.notify(); // 唤醒等待lock对象的一个线程
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 使当前线程等待直到lock对象被唤醒
System.out.println("T2 is running");
lock.notify(); // 唤醒等待lock对象的一个线程
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t3 = new Thread(() -> {
synchronized (lock) {
try {
lock.wait(); // 使当前线程等待直到lock对象被唤醒
System.out.println("T3 is running");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
try {
t2.start();
t3.start();
Thread.sleep(100); // 确保t2和t3启动并进入wait状态
t1.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}方法3:使用CountDownLatch
CountDownLatch是一个同步辅助类,用于延迟线程的进度直到其达到终止状态。
import java.util.concurrent.CountDownLatch;
public class ThreadOrder {
public static void main(String[] args) throws InterruptedException {
CountDownLatch latch1 = new CountDownLatch(1);
CountDownLatch latch2 = new CountDownLatch(1);
Thread t1 = new Thread(() -> {
System.out.println("T1 is running");
latch1.countDown(); // 减少计数
});
Thread t2 = new Thread(() -> {
try {
latch1.await(); // 等待latch1计数到达0
System.out.println("T2 is running");
latch2.countDown(); // 减少计数
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t3 = new Thread(() -> {
try {
latch2.await(); // 等待latch2计数到达0
System.out.println("T3 is running");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
t1.start();
t2.start();
t3.start();
}
}这三种方法各有特点,可以根据具体场景选择最适合的一种来保证线程按顺序执行。
面试题:新建T1、T2、T3三个线程,如何保证它们按顺序执行?
可以使用线程中的 join 方法解决

停止线程
如何停止一个正在运行的线程?
有三种方式可以停止线程
- 使用退出标志,使线程正常退出,也就是当 run 方法完成后线程终止
- 使用stop方法强行终止(不推荐,方法已作废)
- 使用interrupt方法中断线程
- 打断阻塞的线程(sleep,wait,join) 的线程,线程会抛出 InterruptedException 异常
- 打断正常的线程,可以根据打断状态来标记是否退出线程
5、线程基本方法
注意:标黄色的方法代表是
static 方法,可直接类名调用,无需创建对象。
| 名称 | 描述 | 注意事项 |
|---|---|---|
start() | 启动一个新线程, 在新的线程运行 run 方法 | start 方法只是让线程进入就绪,里面代码不一定立刻 运行(CPU 的时间片还没分给它)。每个线程对象的 start方法只能调用一次,如果调用了多次会出现 IllegalThreadStateException |
run() | 新线程启动后会调用的方法 | 如果在构造 Thread 对象时传递了 Runnable 参数,则 线程启动后会调用 Runnable 中的 run 方法,否则默 认不执行任何操作。但可以创建 Thread 的子类对象, 来覆盖默认行为 |
join() | 等待线程运行结束 | |
join(long n) | 等待线程运行结束, 最多等待 n 毫秒 | |
getId() | 获取线程长整型的 id | id 唯一 |
getName() | 获取线程名 | |
setName(String name) | 修改线程名 | |
getPriority() | 获取线程优先级 | |
setPriority(int priority) | 修改线程优先级 | Java 中规定线程优先级是1~10 的整数,较大的优先级 能提高该线程被 CPU 调度的机率 |
getState() | 获取线程状态 | Java 中线程状态是用 6 个 enum 表示,分别为: NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED |
interrupt() | 打断线程 | 如果被打断线程正在 sleep,wait,join 会导致被 打断的线程抛出 InterruptedException,并清除 打断标记;如果打断正在运行的线程,则会设置 打断标记;park 的线程被打断,也会设置打断标记 |
| 判断当前线程是否被打断 | 会清除打断标记 | |
isInterrupted() | 判断当前线程是否被打断 | 不会清除打断标记 |
isAlive() | 判断当前线程是否存活 | |
isDaemon() | 判断当前线程是否是守护线程 | |
setDaemon(boolean on) | 设置当前线程为守护线程 | |
| 获取当前正在执行的线程 | ||
| 让当前执行的线程休眠n毫秒, 休眠时让出 CPU 的时间片 给其它线程 | ||
| 提示线程调度器让出当前线程 对 CPU 的使用 | 主要是为了测试和调试,它的具体的实现依赖于 操作系统的任务调度器 | |
常见面试题
面试题:notify()和notifyAll()有什么区别?
- notifyAll:唤醒所有wait的线程
- notify:只随机唤醒一个wait线程
wait 和 sleep
面试题:在java中wait和sleep方法的不同?
- 共同点
- wait(),wait(long)和sleep(long)的效果都是让当前线程暂时放弃CPU的使用权,进入阻塞状态
- 不同点
- 1.方法归属不同
- sleep(long)是Thread的静态方法
- 而wait(),wait(long)都是Object的成员方法,每个对象都有
- 2、醒来时机不同
- 执行sleep(long)和wait(long)的线程都会在等待相应毫秒后醒来
- wait(long)和wait0还可以被notify唤醒,wait()如果不唤醒就一直等下去
- 它们都可以被打断唤醒
- 3.锁特性不同(重点)
- wait方法的调用必须先获取wait对象的锁,而sleep则无此限制
- wait方法执行后会释放对象锁,允许其它线程获得该对象锁(我放弃cpu,但你们还可以用)
- 而sleep如果在synchronized代码块中执行,并不会释放对象锁(我放弃cpu,你们也用不了)
- 1.方法归属不同
class WaitSleepExample {
private static final Object LOCK = new Object();
public static void main(String[] args) {
// 创建一个线程执行等待操作
Thread waitThread = new Thread(() -> {
synchronized (LOCK) { // 必须在同步块内调用wait,这表示必须持有对象的锁
try {
System.out.println("Wait Thread: Holding lock, now wait");
LOCK.wait(); // 调用wait方法后,当前线程会释放LOCK对象上的锁,并进入等待状态
System.out.println("Wait Thread: Exited wait");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 创建一个线程执行睡眠操作
Thread sleepThread = new Thread(() -> {
synchronized (LOCK) { // 进入同步块,持有LOCK对象的锁
try {
System.out.println("Sleep Thread: Holding lock, now sleep");
Thread.sleep(1000); // 调用sleep方法,但不会释放LOCK对象上的锁
System.out.println("Sleep Thread: Exited sleep");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 创建一个线程执行通知操作
Thread notifyThread = new Thread(() -> {
synchronized (LOCK) { // 进入同步块,持有LOCK对象的锁
// 发送通知,告诉等待在LOCK对象上的线程可以继续执行
LOCK.notifyAll();
System.out.println("Notify Thread: Sent notification");
}
});
waitThread.start(); // 启动等待线程
try {
Thread.sleep(500); // 确保waitThread先执行
} catch (InterruptedException e) {
e.printStackTrace();
}
sleepThread.start(); // 启动睡眠线程
try {
Thread.sleep(500); // 确保sleepThread有机会执行
} catch (InterruptedException e) {
e.printStackTrace();
}
notifyThread.start(); // 启动通知线程
}
}- 关于锁的不同处理:
wait()方法:调用wait()时,线程必须持有对象的锁(这是通过在synchronized块内调用wait()来保证的)。一旦wait()被调用,线程会释放这个对象上的锁,允许其他线程获取这个锁。当其他线程在这个对象上调用notify()或notifyAll()时,等待的线程才有机会重新获取锁并继续执行。sleep()方法:与wait()不同,调用sleep()时线程不会释放任何持有的锁。即使是在synchronized块内调用sleep(),当前线程仍然会保持对锁的持有,直到sleep()完成。这意味着,如果一个线程在持有某个对象锁的同时调用了sleep(),其他想要访问这个同步块的线程会被阻塞,直到睡眠线程醒来并退出同步块,释放锁。
→ 使用 sleep 的时候同步块代码线程会被阻塞。
6、Synchronized 和 Lock 的使用
并发编程中,锁是经常需要用到的。这里讲述一下 Synchronized 和 Lock 的使用。
Synchronized 是 Java 并发编程 中很重要的关键字,另外一个很重要的是 volatile。
Syncronized 的目的是一次只允许一个线程进入由他修饰的代码段,从而允许他们进行自我保护。
Lock 是 Java并发编程中很重要的一个接口,它要比 Synchronized 关键字更能直译"锁"的概念,Lock需要手动加锁和手动解锁,一般通过 lock.lock() 方法来进行加锁, 通过 lock.unlock() 方法进行解锁。与 Lock 关联密切的锁有 ReetrantLock 和 ReadWriteLock。
Synchronized
在方法上使用 Synchronized
方法声明时使用,放在范围操作符之后,返回类型声明之前。即一次只能有一个线程进入该方法,其他线程要想在此时调用该方法,只能排队等候。
java
private int number;
public synchronized void numIncrease(){
number++;
}在某个代码段使用 Synchronized
可以在某个代码块上使用 Synchronized 关键字,表示只能有一个线程进入某个代码段。
java
public void numDecrease(Object num){
synchronized (num){
number++;
}
}使用 Synchronized 锁住整个对象
synchronized后面括号里是一对象,此时线程获得的是对象锁。
java
public void test() {
synchronized (this) {
// ...
}
}Lock
Lock 是 Java并发编程中很重要的一个接口,相关方法如下:
java
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}常用方法
- lock()
- 用来获取锁。如果锁被其他线程获取,则进行等待。
- 如果采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁
- tryLock()
- 方法是有返回值的,它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,
- 这个方法无论如何都会立即返回。在拿不到锁时不会一直等待。
- tryLock(long time, TimeUnit unit)
- 和tryLock()方法是类似
- 在拿不到锁时会等待一定的时间,在时间期限之内如果还拿不到锁,就返回false。如果如果一开始拿到锁或者在等待期间内拿到了锁,则返回true。
- lockInterruptibly()
- 去获取锁时,如果线程正在等待获取锁,则这个线程能够响应中断,即中断线程的等待状态。
- 当两个线程同时通过 lock.lockInterruptibly() 想获取某个锁时,假若此时线程A获取到了锁,而线程B只有在等待,那么对线程B调用 threadB.interrupt() 方法能够中断线程B的等待过程。
- 由于 lockInterruptibly() 的声明中抛出了异常,所以 lock.lockInterruptibly() 必须放在try块中或者在调用lockInterruptibly() 的方法外声明抛出 InterruptedException。
代码示例:
lock()
java
Lock lock = ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}tryLock()
javascript
Lock lock = ...;
if(lock.tryLock()) {
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}
}else {
//如果不能获取锁,则直接做其他事情
}lockInterruptibly()
javascript
public void method() throws InterruptedException {
lock.lockInterruptibly();
try {
//.....
}
finally {
lock.unlock();
}
}一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
注意,当一个线程获取了锁之后,是不会被interrupt()方法中断的。 单独调用 interrupt() 方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()方法获取某个锁时,如果不能获取到,只有进行等待的情况下,是可以响应中断的。 而用synchronized修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
参考: