本文为Java并发编程实战阅读笔记,基于此有一些个人的延伸。读者可以去阅读该书获取详细的知识点。
线程
优点
-
发挥多处理器的强大性能
-
实现简单的建模
专注于一种任务的执行。通过使用线程,可以将复杂并且异步的工作流进一步分解为一组简单并且同步的工作流,每个工作流在一个单独的线程中运行,并在特定的同步位置进行交互。
-
简化异步事件
风险
-
安全性问题
线程的安全问题一般是多线程执行造成的并发问题。其中一个常见并发问题称为竞态条件,指的是在多线程环境下,由于线程的执行顺序不确定,导致多个线程访问共享资源时产生不可预测的结果。
-
活跃性问题
当线程的某操作无法执行下去,就会发生活跃性问题。例如,如果线程A在等待线程B释放其持有的资源,而线程B永远都不释放该资源,那么A就会永久地等待下去。死锁便是常见的活跃性问题。
-
性能问题
性能问题包括多个方面,例如服务时间过长,响应不灵敏,吞吐率过低,资源消耗过高,或者可伸缩性较低等。例如在多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁地出现上下文切换操作(Context Switch),这种操作将带来极大的开销。
线程和进程的区别
- 进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位
- 包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。
- 同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的
如何创建线程
- 继承Thread类
public class Main {
public static void main(String[] args) {
//使用继承Thread的方式创建线程
MyThread myThread = new MyThread();
myThread.start();
}
}
class MyThread extends Thread {
@Override
public void run() {
// 步骤2: 重写run方法,定义线程要执行的任务
System.out.println("线程 " + this.getId() + " 正在执行。");
}
}
- 实现Runnable接口
public class Main {
public static void main(String[] args) {
//使用实现Runnable的方式创建线程
MyRunnable myRunnable = new MyRunnable();
Thread thread = new Thread(myRunnable);
thread.start();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("MyRunnable");
}
}
- 实现Callable接口
public class Main {
public static void main(String[] args) {
//使用实现Callable的方式创建线程
MyCallable myCallable = new MyCallable();
FutureTask<String> futureTask = new FutureTask<>(myCallable);
Thread thread = new Thread(futureTask);
thread.start();
}
}
class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.println("MyCallable.call()");
return "任务执行完毕";
}
}
Runnable和Callable的区别
- Callable重写的方法是call,Runnable重写的方法是run
- Callable 任务执行有返回值
- Callable可以抛出异常
- 运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检査计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。