多线程复习详解
最近在准备面试,准备把以前的东西复习一下和学习一下新东西。写博客做个记录,会分为几个大类吧
多线程概述
普通方法和多线程的区别

- 普通方法走的是run(),执行完run之后才继续
- 多线程是start(),多条执行路径,主线程和子线程交替进行
Process(过程,即进程)与Thread(线程)的总结
-
说起进程,就要提到程序,程序是指令和数据的有序集合,本身没有任何含义,是一个静态的
-
进程就是执行一次程序的过程,是动态的,是系统分配资源的单位
-
一个进程中可以有多个线程(如一部电影是画面,声音,弹幕等组成的)且至少有一个线程,线程是CPU调节和调度的单位
PS:代码中很多线程是模拟出来的,真正的多现实应该是需要多个CPU的,但只有一个CPU的时候,一个时间点CPU只能执行一个代码,不过因为CPU切换的很快,造成了多线程的错觉
-
线程就是独立执行的路径,程序运行时,即使没有自己创建的线程,后台也会有多个线程(main主线程,gc垃圾处理线程等).
-
main()是主线程,是系统的入口,用于执行整个程序,在程序执行过程中,如果有多个线程,线程的调度运行是由调度器安排调度的,调度器和操作系统密切相关,先后顺序不能人为干预。
-
对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制。
-
线程多不一定是有点,因为多线程会带来额外开销,如CPU调度时间,并发控制开销。
-
很重要的一点,每个线程只在自己的工作内存交互,内存控制不当会造成数据不一致。
多线程详解(Thread,Runnable,Callable)
Thread class
操作过程
-
因为是个类,所以是要继承(只能继承一个),所以我们需要自定义线程类继承
-
重写run()方法,编写线程执行体,即你想跑的线程内容
-
创建线程对象,调用start()方法启动线程1,先new一个方法,如Thread1 Th1 = new Thread1()。2,调用start方法,Th1 .start()
代码
public class Thread1 extends Thread {
@Override
public void run() {
//super.run();
//这里我们就是写run方法线程体,即我们想写的线程
for (int i = 0; i < 200; i++) {
System.out.println("这是thread1的第"+i+"次执行");
}
}
public static void main(String[] args) {
//main主线程
//创建一个线程对象
Thread1 Th1 = new Thread1();
//调用start方法开启线程
Th1.start();
for (int i = 0; i < 200; i++) {
System.out.println("这是主线程的第"+i+"次执行");
}
}
下面的是结果图示例,多线程开启之后,会随机执行某个线程

Thread总结
- 这个方法是先重写run方法,再调动start方法
- 线程开启不一定立即执行,是由CPU调动的
Tread练习(下载网络图片)
package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @author :xiaole
* @version :1.0
* 内容:多线程下载网图
* */
public class Thread2 extends Thread{
//创建url和文件名称
private String url;//图片地址
private String fileName;//文件名
public Thread2(String url,String fileName){
this.url = url;
this.fileName = fileName;
}
@Override
public void run() {
WebDownload webDownload = new WebDownload();
webDownload.download(url,fileName);
System.out.println("下载的文件地址是"+ url);
System.out.println("下载的文件地址是"+ fileName);
}
public static void main(String[] args) {
//https://pic.cnblogs.com/face/2273763/20211109003826.png
//上面的文件地址
Thread2 test01 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke01.jpg");
test01.start();
Thread2 test02 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke02.jpg");
test02.start();
Thread2 test03 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke03.jpg");
test03.start();
//最后下载后的图片在当前文件的同一层
}
}
//下载的类
class WebDownload{
//下载方法
public void download(String url,String fileName){
try {
FileUtils.copyURLToFile(new URL(url),new File(fileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("捕获到了download方法的IO异常");
}
}
}
下面的是图片位置

Runnable接口
操作过程
- 因为是个接口,所以实现接口就可以(implements Runnable)
- 重写run()方法,编写线程执行体,即你想跑的线程内容
- 创建线程对象,调用start()方法启动线程1,先new一个方法,如Thread1 Th1 = new Thread1()
- 通过线程对象开启线程
代码
package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;
public class Thread3 implements Runnable {
@Override
public void run() {
//super.run();
//这里我们就是写run方法线程体,即我们想写的线程
for (int i = 0; i < 200; i++) {
System.out.println("这是Runnable1的第"+i+"次执行");
}
}
public static void main(String[] args) {
//main主线程
//创建Runnable接口的实现类对象(runnable只是一个接口,还是需要线程的相关类实现功能)
Thread3 th3 = new Thread3();
// //1。创建线程对象,通过线程对象开启线程,代理
// Thread thread = new Thread(Th3);
// //2.调用start方法开启线程
// thread.start();
//以上两步骤可以合并成一步
new Thread(th3).start();
for (int i = 0; i < 200; i++) {
System.out.println("这是主线程的第"+i+"次执行");
}
}
}
Runnable总结
- 原理本质和Thread是一样的
- 先要有接口
- 要创建Runnable接口的实现类对象( Thread3 th3 = new Thread3() )
- 创建线程对象,通过线程对象开启线程,代理,最后还是通过star方法
Runnable练习(下载网络图片,只更改了一个方法)
package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//如果是通过接口,则是用以下方法
public class Thread2 implements Runnable{
//创建url和文件名称
private String url;//图片地址
private String fileName;//文件名
public Thread2(String url,String fileName){
this.url = url;
this.fileName = fileName;
}
@Override
public void run() {
WebDownload webDownload = new WebDownload();
webDownload.download(url,fileName);
System.out.println("下载的文件地址是"+ url);
System.out.println("下载的文件地址是"+ fileName);
}
public static void main(String[] args) {
//https://pic.cnblogs.com/face/2273763/20211109003826.png
Thread2 test01 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke01.jpg");
new Thread(test01).start();
Thread2 test02 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke02.jpg");
new Thread(test02).start();
Thread2 test03 = new Thread2("https://pic.cnblogs.com/face/2273763/20211109003826.png","xiaoke03.jpg");
new Thread(test03).start();
}
}
//下载的类
class WebDownload{
//下载方法
public void download(String url,String fileName){
try {
FileUtils.copyURLToFile(new URL(url),new File(fileName));
} catch (IOException e) {
e.printStackTrace();
System.out.println("捕获到了download方法的IO异常");
}
}
}
Thread类和Runnable的对比
继承Thread类
-
子类继承Thread类具备多线程能力
-
启动线程:子类对象,start()
*不建议使用,避免OOP单线程局限性
实现Runnable接口
-
实现Runnable接口具有多线程能力
-
启动线程:传入目标对象+Thread对象.start()
*推荐使用,避免单继承局限性,灵活方便,且同一个对象可以被多个线程使用
Callable接口
多线程代码和问题总结
多线程并发问题,即处理同一个资源的数据问题(前面有提到)
-
举例买火车票,代码和结果如下
package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread; //模拟多人买火车票 //即通过开启多线程模拟不同的人 //问题总结,多个线程操作同一个线程的时候,线程不安全。数据紊乱,下面几行就是某次的结果 // 恭喜xiaoke抢到了第3张票 // 恭喜xiaosu抢到了第4张票 // 恭喜xiaoka抢到了第4张票 // 恭喜xiaoka抢到了第2张票 // 恭喜xiaosu抢到了第1张票 public class Thread4 implements Runnable{ //设置票数 private int ticketNumber = 200; @Override public void run() { while (true){ if (ticketNumber <= 0){ System.out.println("票已售罄"); break; } //模拟延时 Thread.sleep try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("恭喜"+Thread.currentThread().getName()+"抢到了第"+ticketNumber--+"张票"); } } public static void main(String[] args) { Thread4 thread4 = new Thread4(); new Thread(thread4,"xiaoke").start(); new Thread(thread4,"xiaoka").start(); new Thread(thread4,"xiaosu").start(); } }结果如下图
![]()
-
写个龟兔赛跑(简单避免一下上图中占用统一资源的问题)
package com.xiaoke.study.day01.com.xiaoke.study.Demo02_Thread; public class Thread5_saipao implements Runnable { //使用static,这样任何时候就可以只判断一次,在成功的时候赋值就可 private static boolean flag = false; //写赛事和对赛事结果的判断 @Override public void run() { for (int i = 0; i <= 100; i++) { getWinner(i); //因为不止一个人,所以这边要先判断是否已经走完了 //即兔子和乌龟哪个赢了 if (flag){ break; } if (Thread.currentThread().getName()=="兔子"){ if(i!=0 && i/10 == 0) { try { Thread.sleep(20); } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(Thread.currentThread().getName()+"已经走到了第"+i+"步"); } } public void getWinner(int i){ if(flag != true) { if(i==100){ flag = true; System.out.println("这场比赛的胜利者是"+Thread.currentThread().getName()); } } } public static void main(String[] args) { Thread5_saipao thread5_saipao = new Thread5_saipao(); new Thread(thread5_saipao,"兔子").start(); new Thread(thread5_saipao,"乌龟").start(); } }
总体总结
- 线程开启不一定立即执行,由CPU调度执行
- start才是开启线程的方法,run只是单纯的执行方法,不会开启多线程

浙公网安备 33010602011771号