多线程概念及多线程开启方式
坦克大战中怎么控制两个坦克,怎么能在玩游戏的同时还配上声音呢?
进程和线程
- 进程: 正在执行的程序
- 线程: 一条独立的执行路径
- 一个进程可以只有一条线程,也可以有多条线程
什么时候开启多线程
因为CPU在执行程序时每个时间刻度上只会存在一个线程,因此多线程实际上降低了CPU的工作效率,但同时提高了CPU的使用率
- 执行某些耗时任务时
- 希望某些程序看起来像是在同时执行
- 希望完成某个特定在子任务
- 防止线程阻塞
CPU的执行原理
原理
真实环境下,CPU能够同时执行多个程序,本质只是在同一个时间刻度上执行一条线程的一条原子性语句,只不过CPU切换执行速度非常快,我们无法察觉以为是同时执行。
并发和并行
并发:在同一个时间段同时执行
并行:在同一个时间刻度上同时执行
同步和异步
同步:并发情况下会出现同步问题
异步:能够同一个时间段能够处理多个任务,例如ajax请求
多线程和多进程的好处
多进程可以提高CPU的使用率
多线程提高了进程的使用率从而提高了CPU的使用率
多线程的启动方式
方式一:继承Thread类
- 自定义类MyThread继承Thread类。
- MyThread类里面重写run()方法。
- 创建线程对象。
- 启动线程。
注意:
- 启动线程使用的是start()方法而不是run()方法
- 线程能不能多次启动
public class ThreadDemo02 {
public static void main(String[] args) {
// 3.创建线程对象。
CopyFileThread cft = new CopyFileThread(new File("ThreadDemo01.java"), new File("thread.txt"));
// 4.启动线程。
// cft.run();
// cft.start();
PrintNumberThread pnt = new PrintNumberThread();
Thread t = new Thread(pnt);
cft.start();
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("main:" + i);
}
}
}
class PrintNumberThread implements Runnable {
// 2.重写run()方法
@Override
public void run() {
for (int i = 1; i <= 100; i++) {
System.out.println("实现Runnable方式开启子线程: " + i);
}
}
}
// 1.自定义类MyThread继承Thread类。
class CopyFileThread extends Thread {
private File srcFile;
private File descFile;
public CopyFileThread() {
super();
}
public CopyFileThread(File srcFile, File descFile) {
super();
this.srcFile = srcFile;
this.descFile = descFile;
}
// 2.重写run方法
@Override
public void run() {
// 到底写什么? --> 主方法写什么这里就写什么,这里就是和方法的写法很像,用来书写特定任务的代码
/*
* 1.任务参数 (通过线程封装外界传入) File srcFile, File descFile
* 2.任务结果 无
* 3.具体的任务 拷贝文件
*/
copyFile(srcFile, descFile);
// for (int i = 0; i < 100; i++) {
// System.out.println("子线程: " + i);
// }
}
public void copyFile(File srcFile, File descFile) {
try (BufferedInputStream bis = new BufferedInputStream(new FileInputStream(srcFile));
BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(descFile))) {
int len = 0;
byte[] bys = new byte[1024];
while ((len = bis.read(bys)) != -1) {
bos.write(bys, 0, len);
bos.flush();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public File getSrcFile() {
return srcFile;
}
public void setSrcFile(File srcFile) {
this.srcFile = srcFile;
}
public File getDescFile() {
return descFile;
}
public void setDescFile(File descFile) {
this.descFile = descFile;
}
}
方式二:实现Runnable接口
1.自定义类MyRunnable实现Runnable接口
2.重写run()方法
3.创建MyRunnable类的对象
4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
5.启动线程
实现接口方式的好处:
可以避免由于Java单继承带来的局限性。适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
public class ThreadDemo03 {
public static void main(String[] args) {
// 3.创建MyRunnable类的对象
MyRunnable mr = new MyRunnable();
// 4.创建Thread类的对象,并把步骤3创建的对象作为构造参数传递
Thread t = new Thread(mr);
// 5.启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程: " + i);
}
}
}
class MyRunnable implements Runnable {
// 2.重写run()方法
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("子线程: " + i);
}
}
}
方式三: 实现Callable方式开启线程
实现Runnable和实现Callable接口的区别
有返回值
可以声明异常
这里的返回值和异常抛出都是给到线程的启动者
public class ThreadDemo04 {
public static void main(String[] args) {
FutureTask<Integer> task = new FutureTask<>(new MyCallable(1,100));
Thread t = new Thread(task);
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("main: " + i);
}
try {
Integer value = task.get();
System.out.println(value);
} catch (InterruptedException | ExecutionException e) {
System.out.println("子线程抛出异常给主线程: " + e);
// e.printStackTrace();
}
System.out.println("over");
}
}
/*
* 计算m~n的和
*/
class MyCallable implements Callable<Integer> {
private Integer m;
private Integer n;
public MyCallable() {
super();
}
public MyCallable(Integer m, Integer n) {
super();
this.m = m;
this.n = n;
}
@Override
public Integer call() throws Exception {
int sum = 0;
for (int i = m; i <= n; i++) {
System.out.println("Callable: " + i);
sum += i;
}
throw new NullPointerException();
// return sum;
}
public Integer getM() {
return m;
}
public void setM(Integer m) {
this.m = m;
}
public Integer getN() {
return n;
}
public void setN(Integer n) {
this.n = n;
}
}
方式四: 匿名内部类开启线程
这里实际是有两种内部类方式,分别为第一种和第二种的内部类形式
public class ThreadDemo05 {
public static void main(String[] args) {
new Thread();
new Thread().start();
// 方式一继承Thread开启线程
new Thread() {}.start();
new Thread() {
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("方式一继承Thread开启线程:" + i);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("方式二实现Runnable开启线程:" + i);
}
}
}).start();
// 如果一个线程既继承了Thread,同时实现了Runnable接口,那么继承Thread优先
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("方式二实现Runnable开启线程---:" + i);
}
}
}) {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("方式一继承Thread开启线程---:" + i);
}
}
}.start();
for (int i = 0; i < 100; i++) {
System.out.println("main:" + i);
}
}
}
方式五开启线程: Lambda表达式开启线程
Lambda表达式是JDK1.8之后引入
本质就是方便匿名内部类的书写
函数式接口:
只有一个抽象方法的接口就是函数式接口,例如 Runnable
Lambda表达式的语法:
- 形参列表: 形式参数允许省略参数类型
- 箭头 ->
- 方法体: 由大括号包裹,当方法体中只有一条语句,{}可以省略;当一个方法有返回值的时候,如果只是返回一条语句,那么return和{}都可以省略,这个表达式结果自动作为返回值的结果返回
public class ThreadDemo06 {
public static void main(String[] args) {
// new Thread(new Runnable() {
//
// @Override
// public void run() {
//
// }
// }).start();
//
// new Thread( () -> System.out.println("HelloWorld") ).start();
new Thread( () -> {
for (int i = 0; i < 100; i++) {
System.out.println("实现Runnable方式: " + i);
}
}).start();
for (int i = 0; i < 100; i++) {
System.out.println("main: " + i);
}
}
}
@FunctionalInterface
interface Inter {
void show(int a, int b);
}