简述多线程:
是指从软件或者硬件上实现多个线程并发执行的技术。
具有多线程能力的计算机因有硬件支持而能够在同一时间执行多个线程,提升性能。
正式着手代码前,需要先理清4个概念:并发,并行,进程,线程。
1.并发:
可以理解为在同一时刻,有多个指令在单核CPU上交替执行。单核cpu就好比一条赛道,赛车就好比指令,第一名才有资格使用cpu的资源,所以在单核cpu中指令的并发就像赛车的缠斗,互相抢夺第一名来使用cpu的资源。
2.并行:
在同一时刻,有多个指令在多个CPU上同时执行。并行的前提是cpu资源充足,在多核cpu中可以实现并行,就像车道来到了多车道宽敞的公路,不比再过分争夺资源,可以同时悠闲的进行多个指令。
总结一下:
并行:在同一时刻,有多个指令在多个CPU上同时执行。
并发:在同一时刻,有多个指令在单个CPU上交替执行。
3.进程:
是正在运行的软件。手机中的APP,电脑中的C/S系统,都可以看做一个进程,用上面的例子中的标注,一台摩托车就可以看做一个进程。规范些的描述如下:
独立性: 进程是一个能独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
动态性: 进程的实质是程序的一次执行过程,进程是动态产生,动态消亡的。
并发性:任何进程都可以同其他进程一起并发执行。
4.线程:
是进程中的单个顺序控制流,是一条执行路径。就好比手机App中的一条下单或者退款指令,或者电脑中的一次点击提交操作。相对进程是部分性的存在,在进程中独立运行,用上述例子就好比摩托车中的发动机活塞往复。
言归正传,下面列举下多线程的实现方式:
继承Thread类的方式进行实现
实现Runnable接口的方式进行实现
利用Callable和Future接口方式实现
1.继承Thread类方式的基础实现:
package com.demo;
public class ThreadDemo extends Thread{
@Override
public void run(){
super.run();
System.out.println("第一段线程");
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
threadDemo.start();
}
}
样例继承Thread类后重写run()方法,然后再main方法中new一个样例出来,通过执行Thread类自带的start()方法运行后即可实现程序的运行。执行结果可见:
这里注意两个地方:
(1)为什么要重写run()方法?
因为run()是用来封装被线程执行的代码,也就是我们在使用时需要将业务逻辑写在run方法中。
(2)run()方法和start()方法的区别?
run():封装线程执行的代码,直接调用,相当于普通方法的调用,并没有开启线程。
start():启动线程;然后由JVM调用此线程的run()方法
2.实现Runnable接口方式:
package com.demo;
public class RunableDemo implements Runnable {
@Override
public void run(){
System.out.println("RUNABLE线程");
}
public static void main(String[] args) {
//runable是个接口,没有start方法去执行,需要借助thread类
RunableDemo runableDemo = new RunableDemo();
Thread thread = new Thread(runableDemo);
thread.start();
}
}
这里的Runnable接口不是一个类,没有自带的start()方式,所以在执行时需要借助Thread类,将样例以参数的形式传入来实现执行,thread类自带的tread方法如下:
该方式运行结果:
3.实现Callable接口方式:
public static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println("超车" + i);
}
//返回值就表示线程运行完毕之后的结果
return "获胜";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//线程开启之后需要执行里面的call方法
MyCallable mc = new MyCallable();
//Thread t1 = new Thread(mc);
//可以获取线程执行完毕之后的结果.也可以作为参数传递给Thread对象
FutureTask<String> ft = new FutureTask<>(mc);
//创建线程对象
Thread t1 = new Thread(ft);
//开启线程
t1.start();
String s = ft.get();
System.out.println(s);
}
因为thread中只能传入rannable接口,所以callable接口在实现时需要借助FutureTask类,该类的类关系图中可知,最终是继承了rannable,所以可以在thread中使用。
具体实现步骤如下:
定义一个类MyCallable实现Callable接口
在MyCallable类中重写call()方法,这里call()方法就是替代上面的run()方法,callable没有run()方法
创建MyCallable类的对象
创建Future的实现类FutureTask对象,把MyCallable对象作为构造方法的参数
创建Thread类的对象,把FutureTask对象作为构造方法的参数,即call()方法返回的数据, 启动线程 再调用get方法,就可以获取线程结束之后的结果。
执行程序结果如下:
三种方式对比:
以上就是3种基本的多线程最简单的实现方式,简单到底了。
除了最简单的实现方式外,还有一些关于线程的参数设置规则:
1.设置,获取线程的名字,线程休眠
代码演示如下,这里用threa类为例:
public static class ThreadDemo extends Thread {
// public ThreadDemo(String name) {
// super(name);
// }
@Override
public void run() {
super.run();
for (int i = 0; i < 15; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName() + "超车---积分" + i);
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
// ThreadDemo yamaha = new ThreadDemo("雅马哈");
// ThreadDemo honda = new ThreadDemo("本田");
ThreadDemo yamaha = new ThreadDemo();
ThreadDemo honda = new ThreadDemo();
yamaha.setName("雅马哈");
honda.setName("本田");
yamaha.start();
honda.start();
}
上述例子中有ThreadDemo构造方法时,可以在new实例的时候直接命名,没有构造方法,可以通过setName()方法给线程命名。
String getName():返回此线程的名称,需要注意的是,getName()方法只有thread类才可以直接用来获取线程名称,其余两种需要使用Thread.currentThread().getName()来获取当前线程名称。
public static void sleep(long time):让线程休眠指定的时间,单位为毫秒。
例子中我创建了2个线程分别命名为雅马哈和本田
运行结果:
线程调度
多线程的并发运行时,计算机中的CPU,在任意时刻只能执行一条机器指令。每个线程只有获得CPU的使用权才能执行代码。各个线程轮流获得CPU的使用权,分别执行各自的任务。
线程有两种调度模型
1.分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
2.抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些
Java使用的是抢占式调度模型:
线程的优先级
public final void setPriority(int newPriority) 设置线程的优先级
public final int getPriority() 获取线程的优先级
这里上代码示例演示下,用Callable的方式实现:
首先定义一个mycallable类:
public static class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + "超车" + i);
}
//返回值就表示线程运行完毕之后的结果
return Thread.currentThread().getName() +"完赛";
}
}
然后是实现方式:
public static void main(String[] args) throws ExecutionException, InterruptedException {
//优先级: 1 - 10 默认值:5
MyCallable mc = new MyCallable();
FutureTask<String> ducati = new FutureTask<>(mc);
Thread t1 = new Thread(ducati);
t1.setName("马力狗");
t1.setPriority(10);
System.out.println("马力狗马力优先级"+t1.getPriority());
t1.start();
System.out.println(ducati.get());
MyCallable mc2 = new MyCallable();
FutureTask<String> suzuki = new FutureTask<>(mc2);
Thread t2 = new Thread(suzuki);
t2.setName("GSX");
t2.setPriority(1);
System.out.println("GSX马力优先级"+t2.getPriority());
t2.start();
System.out.println(suzuki.get());
}
在优先级设置方法setPriority的源码中可以看到有两个参数控制最大最小优先级,最大是10,最小是1。在设置时不要超过这个范围,否则会引发报错
由于电脑配置较高,而且执行的逻辑较简单,数据量小,所以运行结果体现不出cpu资源的竞争,看上去都是按着代码顺序执行的,就不展示结果了。
以上内容如有不对欢迎指正,业余时间总结记录一下。