​​


1. 线程概述



1.1 线程和进程

  • 进程是处于运行过程中的程序,并且具有一定的独立功能

  • 并发性​:同一个时刻只能有一条指令执行,但多个进程指令被快速轮换执行

  • 并行​:多条指令在多个处理器上同时执行

  • 线程是进程的执行单元


1.2 多线程的优势

  • 进程之间不能共享内存,但线程之间非常容易

  • 系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程效率更高

  • Java语言内置了多线程功能


2. 线程创建与启动



2.1 继承Thread



public class FirstThread extends Thread {

private int i;

@Override
public void run() {
for(i = 0; i < 50; i ++){
System.out.println(this.getName() + "" + i);
}
}

public static void main(String[] args){
FirstThread ft = new FirstThread();
for(int i =0; i < 100;i ++){
System.out.println(Thread.currentThread().getName() + "" + i);
if(i == 20) {
ft.run();
}
}
}
}



2.2 实现Runnable接口



public class FirstThread implements java.lang.Runnable {

private int i;

public void run() {
for(i = 0; i < 50; i ++){
System.out.println(Thread.currentThread().getName()+ "" + i);
}
}

public static void main(String[] args){
FirstThread ft = new FirstThread();
for(int i =0; i < 100;i ++){
System.out.println(Thread.currentThread().getName() + "" + i);
if(i == 20) {
ft.run();
}
}
}
}


2.3 使用Callable和Future

  • ​Callable​​​接口提供了一个​​call()​​​方法可以作为线程执行体,​​call()​​方法有返回值且可以声明抛出异常

  • Java5提供了​​Future​​​接口来代表​​Callable​​​接口里​​call()​​​方法的返回值,并为​​Future​​​接口提供了一个​​FutureTask​​实现类

  • ​Future​​接口定义的方法:

方法名

作用

​boolean cancel(boolean mayInterruptIfRunning)​

试图取消该​​Future​​里关联的​​Callable​​任务

​V get()​

返回​​Callable​​任务里​​call​​方法的返回值,该方法会造成线程阻塞,等子线程执行完才能获得

​V get(long timeout, TimeUnit unit)​

返回​​Callable​​任务里​​call​​方法的返回值。该方法让程序最多阻塞​​timeout​​和​​unit​​指定的时间,如果经过指定时间​​Callable​​任务还没有返回值则抛出​​TimeoutException​​异常

​boolean isCancelled()​

​Callable​​中的任务是否取消

​boolean isDone()​

​Callable​​中的任务是否完成

public class CallableDemo {

public static void main(String[] args){
FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> {
int i = 0;
for( ; i < 100; i++){
System.out.println(i);
}
return i;
});
new Thread(task).start();
try {
System.out.println(task.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}



2.4 创建线程的三种方式对比

​Runnable​​​和​​Callable​​优劣势:

  • 线程类只是实现了​​Runnable​​​、​​Callable​​接口,还可以继承其他类

  • Runnable和Callable情况下,多个线程可以共享同一个​​target​​对象,所以非常适合多个相同线程来处理同一份资源的情况

  • 编程稍稍复杂,如果需要访问当前线程,则必须使用​​Thread.currentThread()​

​Thread​​优劣势:

  • 线程类已经继承了​​Thread​​类,所以不能再继承其他父类

  • 编写简单,如果需要访问当前线程,用​​this​​使用


3. 线程生命周期



3.1 新建和就绪状态

  • ​new​​语句仅仅由Java虚拟机为其分配内存,并没有表现出任何线程的动态特征

  • 如果直接调用继承类的​​run​​​方法,则只会有​​MainActivity​​​,而且不能通过​​getName​​​获得当前执行线程的名字,而需用​​Thread.currentThread().getName()​

  • 调用了​​run​​方法后,该线程已经不再处于新建状态


3.2 运行和阻塞状态

  • 当线程数大于处理器数时,存在多个线程在同一个CPU上轮换的现象

  • 协作式调度策略​:只有当一个线程调用了​​sleep()​​​或​​yield()​​方法才会放弃所占用的资源——即必须线程主动放弃所占用的资源

  • 抢占式调度策略​:系统给每个可执行的线程分配一个小的时间段来处理任务,当任务完成后,系统会剥夺该线程所占用的资源

  • 被阻塞的线程会在合适的时候重新进入就绪状态

Java多线程介绍_java

线程状态换图



3.3 死亡状态

  • 测试线程死亡可用​​isAlive()​

  • 处于死亡的线程无法再次运行,否则引发​​IllegalThreadStateException​​异常


4. 控制线程



4.1 join线程

  • 在​​MainActivity​​​调用了​​A.join()​​​,则​​MainActivity​​​被阻塞,A线程执行完后​​MainActivity​​才执行


4.2 后台线程(Daemon Thread)

  • 如果所有的前台线程都死亡,后台线程会自动死亡


public class DaemonThread extends Thread {
@Override
public void run() {
for(int i = 0; i< 1000; i++){
System.out.println("DaemonActivity" + i);
}
}

public static void main(String[] args){
DaemonThread thread = new DaemonThread();
thread.setDaemon(true);
thread.start();
for(int i = 0; i < 10; i ++ ){
System.out.println("MainActivity" + i);
}
}
}

Java多线程介绍_方法名_02

运行结果



4.3 线程睡眠sleep


try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
  • ​sleep​​​方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程优先级;但​​yield​​方法只会给优先级相同或更高的线程

  • ​sleep​​​方法将入阻塞状态,直到经过阻塞时间才会转入就绪;​​yield​​强制当前线程转入就绪状态

  • ​sleep​​​方法抛出了​​InterruptedException​​,yield方法没抛出异常


4.4 改变线程优先级

  • 优先级高的线程获得较多的执行机会,优先级低的线程获得较少的执行机会

  • ​setPriority​​​和​​getPriority​​方法来设置和返回指定线程的优先级


5. 线程同步

  • ​run()​​方法不具有同步安全性

  • java​引入了同步监视器来解决多线程同步问题,​​sychronized(obj)​​​中​​obj​​就是共享资源


5.1 同步方法

  • 同步方法就是使用​​synchronized​​来修饰某个方法

  • 实例方法的同步监视器默认是​​this​

  • Java​中不可变类总是线程安全的,可变类对象需要额外的方法来保证其线程安全


public class DaemonThread extends Thread {

static int balance = 100;
int drawAmount;
String name;

public DaemonThread(int drawAmount, String name){
this.drawAmount = drawAmount;
this.name = name;
}

@Override
public void run() {
this.draw(drawAmount);
}

public synchronized void draw(int amount){
if(balance >= amount){
System.out.println(this.name + "取出了" + amount);
try{
Thread.sleep(1);
} catch (InterruptedException e){
e.printStackTrace();
}
balance -= amount;
System.out.println("\t余额为" + balance);

} else{
System.out.println(this.name + "取现失败");
}
}
public static void main(String[] args){
new DaemonThread(50, "A").start();
new DaemonThread(100, "B").start();
}
}



5.2 释放同步监视器的锁定

下列情况下,线程会释放对同步监视器的锁定

  • 当前线程的同步方法、同步代码块执行结束

  • 遇到了​break​、​return

  • 遇到异常

  • 程序执行了同步监视器对象的​​wait()​​方法

下列情况下不会释放:

  • 执行同步方法时,程序调用​​Thread.sleep()​​​ ​​Thread.yield()​​方法

  • 其他线程调用了该线程的​​suspend​​方法


5.3 同步锁

  • Java5​开始,提供了一种功能更强大的同步锁机制,可以通过显式定义同步锁对象来实现同步

  • Lock提供了比synchronized更广泛的锁定操作,并且支持多个相关的Condition对象

  • Lock类型:
    LockReadWriteLockReentrantLock​:常用,可以对一个加锁的对象重新加锁
    ReentrantReadWriteLockStampedLock

方法名

作用

​lock​

加锁

​unlock​

解锁


5.4 死锁

A等B,B等A



5.5 线程通信



5.5.1 传统的线程通信

方法名

作用

​wait​

导致当前线程等待,直到其他线程调用该同步监视器的​​notify()​​或​​notifyAll()​​方法

​notify​

唤醒在此同步监视器等待的单个线程

​notifyAll​

唤醒在此同步监视器等待的所有线程

  • ​wait()​​必须在加锁的情况下执行


5.5.2 使用Condition

  • 如果系统中不适用synchronized来保证线程同步,而使用Lock对象来保证同步,那么无法使用​​wait​​​,​​notify​​​,​​notifyAll()​​来进行线程通信

  • 当使用​​Lock​​对象,​Java​提供​​Condition​​保证线程协调

  • ​Condition​​方法如下

方法名

作用

​await​

导致当前线程等待,直到其他线程调用该同步监视器的​​signal()​​或​​signalAll()​​方法

​signal​

唤醒在此Lock对象的单个线程

​signalAll​

唤醒在此Lock对象的所有线程


5.5.3 使用阻塞队列

  • Java​提供了一个BlockingQueue接口

  • 当生产者线程试图向​​BlockingQueue​​​放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从​​BlockingQueue​​取出元素时,如果该队列已空,则该线程被阻塞

方法名

作用

​put(E e)​

尝试把E元素放入​​BlockingQueue​

​take()​

尝试从​​BlockingQueue​​的头部取出元素


public class BlockingQueueThread extends Thread {

private BlockingQueue<String> bq;

public BlockingQueueThread(BlockingQueue<String> bq){
this.bq = bq;
}

@Override
public void run() {
String[] strColl = new String[]{
"Java",
"Kotlin",
"JavaScript"
};



for(int i = 0; i < 1000; i ++){
try {
System.out.println(getName() + "开始动工" + i);
bq.put(strColl[i % 3]);
} catch (InterruptedException e) {
e.printStackTrace();
}
}

System.out.println(getName() + "工作结束");
}

public static void main(String[] args){
BlockingQueue<String> bq = new ArrayBlockingQueue<>(5);
new BlockingQueueThread(bq).start();
}
}

Java多线程介绍_线程组_03

结果展示


可以看到,当Thread-0运行到第6次时就已经被阻塞,不能往里添加内容



6. 线程组和未处理的异常

  • ​ThreadGroup​​表示线程组,可以对一批线程进行分类管理

  • 子线程和创建它的父线程在同一个线程组内

  • ​ThreadGroup​​方法

方法名

作用

​int activeCount​

返回线程组中活动线程的数目

​interrupt​

中断此线程组中所有活动线程的数目

​isDaemon​

线程组是否是后台线程组

​setDaemon​

设置后台线程

​setMaxPriority​

设置线程组的最高优先级


7. 线程池

  • 线程池在系统启动时即创建大量空闲的线程

  • 程序将一个​​Runnable​​​对象或​​Callable​​对象传给线程池,线程池就会启动一个空闲线程来执行他们

  • 线程结束不死亡,而是回到空闲状态

  • Java8​之后新增了一个​​Executors​​工厂类来生产线程池


7.1 ThreadPool

public class ThreadPoolTest {


public static void main(String[] args){
ExecutorService pool = Executors.newFixedThreadPool(2);

java.lang.Runnable target = () -> {
for (int i = 0; i < 100 ; i ++){
System.out.println(Thread.currentThread().getName() + "的i为" +i);
}
};

pool.submit(target);
pool.submit(target);
pool.shutdown();
}
}

Java多线程介绍_线程组_04

结果展示



7.2 ForkJoinPool

  • 将一个任务拆分成多个小任务并行计算,再把多个小任务的结果合并成总的计算结果

  • ​ForkJoinPool​​​是​​ExecutorService​​的实现类

public class PrintTask extends RecursiveAction {


public static int THREADSH_HOLD = 50;

private int start;

private int end;

public PrintTask(int start, int end){
this.start = start;
this.end = end;
}

@Override
protected void compute() {
if(end - start < THREADSH_HOLD){
for(int i = start; i < end; i ++){
System.out.println(Thread.currentThread().getName() + "的i为" + i);
}
} else {
PrintTask left = new PrintTask(start, (start + end) / 2);
PrintTask right = new PrintTask((start + end) / 2 , end);
left.fork();
right.fork();
}
}

public static void main(String[] args) throws InterruptedException {
PrintTask printTask = new PrintTask(0 , 300);
ForkJoinPool pool = new ForkJoinPool();
pool.submit(printTask);
pool.awaitTermination(2, TimeUnit.SECONDS);
pool.shutdown();

}
}

Java多线程介绍_方法名_05