线程的历史 – 一部对于CPU性能压榨的历史
▪单进程人工切换
纸带机
▪ 多进程批处理
多个任务批量执行
▪ 多进程并行处理
把程序写在不同的内存位置上来回切换
▪ 多线程
一个程序内部不同任务的来回切换
selector - epoll ▪ 纤程/协程
绿色线程,用户管理的(而不是OS管理的)线程
- 单核CPU设定多线程是否有意义?
通常情况下每一个线程运行在一个CPU的内核上,但是任务分为IO密集型和计算密集型。计算需要CPU,IO不要占用CPU,如果是执行IO密集型任务的时候就可以把CPU等系统资源让出来给其他线程 - 工作线程数是不是设置的越大越好?
不是,线程是运行在内核态,线程太多了来回切换是需要耗费系统资源的 - 工作线程数(线程池中线程数量)设多少合适?
线程的基本概念
之前的硬件,只有一个CPU
之前的OS,只运行一个进程
随着多核CPU的出现,人们开始追求对CPU效率的极致压榨
多线程的程序随之诞生,但随之诞生的,也是非常难以应对的各种并发bug
进程 线程
- 什么是进程:资源分配的基本单位(静态概念)
- 什么是线程:资源调度的基本单位(动态概念) 通俗说:一个程序中不同的执行路径
线程
在操作系统中,线程是比进程更小的能够独立运行的基本单位。同时,它也是CPU调度的基本单位。线程本身基本上不拥有系统资源,只是拥有一些在运行时需要用到的系统资源,例如程序计数器,寄存器和栈等。一个进程中的所有线程可以共享进程中的所有资源。
多线程
多线程可以理解为在同一个程序中能够同时运行多个不同的线程来执行不同的任务,这些线程可以同时利用CPU的多个核心运行。
为什么要写多线程程序
为了压榨CPU,提高资源利用率。多线程编程能够最大限度的利用CPU的资源。如果某一个线程的处理不需要占用CPU资源时(例如IO线程),可以使当前线程让出CPU资源来让其他线程能够获取到CPU资源,进而能够执行其他线程对应的任务,达到最大化利用CPU资源的目的。
启动线程的5种方法
- 继承Thread类
- 实现Runnable接口:无返回值,实现对象作为Thread构造函数的参数来实现Thread
- 实现Callable接口:有返回值,实现类要作为FutureTask构造函数的参数,然后FutureTask作为Thread构造函数的参数来实现Thread
- Lambda 表达式
- 线程池
因为JAVA是单继承多实现,所以实现接口方式更方便
import java.util.concurrent.*;
public class T02_HowToCreateThread {
static class MyThread extends Thread {
@Override
public void run() {
System.out.println("Hello MyThread!");
}
}
static class MyRun implements Runnable {
@Override
public void run() {
System.out.println("Hello MyRun!");
}
}
static class MyCall implements Callable<String> {
@Override
public String call() {
System.out.println("Hello MyCall");
return "success";
}
}
//启动线程的5种方式
public static void main(String[] args) throws Exception {
new MyThread().start();
new Thread(new MyRun()).start();
new Thread(() -> {
System.out.println("Hello Lambda!");
}).start();
FutureTask<String> task = new FutureTask<>(new MyCall());
Thread t = new Thread(task);
t.start();
System.out.println(task.get());
ExecutorService service = Executors.newCachedThreadPool();
service.execute(() -> {
System.out.println("Hello ThreadPool");
});
execute只能提交Runnable类型的任务.
execute会直接抛出任务执行时的异常,可以用try、catch来捕获,和普通线程的处理方式完全一致
execute()没有返回值
Future<String> f = service.submit(new MyCall());
String s = f.get();
System.out.println(s);
service.shutdown();
}
submit既能提交Runnable类型任务也能提交Callable类型任务
submit会吃掉异常,可通过Future的get方法将任务执行时的异常重新抛出。
submit有返回值,所以需要返回值的时候必须使用submit
}
sleep() yield() join()
- Sleep,意思就是睡眠,当前线程暂停一段时间让给别的线程去运行。Sleep是怎么复活的?由你的睡眠时间而定,等睡眠到规定的时间自动复活
- Yield,就是当前线程正在执行的时候停止下来进入等待队列(就绪状态,CPU依然有可能把这个线程拿出来运行),回到等待队列里在系统的调度算法里头呢还是依然有可能把你刚回去的这个线程拿回来继续执行,当然,更大的可能性是把原来等待的那些拿出一个来执行,所以yield的意思是我让出一下CPU,后面你们能不能抢到那我不管
- join, 意思就是在自己当前线程加入你调用Join的线程(),本线程等待。等调用的线程运行完了,自己再去执行。t1和t2两个线程,在t1的某个点上调用了t2.join,它会跑到t2去运行,t1等待t2运行完毕继续t1运行(自己join自己没有意义)
public class T03_Sleep_Yield_Join {
public static void main(String[] args) {
testSleep();
// testYield();
//testJoin();
}
static void testSleep() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
static void testYield() {
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
if (i % 10 == 0) Thread.yield();
}
}).start();
new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("------------B" + i);
if (i % 10 == 0) Thread.yield();
}
}).start();
}
static void testJoin() {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100; i++) {
System.out.println("A" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 100; i++) {
System.out.println("B" + i);
try {
Thread.sleep(500);
//TimeUnit.Milliseconds.sleep(500)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}