多线程基础
1、多线程
多线程对应的一个概念叫多任务,就是在同时的时候执行了多个任务。现实中很多这样的例子,看起来是多个任务在同时都在做,但是其实本质上我们的大脑在同一时间依旧是只做了一件事。
多条执行路径,主线程和主线程同时进行。
程序:程序是指令和数据的有序集合,其实就是我们编写的源代码,其本身没有任何运行的含义,是一个静态的概念。
进程:进程是执行程序的一次执行过程,它是一个动态的概念,是系统资源分配的单位。
线程:一个进程中通常可以包含若干个线程,哪怕你啥也不干,进程里也有一个主线程在。当然一个进程中至少有一个线程,不然没有存在的意义。线程是CPU调度和执行的单位。
注意,很多的多线程其实是模拟出来的,只是一个逻辑上的多线程,真正的多线程所指的是有多个cpu,即多核,比如服务器。如果是模拟出来的多线程,即在一个CPU的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同事执行的错觉,这就是并发和并行的概念所在。
总结:
线程就是独立的执行路径;在程序运行时,即便没有自己创建线程,后台也会有多个线程,如主线程,gc线程;main()称之为主线程,为系统的入口,用于执行整个程序;在一个进程中,如果开辟了多个线程,线程的运行由调度器安排调度,调度器是和操作系统紧密相关的,先后顺序不是人为可以干预的;对同一份资源操作时,会存在资源抢夺的问题,需要假如并发控制;线程会带来额外的开销,比如cpu调度时间,并发控制开销;每个线程在自己的工作内存交互,内存控制不当会造成线程之间互相影响,造成数据不一致。
2、多线程实现方式
2.1、继承Thread类实现(不推荐使用)
package com
.lwq
.demo01
;
public class TestThread01 extends Thread{
@Override
public void run() {
for(int i
= 0;i
<300;i
++){
System
.out
.println("子线程在执行:"+i
);
}
}
public static void main(String
[] args
) {
TestThread01 th1
= new TestThread01();
th1
.start();
for (int i
= 0;i
<300;i
++){
System
.out
.println("主线程在执行:"+i
);
}
}
}
最终的打印输出的结果就是交替的,主线程和子线程就是交替执行的。这就是多线程的体现,不行就多循环几个,可能你电脑快,还没启动子线程呢,主线程就执行完了。
2.1.1、多线程实战下载网络文件
多线程下载远程网络文件:
package com
.lwq
.demo01
;
import org
.apache
.commons
.io
.FileUtils
;
import java
.io
.File
;
import java
.io
.IOException
;
import java
.net
.URL
;
public class TestDownFile extends Thread{
private String url
;
private String fileName
;
public TestDownFile(String url
,String fileName
){
this.url
= url
;
this.fileName
= fileName
;
}
@Override
public void run() {
WebDownLoadFile webDownLoadFile
= new WebDownLoadFile();
webDownLoadFile
.downFile(url
,fileName
);
System
.out
.println(fileName
+ "下载完成");
}
public static void main(String
[] args
) {
TestDownFile th1
= new TestDownFile("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603099364613&di=8d0eb530879636ad03082d68f4355695&imgtype=0&" +
"src=http%3A%2F%2Fphotocdn.sohu.com%2F20090309%2FImg262681568.jpg","1.jpg");
TestDownFile th2
= new TestDownFile("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603099491319&di=e5a4d59a9f1b4ee76d3f13c6471eb619&imgtype=0&" +
"src=http%3A%2F%2Fimg.mp.itc.cn%2Fupload%2F20160711%2F4ff5aecbde4e4f9995887852fbd1a4a5_th.jpg","2.jpg");
TestDownFile th3
= new TestDownFile("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603099529442&di=b6c7ab7ec465624c3709c0379b2685fb&imgtype=0&" +
"src=http%3A%2F%2Fdingyue.ws.126.net%2F2020%2F0501%2Fcd303a0cj00q9mwr4004ic000xc00p9c.jpg","3.jpg");
th1
.start();
th2
.start();
th3
.start();
}
}
class WebDownLoadFile{
public void downFile(String url
,String fileName
){
try {
FileUtils
.copyURLToFile(new URL(url
),new File(fileName
));
} catch (IOException e
) {
e
.printStackTrace();
System
.out
.println("downFile方法的文件下载出错");
}
}
}
最后的结果执行的就不是
1,2,3的顺序,这就是多线程,由cpu执行调度的,所以具有随机性。
2.2、实现Runnable接口(推荐使用)
package com
.lwq
.demo01
;
public class TestRunnable02 implements Runnable{
public void run() {
for(int i
= 0;i
<500;i
++){
System
.out
.println("子线程运行:" + i
);
}
}
public static void main(String
[] args
) {
TestRunnable02 tr
= new TestRunnable02();
Thread th
= new Thread(tr
);
th
.start();
for(int i
= 0;i
<500;i
++){
System
.out
.println("主线程运行:" + i
);
}
}
}
对比
实现方式就不说了,主要说下优缺点,继承Thread方式是java的继承方式,但是java的面向对象特性决定了是单继承,单继承是具有局限性的。
实现Runnable接口方式就不会被单继承局限,灵活方便,而且只需要你有一个Runnable的实现类,就能到处使用这个实现类对象然后去创建代理启动线程。
TestRunnable02 tr
= new TestRunnable02();
new Thread(tr
,a
).start();
new Thread(tr
,b
).start();
new Thread(tr
,c
).start();
2.2.1、多线程实战模拟抢票
package com
.lwq
.demo01
;
public class TestBuyTickets implements Runnable {
private int ticketNums
= 10;
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
) {
TestBuyTickets testBuyTickets
= new TestBuyTickets();
new Thread(testBuyTickets
,"刘文强").start();
new Thread(testBuyTickets
,"顾海滨").start();
new Thread(testBuyTickets
,"王月").start();
}
}
运行结果如下:
线程刘文强--》抢到了第10张票
线程王月--》抢到了第9张票
线程顾海滨--》抢到了第10张票
线程顾海滨--》抢到了第8张票
线程王月--》抢到了第7张票
线程刘文强--》抢到了第8张票
线程顾海滨--》抢到了第6张票
线程王月--》抢到了第6张票
线程刘文强--》抢到了第6张票
线程顾海滨--》抢到了第5张票
线程刘文强--》抢到了第4张票
线程王月--》抢到了第5张票
线程王月--》抢到了第3张票
线程刘文强--》抢到了第3张票
线程顾海滨--》抢到了第2张票
线程王月--》抢到了第1张票
线程刘文强--》抢到了第0张票
线程顾海滨--》抢到了第0张票
我们看到线程之间是交错执行的,但是出现另一个问题就是在多个线程之间操作一份数据的时候,数据会紊乱,这就是线程的不安全问题。这块我们初识线程不安全,之后在学习到同步的时候会具体处理这个问题。
2.2.2、多线程实战模拟龟兔赛跑
package com
.lwq
.demo01
;
public class TestRace implements Runnable{
private int distance
= 100;
private String winner
;
public void run() {
for(int i
= 0;i
<=distance
;i
++){
if(i
== 100){
winner
= Thread
.currentThread().getName();
System
.out
.println("本次的胜利者是:" + Thread
.currentThread().getName());
}else{
System
.out
.println(Thread
.currentThread().getName() + "--》跑了" + i
+ "米");
}
if("兔子".equals(Thread
.currentThread().getName()) && (i
%10 == 0)){
try {
Thread
.sleep(10);
} catch (InterruptedException e
) {
e
.printStackTrace();
}
}
if(winner
!= null
&& !"".equals(winner
)){
break;
}
}
}
public static void main(String
[] args
) {
TestRace testRace
= new TestRace();
new Thread(testRace
,"兔子").start();
new Thread(testRace
,"乌龟").start();
}
}
这个其实没什么难的,就是一个简单的实现,但是其中只有一个学习的点在我看来就是把实际的模型映射到代码逻辑中的一个能力。这个其实很重要其实也很难。
2.3、实现Callable接口(不重要,用得少)
package com
.lwq
.demo02
;
import org
.apache
.commons
.io
.FileUtils
;
import java
.io
.File
;
import java
.io
.IOException
;
import java
.net
.URL
;
import java
.util
.concurrent
.*
;
public class TestCallable03 implements Callable<Integer> {
private String url
;
private String fileName
;
public Integer
call() throws Exception
{
WebDownLoadFile webDownLoadFile
= new WebDownLoadFile();
webDownLoadFile
.downFile(url
,fileName
);
System
.out
.println("下载了文件:" + fileName
);
return 0;
}
public TestCallable03(String url
,String fileName
){
this.url
= url
;
this.fileName
= fileName
;
}
public static void main(String
[] args
) throws ExecutionException
, InterruptedException
{
ExecutorService ex
= Executors
.newFixedThreadPool(1);
TestCallable03 th1
= new TestCallable03("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603099364613&di=8d0eb530879636ad03082d68f4355695&imgtype=0&" +
"src=http%3A%2F%2Fphotocdn.sohu.com%2F20090309%2FImg262681568.jpg","1.jpg");
TestCallable03 th2
= new TestCallable03("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603099491319&di=e5a4d59a9f1b4ee76d3f13c6471eb619&imgtype=0&" +
"src=http%3A%2F%2Fimg.mp.itc.cn%2Fupload%2F20160711%2F4ff5aecbde4e4f9995887852fbd1a4a5_th.jpg","2.jpg");
TestCallable03 th3
= new TestCallable03("https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1603099529442&di=b6c7ab7ec465624c3709c0379b2685fb&imgtype=0&" +
"src=http%3A%2F%2Fdingyue.ws.126.net%2F2020%2F0501%2Fcd303a0cj00q9mwr4004ic000xc00p9c.jpg","3.jpg");
Future
<Integer> future1
= ex
.submit(th1
);
Future
<Integer> future2
= ex
.submit(th2
);
Future
<Integer> future3
= ex
.submit(th3
);
Integer result1
= future1
.get();
Integer result2
= future2
.get();
Integer result3
= future3
.get();
System
.out
.println(result1
);
System
.out
.println(result2
);
System
.out
.println(result3
);
ex
.shutdown();
}
}
class WebDownLoadFile{
public void downFile(String url
,String fileName
){
try {
FileUtils
.copyURLToFile(new URL(url
),new File(fileName
));
} catch (IOException e
) {
e
.printStackTrace();
System
.out
.println("downFile方法的文件下载出错");
}
}
}
下载结果和之前方式下载一样。
2.4、Lamda表达式(看起来帅,不好debug)
小知识,没啥必须的,先不研究。