基本概念

程序是操作系统能运行的文件

进程是运行的程序

一个程序可以有多个进程

进程:进程是操作系统进行资源分配的基本单位

多进程并行处理:把程序写在不同的内存位置上来回切换

多线程并行处理:一个程序内部不同任务的来回切换

通俗来讲:

  • 线程:程序里不同的执行路径

  • mian方法开启的线程叫做主线程

线程:调度执行的基本单位

  • 一个程序执行的时候,内存会先找到它的主线程,推送给CPU执行,主线程执行时可能会开启其他线程,这时就涉及到线程之间的切换

线程切换也是需要资源的

面试题

  1. 单核CPU设定多线程是否有意义?

    有意义

    CPU密集型:计算多,IO密集型:输入输出多

  2. 工作线程数是不是设置的越大越好

    不是,线程切换也需要消耗资源

  3. 工作线程数(线程池中线程数)设为多少最合适?

    N = N(CPU)+ U(CPU)* (1 + W / C)

    N(CPU)是处理器核的数目

    U(CPU)是期望的CPU利用率(0 - 1)

    W / C 是等待时间与计算时间的比率

线程的创建

创建线程的5种方法

  1. new MyThread().start() MyThread扩展了Thread类,重写run方法

  2. new Thread(new r()).start() r继承了Runnable接口,重写run方法

  3. new Thread(lamda).start() 使用lamda表达式

    1
    2
    3
    new Thread(()->{
    System.out.println("Hello Lambda");
    }).start()
  4. ThreadPool 线程池 Executors.newCachedThrad

    1
    2
    3
    4
    ExecutorService service = Executors.newCachedThreadPool();
    service.execute(()->{
    System.out.println("Hello ThreadPool");
    })
  5. Future Callable and FutureTask

    Callable 与 Runnable很类似,但继承Callable时,可以通过泛型指定返回类型,在通过线程池得到,用Future 接收

    1
    2
    Future<String> f = service.submit(new MyCall());
    String s = f.get();

    FutureTask类型

    1
    2
    3
    4
    FutureTask<String> task = new FutureTask<>(new MyCall());
    Thread t = new Thread(task);
    s.start();
    System.out.println(task.get());

线程的方法

sleep():当前线程暂停一段时间。

yield():退出CPU,进入等待队列(返回就绪状态)

join():t2线程里使用t1.join(),t1线程加入t2线程中,等t2执行完,再执行t1。经常用来等待另一个线程结束

线程的状态*

小节说明:

  • 本节重要程度:中 (帮助理解线程问题,保障知识完整性,面试很少考)
  • 本节难度:低

Java线程的6种状态:

zhuangtai

  1. NEW:线程刚刚被创建还没有启动
  2. RANNABLE:可运行状态
  3. WAITING:等待被唤醒
  4. TIMED WAITING:隔一段时间后自动唤醒
  5. BLOCKED:被阻塞,正在等待锁
  6. TERMINATED:线程结束

每个线程只能start一次

lock方法用的是JUC的CAS上锁,忙等待,不会陷入BLOCKED状态,而是WAITING

线程的打断*

  • interrupt():实例方法,设置线程的中断标志位,线程自己决定是否中断
  • isInterrupted():实例方法,有没有人打扰我?
  • interrupted():静态方法,有没有人打扰我(当前线程)?复位!
1
2
3
4
//Thread.java  
public void interrupt() //t.interrupt() 打断t线程(设置t线程某给标志位f=true,并不是打断线程的运行)
public boolean isInterrupted() //t.isInterrupted() 查询打断标志位是否被设置(是不是曾经被打断过)
public static boolean interrupted()//Thread.interrupted() 查看“当前”线程是否被打断,如果被打断,恢复标志位

线程使用sleep(),wait(),jion()这些方法时,会设置中断标志位,同时,也会抛出interruptedException异常,在抛出这个异常的时候,可以做某些操作,同时也会重置标志位。

interrupt()不能打断正在竞争锁的线程synchronized()和lock()

如果想打断正在竞争锁的线程,使用ReentrantLock的lockInterruptibly()

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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/**
* interrupt与lockInterruptibly()
*/
public class T11_Interrupt_and_lockInterruptibly {

private static ReentrantLock lock = new ReentrantLock();

public static void main(String[] args) {
Thread t1 = new Thread(()-> {
lock.lock();
try {
SleepHelper.sleepSeconds(10);
} finally {
lock.unlock();
}
System.out.println("t1 end!");
});

t1.start();

SleepHelper.sleepSeconds(1);


Thread t2 = new Thread(()-> {
System.out.println("t2 start!");
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
System.out.println("t2 end!");
});

t2.start();

SleepHelper.sleepSeconds(1);

t2.interrupt();

}
}

线程的结束

面试题:

如何优雅的结束一个线程?

e.g.上传一个大文件,正在处理费时的计算,如何优雅的结束这个线程?

结束线程的方法:

  1. 自然结束(能自然结束就尽量自然结束)
  2. stop() suspend() resume(),现在已经不推荐用了,可能会导致数据的不一致
  3. volatile标志
    1. 不适合某些场景(比如还没有同步的时候,线程做了阻塞操作,没有办法循环回去)
    2. 打断时间也不是特别精确,比如一个阻塞容器,容量为5的时候结束生产者,
      但是,由于volatile同步线程标志位的时间控制不是很精确,有可能生产者还继续生产一段儿时间
  4. interrupt() and isInterrupted(比较优雅)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public static void main(String[] args){
Thread t = new Thread(() -> {
while(!Thread.interrupted()){
//sleep wait
}

System.out.println("t1 end!")
})
t.start();

SleepHelper.sleepSeconds(1);

t.interrupt();
}