注意:很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器。如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错局。
核心概念总结:
- 线程就是独立的执行路径;
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,如主线程,gc线程;
- main()称之为主线程,为系统的入口,用于执行整个程序;
- 在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的。
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制;
- 线程会带来额外的开销,如cpu调度时间,并发控制开销;
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
2线程创建
三种创建方式
Thread类
-
自定义线程类继承Thread类
-
重写run()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
继承Thread()类创建线程:
//创建线程方式一:继承Thread类,重写run()方法,调用start开启线程
public class TestThread1 extends Thread{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println(“我在看代码—” + i);
}
}
public static void main(String[] args) {
//主线程
//创建一个线程对象 调用start方法开启线程
TestThread1 testThread1 = new TestThread1();
testThread1.start();
for (int i = 0; i < 20; i++) {
System.out.println(“我在学习多线程–” + i);
}
}
}
将testThread1.start();
改为testThread1.run();
,注意观察执行顺序:
调用start()方法是同时执行的,而调用run()方法是先跑完run()方法,再跑下一个
注意:
- 线程开启不一定立即执行,由CPU调度执行
网图下载
需要commons-io.jar包
将jar包复制到文件夹中,并右击“add as Library”
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
//练习Thread实现一个多线程同步下载图片
public class TestThread2 extends Thread {
private String url;//网络图片地址
private String name;//保存的文件名
public TestThread2(String url, String name) {
this.url = url;
this.name = name;
}
//下载图片线程的执行体
@Override
public void run() {
WebDownLoader webDownLoader = new WebDownLoader();
webDownLoader.downLoader(url, name);
System.out.println(“下载了文件,名为:” + name);
}
public static void main(String[] args) {
TestThread2 t1 = new TestThread2(“https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2F2021%2Fedpic_source%2F1d%2F31%2F79%2F1d3179f050039cc45f9f62a105b12c3a_8.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646039329&t=700647f4c668754644f743467bda0e09”, “1.jpg”);
TestThread2 t2 = new TestThread2(“https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2F2021%2Fedpic_source%2F1d%2F31%2F79%2F1d3179f050039cc45f9f62a105b12c3a_8.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646039329&t=700647f4c668754644f743467bda0e09”, “2.jpg”);
TestThread2 t3 = new TestThread2(“https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fup.enterdesk.com%2F2021%2Fedpic_source%2F1d%2F31%2F79%2F1d3179f050039cc45f9f62a105b12c3a_8.jpg&refer=http%3A%2F%2Fup.enterdesk.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1646039329&t=700647f4c668754644f743467bda0e09”, “3.jpg”);
t1.start();
t2.start();
t3.start();
}
}
class WebDownLoader {
//下载方法
public void downLoader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println(“IO异常,downLoader方法出现问题”);//异常提醒
}
}
}
实现Runnable
-
定义MyRunnable类实现Runnable接口
-
实现run()方法,编写线程执行体
-
创建线程对象,调用start()方法启动线程
//创建线程方式二:实现Runnable接口
public class TestThread3 implements Runnable{
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println(“我在看代码—” + i);
}
}
public static void main(String[] args) {
//创建runnable接口的实现类对象
TestThread3 testThread3 = new TestThread3();
//创建线程对象,通过线程对象开启线程
// Thread thread = new Thread();
// thread.start();
new Thread(testThread3).start();
for (int i = 0; i < 20; i++) {
System.out.println(“我在学习多线程–” + i);
}
}
}
线程同时执行。
小结:
- 继承Thread类:
子类继承Thread类具备多线程能力
启动线程:子类对象.start()
**不建议使用,,避免OOP单继承局限性**
- 实现Runnable接口:
实现接口Runnable具有多线程能力
启动线程:传入目标对象+Thread对象.start()
**推荐使用,,避免单继承局限性,灵活方便,方便同一个对象被多个线程使用**
初识并发问题
典型案例:买火车票
//多个线程同时操作同一个对象
//买火车票
public class TestThread4 implements Runnable {
//票数
private int ticketNums = 10;
@Override
public void run() {
while (true) {
if (ticketNums <= 0) {
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + “拿到了第” + ticketNums-- + “票”);
}
}
public static void main(String[] args) {
TestThread4 testThread4 = new TestThread4();
new Thread(testThread4, “小明”).start();
new Thread(testThread4, “小红”).start();
new Thread(testThread4, “黄牛”).start();
}
}
其中:
Thread.currentThread().getName()
:获得当前执行线程的名字
Thread.sleep(200);
:模拟延时
但是通过观察运行情况,发现有不同人拿到同一张票的情况,即多个线程操作同一个资源,线程不安全。
案例:龟兔赛跑
//模拟龟兔赛跑
public class Race implements Runnable {
//胜利者
private static String winner;
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
//模拟兔子睡觉
if (Thread.currentThread().getName().equals(“兔子”)&&i%10==0) {
try {
Thread.sleep(1);
计算机网络
-
HTTP 缓存
-
你知道 302 状态码是什么嘛?你平时浏览网页的过程中遇到过哪些 302 的场景?
-
HTTP 常用的请求方式,区别和用途?
-
HTTPS 是什么?具体流程
-
三次握手和四次挥手
-
你对 TCP 滑动窗口有了解嘛?
-
WebSocket与Ajax的区别
-
了解 WebSocket 嘛?
-
HTTP 如何实现长连接?在什么时候会超时?
-
TCP 如何保证有效传输及拥塞控制原理。
-
TCP 协议怎么保证可靠的,UDP 为什么不可靠?
开源分享:【大厂前端面试题解析+核心总结学习笔记+真实项目实战+最新讲解视频】
算法
-
链表
-
字符串
-
数组问题
-
二叉树
-
排序算法
-
二分查找
-
动态规划
-
BFS
-
栈
-
DFS
-
回溯算法