Java学习多线程:线程创建线程状态线程同步线程通信全总结
Posted 毛_三月
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java学习多线程:线程创建线程状态线程同步线程通信全总结相关的知识,希望对你有一定的参考价值。
1、基本概念
-
进程
-
- 在操作系统中运行的程序就是进程,进程就是执行程序的一次执行过程,它是一个动态的概念式系统资源分配的单位
- 通常再一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义,线程是CPU调度和执行的单位
-
线程
-
- 线程就是独立的执行路径
- 在程序运行时,即使没有自己创建线程,后台也会有多个线程,比如主线程,GC线程
-
- main()称之为主线程,为系统的入口,用于执行整个程序
- 在一个进程中,如果开辟了多个线程,线程的运行是由调度器安排调度的,调度器是与操作系统紧密相关的,先后顺序是不能人为干预的
-
- 对同一份资源操作时,会存在资源抢夺的问题,需要加入并发控制
- 线程会带来额外的开销,如CPU调度时间,并发控制开销
-
- 每个线程在自己的工作内存交互,内存控制不当会造成数据不一致
-
多线程
-
- 多条执行路径,主线程与子线程并行交替执行(普通方法只有主线程一条路径)
2、线程创建
2.1、 继承 Thread 类(重点)
Thread API
Class Thread
-
- java.lang.Thread
-
All Implemented Interfaces:
Runnable -
已知直接子类:
ForkJoinWorkerThread
public class Thread
extends Object
implements Runnable
线程是程序中执行的线程。Java虚拟机允许应用程序同时执行多个执行线程。
每个线程都有优先权。 具有较高优先级的线程优先于优先级较低的线程执行。 每个线程可能也可能不会被标记为守护程序。 当在某个线程中运行的代码创建一个新的Thread
对象时,新线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护线程。
当Java虚拟机启动时,通常有一个非守护进程线程(通常调用某些指定类的名为main
的方法)。 Java虚拟机将继续执行线程,直到发生以下任一情况:
-
- 已经调用了
Runtime
类的exit
方法,并且安全管理器已经允许进行退出操作。 - 所有不是守护进程线程的线程都已经死亡,无论是从调用返回到
run
方法还是抛出超出run
方法的run
。
- 已经调用了
创建一个新的执行线程有两种方法。 一个是将一个类声明为Thread
的子类。 这个子类应该重写run
类的方法Thread
。 然后可以分配并启动子类的实例。 例如,计算大于规定值的素数的线程可以写成如下:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并启动它运行:
PrimeThread p = new PrimeThread(143);
p.start();
另一种方法来创建一个线程是声明实现类接口。 那个类然后实现了run
方法。 然后可以分配类的实例,在创建Thread
时作为参数传递,并启动。 这种其他风格的同一个例子如下所示:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并启动它运行:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
每个线程都有一个用于识别目的的名称。 多个线程可能具有相同的名称。 如果在创建线程时未指定名称,则会为其生成一个新名称。
除非另有说明,否则将参数传递给null
中的构造函数或方法将导致抛出NullPointerException
。
-
- 从以下版本开始:
JDK1.0 - 另请参见:
Runnable
,Runtime.exit(int)
,run()
,stop()
- 从以下版本开始:
-
自定义线程类,继承Thread类
-
重写run()方法,编写线程执行体
-
在主函数中创建一个线程对象,调用start()方法开启线程。
案例:
案例1:主线程调用run
package com.mao.demo01;
/**
* @ClassName TestThread1
* @Description TODO
* @Author Huchao
* @Date 2021/11/7 16:21
* @Version 1.0
**/
public class TestThread1 extends Thread {
@Override
public void run() {
// run 方法体线程
for (int i = 0; i < 10; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
// 创建一个线程
final TestThread1 testThread1 = new TestThread1();
// start 开启线程
// testThread1.start();
testThread1.run();
// 主线程
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程----"+i);
}
}
}
案例2:主线程调用start
package com.mao.demo01;
/**
* @ClassName TestThread1
* @Description TODO
* @Author Huchao
* @Date 2021/11/7 16:21
* @Version 1.0
**/
public class TestThread1 extends Thread {
@Override
public void run() {
// run 方法体线程
for (int i = 0; i < 10; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
// 创建一个线程
final TestThread1 testThread1 = new TestThread1();
// start 开启线程
testThread1.start();
// testThread1.run();
// 主线程
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程----"+i);
}
}
}
总结:线程开启不一定立即执行,由CPU调度执行。
案例:图片下载
package com.mao.demo01;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @ClassName TestThread2
* @Description TODO
* @Author Huchao
* @Date 2021/11/8 19:32
* @Version 1.0
**/
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() {
// super.run();
final webDownloader webDownloader = new webDownloader();
webDownloader.downloader(url,name);
System.out.println("下载了文件名为:"+name);
}
public static void main(String[] args) {
final TestThread2 test1 = new TestThread2("https://i0.hdslb.com/bfs/article/f39427eefe8b5c9318b6c9ef99c4108efdf1e747.jpg@1320w_740h.webp","edg1.png");
final TestThread2 test2 = new TestThread2("https://i0.hdslb.com/bfs/archive/57ab5a6e8b36c227cc13c2cd96270e04b0a253be.png","edg2.png");
final TestThread2 test3 = new TestThread2("https://i0.hdslb.com/bfs/activity-plat/static/b711c4ecbed559f94155437efb3d8532/j6mFBYivhq_w1920_h658.png","文豪试炼场.png");
// 实际下载并不是按照顺序执行
test1.start();
test2.start();
test3.start();
}
}
// 下载器
class webDownloader{
// 下载方法
public void downloader(String url,String name){
try {
FileUtils.copyURLToFile(new URL(url),new File(name)); // 讲url变成图片
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO 异常,downloader方法出现问题");
}
}
}
实际下载并未按照顺序执行
2.2、 实现Runnable接口(重点)
Runnable接口API
另一种方法来创建一个线程是声明实现类接口。 那个类然后实现了run
方法。 然后可以分配类的实例,在创建Thread
时作为参数传递,并启动。 这种其他风格的同一个例子如下所示:
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
然后,以下代码将创建一个线程并启动它运行:
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
每个线程都有一个用于识别目的的名称。 多个线程可能具有相同的名称。 如果在创建线程时未指定名称,则会为其生成一个新名称。
除非另有说明,否则将参数传递给null
中的构造函数或方法将导致抛出NullPointerException
。
-
- 从以下版本开始:
JDK1.0 - 另请参见:
Runnable
,Runtime.exit(int)
,run()
,stop()
- 从以下版本开始:
-
自定义线程类,实现Runnable接口
-
重写run()方法,编写线程执行体
-
执行线程需要丢入runnable接口实现类,调用start()方法。
案例:
package com.nty.test02;
public class TestThread2 implements Runnable {
@Override
public void run() {
//run方法线程方法体
for (int i = 0; i < 20; i++) {
System.out.println("我在看代码----" + i);
}
}
public static void main(String[] args) {
//创建一个线程对象
TestThread2 testThread2 = new TestThread2();
//创建线程对象,通过线程对象来开启线程,代理
// Thread thread = new Thread(testThread2);
//
// //start开启线程
// thread.start();
new Thread(testThread2).start();
//主线程
for (int i = 0; i < 200; i++) {
System.out.println("我在学习多线程-----" + i);
}
}
}
以上两种方式的比较:
继承 Thread 类
-
子类继承 Thread 类具备多线程能力
-
启动线程:子类对象 .start()
-
不建议使用:避免 OOP 单继承局限性
-
实现 Runnable 接口
// Thread 源码探究
public
class Thread implements Runnable {
/* Make sure registerNatives is the first thing <clinit> does. */
private static native void registerNatives();
static {
registerNatives();
}
private volatile String name;
private int priority;
private Thread threadQ;
private long eetop;
/* Whether or not to single_step this thread. */
private boolean single_step;
实现接口 Runnable
-
具有多线程能力
-
启动线程:传入目标对象 + Thread对象.start()
-
推荐使用:避免单继承局限性,方便同一个对象被多个线程使用。
@FunctionalInterface
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
初识并发问题——买火车票
package com.mao.demo01;
import com.sun.org.apache.bcel.internal.generic.NEW;
/**
* @ClassName TestThread
* @Description TODO
* @Author HuChao
* @Date 2021/11/8 20:40
* @Version 1.0
**/
// 多个线程同时操作同一个对象
// 买火车票的例子
// 发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
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) {
final TestThread4 testThread4 = new TestThread4();
new Thread(testThread4,"超哥").start();
new Thread(testThread4,"废物凤").start();
new Thread(testThread4,"黄牛").start();
}
}
执行结果:
"E:\\Program Files\\Java\\bin\\java.exe" "-javaagent:D:\\Program Files (x86)\\IDEA\\IntelliJ IDEA 2020.3.2\\lib\\idea_rt.jar=60780:D:\\Program Files (x86)\\IDEA\\IntelliJ IDEA 2020.3.2\\bin" -Dfile.encoding=UTF-8 -classpath "E:\\Program Files\\Java\\jre\\lib\\charsets.jar;E:\\Program Files\\Java\\jre\\lib\\deploy.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\access-bridge-64.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\cldrdata.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\dnsns.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\jaccess.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\jfxrt.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\localedata.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\nashorn.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\sunec.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\sunjce_provider.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\sunmscapi.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\sunpkcs11.jar;E:\\Program Files\\Java\\jre\\lib\\ext\\zipfs.jar;E:\\Program Files\\Java\\jre\\lib\\javaws.jar;E:\\Program Files\\Java\\jre\\lib\\jce.jar;E:\\Program Files\\Java\\jre\\lib\\jfr.jar;E:\\Program Files\\Java\\jre\\lib\\jfxswt.jar;E:\\Program Files\\Java\\jre\\lib\\jsse.jar;E:\\Program Files\\Java\\jre\\lib\\management-agent.jar;E:\\Program Files\\Java\\jre\\lib\\plugin.jar;E:\\Program Files\\Java\\jre\\lib\\resources.jar;E:\\Program Files\\Java\\jre\\lib\\rt.jar;E:\\soft\\workspace\\java\\多线程\\out\\production\\多线程;E:\\soft\\workspace\\java\\多线程\\src\\com\\lib\\commons-io-2.11.0.jar" com.mao.demo01.TestThread4
超哥-->拿到了第10票
废物凤-->拿到了第9票
黄牛-->拿到了第8票
废物凤-->拿到了第7票
黄牛-->拿到了第7票
超哥-->拿到了第7票
超哥-->拿到了第6票
黄牛-->拿到了第5票
废物凤-->拿到了第6票
黄牛-->拿到了第4票
超哥-->拿到了第2票
废物凤-->拿到了第3票
黄牛-->拿到了第1票
超哥-->拿到了第-1票
废物凤-->拿到了第0票
Process finished with exit code 0
发现问题:多个线程操作同一个资源的情况下,线程不安全,数据紊乱
龟兔赛跑问题:
package com.mao.demo01;
/**
* @ClassName Race
* @Description TODO
* @Author HuChao
* @Date 2021/11/8 21:14
* @Version 1.0
**/
public class Race implements Runnable {
// 胜利者 静态 保证只有一个winner
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((long) 0.1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 判断比赛是否结束
boolean flag = gameover(i);
// 如果比赛结束 就停止比赛
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"-->跑了"+i+"步");
}
}
// 判断比赛是否完成
private boolean gameover(int steps){
if (winner!=null){ // 已经存在胜利者
return true; // 比赛结束
}else {
if (steps>=100){
winner=Thread.currentThread().getName();
System.out.println("winner is"+winner);
return true;
}
}
return false;
}
public static void main(String[] args) {
final Race race = new Race();
new Thread(race,"兔子").start()多线程