最后更新:2020年10月23日09点47分
目录
Thread和Runnable的区别 = 使用Runnable接口的好处:
十一、创建多线程程序的第三种方法(实现Callable接口)
一、并发与并行
1、并发
指的是多个事件在同一时间段内发生(多个事件交替执行);
2、并行
指的是多个时间在同一时刻发生(多个事件同时执行)(CPU是多核的情况下才能并行);
二、程序、线程与进程
1、程序
程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念;
2、进程
①是指一个内存中运行的应用程序,每个进程都有独立的内存空间,一个应用程序可以同时运行多个进程;
②进程也程序的一次执行过程,是系统运行程序的基本单位;
③系统运行一个程序即是进程的创建、运行到消亡的过程;
3、线程
①线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程;
②一个进程可以有多个线程,这个应用程序可以称为多线程程序;
注意:
很多多线程是模拟出来的,真正的多线程指的是有多个CPU,即多核,如服务器;
一个CPU在一个时间点只能执行一个线程,但由于各线程之间切换的速度极快,所以模拟出来的多线程会令人感到有同时执行的错觉;
简言之:一个程序至少有一个进程,一个进程可以有多个线程;
三、线程调度
1、分时调度
所有线程轮流获得CPU的使用权,平均分配每个线程占用CPU的时间;
2、抢占式调度
优先让优先级高的线程使用CPU,如果线程优先级相同那么或随机选一个(线程随机性),java使用的为抢占式调度;
四、主线程
1、主线程
执行主方法(main)的线程;
2、单线程程序
①程序只有一个线程;
②程序从main方法从上到下一次执行;
五、创建多线程程序的第一种方法(继承Thread类)
1、创建多线程程序
Thread类是描述多线程的类,要想常见多线程类就必须继承Thread类;
2、实现步骤
①创建Thread类的子类;
②重写Thread类中的run方法,设置线程任务(开启线程要做什么);
③创建Thread类的子类对象;
④调用Thread类中的start方法,开启新线程(调用start方法),执行run方法;
3、start方法
①start方法的调用结果是两个线程并发地运行——当前线程(从调用返回给 start 方法)和另一个线程(执行其 run 方法);
②多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动;
③如果不调用start方法,而是调用run方法,就意味着不是开启新线程,只是调用一个类的普通方法,都在主线程执行;
4、注意
java程序属于抢占式调度,优先级高的线程优先执行,同一优先级的线程随机执行;
5、代码示例
自定义的线程类:
package study.thread;
//定义一个类继承Thread类
public class MyThread extends Thread {
//重写Thread类的run方法,做要做的事情
@Override
public void run() {
super.run();
//输出run(0-9)
for(int i=0; i<10; i++){
System.out.println("run"+i);
}
}
}
Test测试类:
package study.thread;
public class Test {
public static void main(String[] args) {
//实例化一个自定义线程的对象
MyThread myThread = new MyThread();
//调用start方法,会自动执行run方法,开启一个新的线程
myThread.start();//此处若写run方法,则执行run方法内容,并未开启新线程
//在主方法中也输出main(0-9)
for(int i=0; i<10; i++){
System.out.println("main"+i);
}
}
}
执行结果(交替执行):
main0
run0
main1
run1
main2
run2
main3
run3
main4
main5
main6
main7
main8
main9
run4
run5
run6
run7
run8
run9
六、多线程原理
1、随机打印
2、内存图解
3、执行时机
开启子线程之后,子线程并不一定是立即执行,而是要看CPU是如何调度的,CPU调度了才执行;
七、Thread类的常用方法
1、获取线程名称
方法一:使用getName方法
自定义的线程类:
package study.thread;
//定义一个类继承Thread类
public class MyThread extends Thread {
//重写Thread类的run方法,做要做的事情
@Override
public void run() {
super.run();
System.out.println(getName());
}
}
Test测试类:
package study.thread;
public class Test {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
运行结果:
Thread-1
Thread-2
Thread-0
方法二:
自定义的线程类:
package study.thread;
//定义一个类继承Thread类
public class MyThread extends Thread {
//重写Thread类的run方法,做要做的事情
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread());
//也可以这么写Thread.currentThread().getName();(链式编程)
//输出的结果与方法一相同
}
}
Test测试类:
package study.thread;
public class Test {
public static void main(String[] args) {
new MyThread().start();
new MyThread().start();
new MyThread().start();
}
}
运行结果:
Thread[Thread-0,5,main]
Thread[Thread-1,5,main]
Thread[Thread-2,5,main]
2、设置线程名称(了解)
自定义的线程类:
package study.thread;
//定义一个类继承Thread类
public class MyThread extends Thread {
public MyThread() {
super();
}
//为线程设置名字的第二种方法
public MyThread(String name) {
super(name);
}
//重写Thread类的run方法,做要做的事情
@Override
public void run() {
super.run();
System.out.println(Thread.currentThread().getName());
}
}
Test测试类:
package study.thread;
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//为线程设置名字的第一种方法
myThread.setName("小明线程");
myThread.start();//小明线程
new MyThread("小李线程").start();
}
}
运行结果:
小明线程
小李线程
3、sleep
作用:
使正在执行的线程,暂停特定的毫秒;
代码示例:
package study.thread;
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
for (int i = 0; i < 61; i++) {
System.out.println(i);
//线程暂时1秒
Thread.sleep(1000);
}
}
}
八、创建多线程程序的第二种方法(实现Runnable接口)
2019.9.21.10:19补充:
如果多线程类extends了另一个类,那就不能再extends第一种方法的Thread类了,所以可以通过实现Runnable接口实现多线程;
1、实现步骤
①创建Runable的实现类;
②该实现类重写run方法;
③创建实现类对象;
④new Thread(多线程实现类对象).start();
2、代码示例
RunnableImpl:
package study.thread;
public class RunnableImpl implements Runnable {
@Override
public void run() {
System.out.println("Runnable接口实现类的run方法");
}
}
Test测试类:
package study.thread;
public class RunnableImplTest {
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
new Thread(runnable).start();
}
}
运行结果:
Runnable接口实现类的run方法
九、Thread和Runnable的区别
Thread和Runnable的区别 = 使用Runnable接口的好处:
①避免了单继承的局限性——一个类继承了Thread就不能继承其他类,但若使用Runnable接口实现多线程则不仅就可以继承其他类,也可以继承其他接口;
②增强了程序的扩展性,降低了程序的耦合性(解耦)
——实现Runnable接口的方式,把设置线程任务和开启线程进行了分离(解耦);
——实现Runnable重写run方法用来设置线程的任务;
——创建Thread类用来开启线程;
十、匿名内部类方式实现线程的创建
1、继承Thread类的方式
//继承Thread类的方式
new Thread(){
@Override
public void run() {
super.run();
System.out.println("继承Thread类的方式");
}
}.start();
2、实现Runnable接口的方式
//实现Runnable接口的方式
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("实现Runnable接口的方式");
}
};
new Thread(runnable).start();
十一、创建多线程程序的第三种方法(实现Callable接口)
(了解,工作三五年之后变得重要!)
1、实现步骤
①实现Callable接口,需要返回值类型;
②重写call方法,需要抛出异常;
③创建目标对象;
④创建执行服务;
⑤提交执行;
⑥获取结果;
⑦关闭服务
2、代码实现
package com.zb.thread;
import java.util.concurrent.*;
public class Dog implements Callable<Boolean> {
@Override
public Boolean call(){
System.out.println("开始数狗:");
for (int i = 0; i < 20; i++) {
System.out.println("开始数狗,第" + i + "条狗!");
}
return true;
}
//主线程
public static void main(String[] args) throws ExecutionException, InterruptedException {
//创建执行服务
ExecutorService service = Executors.newFixedThreadPool(1);//线程池大小
//提交执行
Future<Boolean> result = service.submit(new Dog());
//获取结果
Boolean aBoolean = result.get();
//关闭服务
service.shutdownNow();
//打印结果
System.out.println(aBoolean);
}
}
3、运行结果
开始数狗:
开始数狗,第0条狗!
开始数狗,第1条狗!
开始数狗,第2条狗!
开始数狗,第3条狗!
开始数狗,第4条狗!
开始数狗,第5条狗!
开始数狗,第6条狗!
开始数狗,第7条狗!
开始数狗,第8条狗!
开始数狗,第9条狗!
开始数狗,第10条狗!
开始数狗,第11条狗!
开始数狗,第12条狗!
开始数狗,第13条狗!
开始数狗,第14条狗!
开始数狗,第15条狗!
开始数狗,第16条狗!
开始数狗,第17条狗!
开始数狗,第18条狗!
开始数狗,第19条狗!
true
4、实现Callable接口的好处
可以定义返回值;
可以抛出异常;
十二、Thread底层原理——静态代理模式
1、Thread实现了Runnable接口
class Thread implements Runnable
底层原理就是静态代理模式,Runnable是一个多线程接口,只有一个run方法,Thread代理了这个run方法,并添加了一些其他功能;