多线程介绍
同步与异步
同步: 排队执行 , 效率低但是安全.
异步: 同时执行 , 效率高但是数据不安全.
并发与并行
并发: 指两个或多个事件在同一个时间段内发生。
并行: 指两个或多个事件在同一时刻发生(同时发生)。
进程与线程
(1) 进程:
进程是计算机分配资源的基本单位,指一个应用程序,拥有独自的内存空间(像堆栈类似的东西)
(2) 线程:
线程是进程中的一个执行路径,共享一个内存空间(进程的),线程之间可以自由来回切换,并发执行,一个进程最少有一个线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干个执行路径又可以划分成若干个线程
★进程、线程的执行是使用cpu,cpu是资源的调度者,cpu相当于脑,每个cpu只能做一件事,即只能执行一个线程
★多线程并不能提高运行速度,但是能提高运行效率,多线程只不过是cpu在每个线程来回切换,而且切换的速度很快,让人产生电脑能同时运行多个程序,一个cpu能同时做多件事的错觉
多线程的效果:
例子:像一边网易云听歌一边上qq聊天其实不是两个同时运行的,cpu执行时在这两个进程(网易云和qq)的线程间来回切换,因为切换的时间很短,可能就0.0001秒,我们感受不到它的停顿
多线程的优点是:
程序代码不用按照线性顺序执行,即不用做完一件事再做另一件事,比如上面的例子我们是想一边听歌一边聊天,而不是聊天的时候音乐停止,等聊完天了再音乐继续
(所以说多线程只能提高运行效率,让优先级高的任务先完成,而不是提高运行速度)
线程调度
(1)分时调度:指把cpu的使用权,平均分配给每一个线程
(2)抢占式调度:cpu在有空闲资源的时候,抛出时间片,优先级高的线程会得到时间片的(抢用cpu)概率会比优先级低的高,线程的优先级自己设置,同一优先级的线程随机执行没有先后顺序,都是随机的,主要看谁能抢到cpu的时间片(默认的调度方式)
实现多线程的三种方法
Thread类
使用多线程,要继承Thread类,创建对象调用start方法,会自动运行继承Thread类的代码
★子线程有属于自己的栈空间,共用一份堆内存,它运行方法在自己的栈内存中执行
public class Demo1 {
public static void main(String[] args) {
// 创建新的线程
Thread t = new Text();
// 启动线程
t.start();
// 主线程
int count = 5;
for (int i = 0; i < 5; i++) {
count--;
System.out.println(Thread.currentThread().getName() + i);
}
}
// 多线程的实现方法
// 1.继承Thread类
static class Text extends Thread{
@Override
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
}
Thread一些常用方法
(1) setDaemon(boolean on){ }
设置守护线程daemon,当所有用户线程(子线程、主线程)死亡,daemon才会死亡
(2) setName 设置线程名
(3) setId 设置线程id
(4) sleep(long millis){ }休眠,传入休眠时间毫秒数
(5) setPriority(int newPriority){ }设置优先级,输入线程优先级常量
Runnable接口
public class Demo2 {
public static void main(String[] args) {
// 创建线程要执行的任务
Test1 run = new Test1();
// 创建线程,并为其分配一个任务
Thread t = new Thread(run);
//启动线程
t.start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() +"我是"+ i);
}
}
// 第二种实现多线程的方法
// 实现Runnable接口(线程要执行的任务)
static class Test1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() +"我是"+ i);
}
}
}
}
★Runnable接口比Thread类的优势:
(1)通过创建任务,然后给线程分配的方式来实现多线程,更适合多个线程同时
执行相同任务的情况
(2)可以避免单继承带来的局限性
(3)任务与线程本身是分离的,提高了程序的健壮性
(4)线程池技术,接收Runnable类型的任务,不接受Thread类型的线程
Callable接口
Thread类和Runnable创健的线程都是并发执行的,而Callable接口的线程(会返回一个boolean类型的值)可以实现并发执行的线程,也可以实现等子线程执行完,主线程再执行的情况(相当于主线程指派了一个任务给子线程,等主线程接收到任务的结果才会执行)使用FutureTask类的get方法可以让主线程等子线程结束再执行
和Runnable一样效果的代码实现
public class Demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务(Callable会返回一个boolean类型的值)
Callable<Integer> c = new Test2();
// 创建FutureTask类,用里面的工具方法检索c的返回值
FutureTask<Integer> task = new FutureTask<>(c);
// 启动新线程
new Thread(task).start();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
}
}
// 第三种多线程技术
// 实现Callable接口
static class Test2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
}
return null;
}
}
}
实现主线程等待子线程执行完成的代码
public class Demo3 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建任务(Callable会返回一个boolean类型的值)
Callable<Integer> c = new Test2();
// 创建FutureTask类,用里面的工具方法检索c的返回值
FutureTask<Integer> task = new FutureTask<>(c);
// 启动新线程
new Thread(task).start();
// 等主线程等子线程执行,等到等待计算完成,然后检索其结果。
task.get();
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
}
}
// 第三种多线程技术
// 实现Callable接口
static class Test2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"==="+i);
}
return null;
}
}
}
Runnable接口和Callable的区别
(1)Runnable不会返回结果,也不能抛出已检查的异常。
(2)Runnable创健的线程都是并发执行的,而Callable接口的线程(会返回一个boolean类型的值)可以实现并发执行的线程,也可以实现等子线程执行完,主线程再执行的功能
线程池
线程池实际就是(类似数组)存放一个过多个线程的容器,另外有存放任务的容器
有四种线程池:缓存线程池(不定长)、定长线存池、单线程线程池、周期性任务定长线程池)
★线程都结束后不会马上关闭线程池,但是一定时间以后线程池也会关闭
缓存线程池
长度是不定的(自动扩容数组)
执行流程:
1、 判断线程池是否有空闲线程
2、 存在则使用空闲线程
3、 不存在,则创建新线程放进线程池,如果线程池已满,自动扩容
★如果在传入新任务时有多个线程空闲,则抢到时间片的先执行任务
public static class Test {
// 缓存线程池(不定长)
public static void main(String[] args) throws IOException {
// 创建线程池
ExecutorService service = Executors.newCachedThreadPool();
// 指挥线程池中的空闲线程执行新的任务
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"社会主义万岁");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"社会主义万岁");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"社会主义万岁");
}
});
// 为了证明线程池里的线程是重复使用的,这里我们让一个线程完成任务后沉睡等全部线程都完成了再传任务
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"新中国社会主义");
}
});
定长线程池
长度是指定的数量
执行流程:
1、 判断线程池是否有空闲线程
2、 存在则使用空闲线程
3、 不存在,且线程池未满的情况下,则创建新的线程放进线程池,然后使用
4、 不存在,且线程池已经满了的情况下,则任务在任务数组中排队等待,等待线程池存在空闲线程
★如果在传入新任务时有多个线程空闲,则抢到时间片先执行任务
// 定长线程池
ExecutorService service = Executors.newFixedThreadPool(2);
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"下雨了");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"下雨了");
try {
Thread.sleep(1500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
// 为了证明是定长的线程我们执行三个任务我们让上面两个线程输出了之后睡眠
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"下雨了");
}
});
单线程线程池
效果与定长线程池创建时指定数量为1的效果一致
执行流程:
1、 判断线程池的那个线程是否为空闲
2、 空闲则使用
3、 不空闲则等待
// 单线程池
ExecutorService service = Executors.newSingleThreadExecutor();
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我们");
}
});
service.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"我们");
}
});
周期定长线程池
执行流程:
1、 判断线程池是否有空闲线程
2、 存在则使用
3、 不存在,且线程池未满的情况下,创建新的线程放进线程池,执行任务
4、 不存在,且线程池已满的情况下,则等待线程池存在空闲的线程
周期性任务:
定时执行,当某个时机触发时,自动执行某任务
// 创建周期定长线程池,指定长度
ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
//* 周期定长线程池要设定四个参数
1.任务
2.等待多久时间开始第一次任务
3.周期间隔时间
4.时间度量(时、分、秒),用TimeUnit的常量表示时分秒
//*
service.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println("game start!");
}
}, 5, 2, TimeUnit.SECONDS);
Lambda表达式
Lambda表达式(是一种函数式编程思想)
public class Demo {
public static void main(String[] args) {
// 下面展示函数式编程思想(只关注结果,不关注怎么实现,不在乎是面向对象还是过程,只操作方法体)
print((int x, int y) ->{
x = x + y;
return x;
},5,6);
// 在面向对象思想中,如果想调用Math接口的方法,我们需要传入Math接口的实现类
// 用匿名类的方法实现这个Math接口
print(new Math() {
@Override
public int sum(int x, int y) {
x = x + y;
return x;
}
},5,6);
}
// 提供一个打印Math接口sum结果的方法
public static void print(Math m, int x, int y){
int num = m.sum(x, y);
System.out.println(num);
}
static interface Math{
int sum(int x, int y);
}