ToB企服应用市场:ToB评测及商务社交产业平台
标题:
多线程详解
[打印本页]
作者:
种地
时间:
2022-8-24 18:02
标题:
多线程详解
1. 线程简介
程序
:程序时指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念
进程
:执行程序的一次执行过程,或是正在运行的一个程序,是一个
动态
的过程。由它自身的产生、存在和消亡的过程
线程是由进程创建的
,是进程的一个实体。通常在一个进程中可以包含
若干个
线程,当然一个进程中至少有一个线程,不然没有存在的意义。
线程是CPU调度和执行的单位
很多多线程是模拟出来的,真正多线程实现并行,是有多个CPU,即多核。一个CPU统一时间点只能执行一个代码,因为切换快,所以就有同时执行的错觉。
宏观并行,微观串行
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
//获取当前电脑的CPU数量
int i = runtime.availableProcessors();
System.out.println(i);
}
复制代码
2. 线程实现(重点)
2.1 实现方式一:继承Thread类
实现步骤:
自定义线程类继承
Thread类
重写
run()
方法,编写
线程执行体
创建线程对象,调用
start()
方法启动线程
package com.ThreadStudy;
/**
* @ClassName ThreadDemo01
* @Description TODO 创建线程方式一:继承Thread类
* @Author Mark
* @Date 2022/6/23 13:40
* @Version 1.0
*/
//创建线程方式一:继承Thread类,重写run方法,调用start开始线程
public class ThreadDemo01 {
public static void main(String[] args) {
Cat cat = new Cat(); //new Cat() .var/ctrl+alt+v/alt+enter导入前半部分
cat.start();//启动线程
//当main线程启动一个子线程 Thread-0 主线程不会阻塞,会继续执行
/*
* 源码:
* public synchronized void start() {
* start0();
* }
*
* private native void start0();//start0是本地方法,是JVM调用,底层由C/C++实现的
* //真正实现多线程效果由start0()实现
* */
}
}
//Thread实现了Runnable接口的run方法
/*
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
class Cat extends Thread {//Cat继承于Thread类,该类可当作线程使用
int times = 0;
@Override
public void run() {//重写run方法,写入自己的业务代码
while (true) {
//每隔一秒输出一次“喵喵”
System.out.println("喵喵 " + (++times)+" 线程名:"+Thread.currentThread().getName());
//sleep:使线程休眠 ctrl+alt+t写入try-catch,或者鼠标指向要抛出异常的方法,ctrl+alt+enter
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 80)
break;//当times=8,线程停止
}
}
}
复制代码
进程启动--->创建main线程--->在主线程中创建一个新的子线程Thread-0
可以使用控制台jconsole打开Java监视和管理控制台查看线程执行情况
,当所有的线程结束后,进程才会结束(
当主线程结束,主线程的子线程还未结束,进程并不会关闭,而是等待子线程结束后关闭
)
案例:
多线程下载图片
package com.ThreadStudy;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
/**
* @ClassName ThreadDemo02
* @Description TODO 练习Thread,实现多线程同步下载图片
* @Author Mark
* @Date 2022/6/23 19:09
* @Version 1.0
*/
public class ThreadDemo02 extends Thread {
private String url;//网络地址
private String name;//保存的文件名
public ThreadDemo02(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) {
ThreadDemo02 thread1 = new ThreadDemo02("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
ThreadDemo02 thread2 = new ThreadDemo02("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
ThreadDemo02 thread3 = new ThreadDemo02("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");
thread1.start();
thread2.start();
thread3.start();
//下载了的文件名为:mgn.jpeg --thread2
//下载了的文件名为:fpx.jpeg --thread1
//下载了的文件名为:edg.png --thread3
}
}
//下载器
class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\" + name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现异常");
}
}
}
复制代码
2.2 实现方式二:实现Runnable接口
java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时再用继承Thread类创建线程显然不可行了,因此我们通过实现Runnable接口来创建线程
实现步骤:
定义MyRunnable类实现
Runnable接口
实现
run()
方法,编写线程执行体
创建线程对象,调用
start()
方法启动线程
ThreadDemo03 threadDemo03=new ThreadDemo03();
new Thread(threadDemo03).start();
推荐使用Runnable对象,因为Java单继承的局限性
package com.ThreadStudy;
/**
* @ClassName TheardDemo03
* @Description TODO 创建线程方式二:实现Runnable接口
* @Author Mark
* @Date 2022/6/23 20:54
* @Version 1.0
*/
public class ThreadDemo03 {
public static void main(String[] args) {
// Dog dog = new Dog();
// //dog.start; dog不能调用start
// //创建Thread对象,把dog对象(实现Runnable的对象)放入Thread
// new Thread(dog).start();
// //这里底层使用了设计模式:代理模式
Tiger tiger = new Tiger();
Proxy proxy = new Proxy(tiger);
proxy.start();
}
}
class Animal{
}
class Tiger extends Animal implements Runnable{
@Override
public void run() {
System.out.println("嗷嗷");
}
}
//线程代理类,模拟一个极简的Thread类
class Proxy implements Runnable { //这里可以把Proxy类当作Thread
private Runnable target = null;//属性、类型是Runnable
@Override
public void run() {
if (target != null) {
target.run();//动态绑定
}
}
public Proxy(Runnable target) {
this.target = target;
}
public void start() {
start0();//⭐⭐⭐⭐⭐
}
private void start0() {
run();
}
}
class Dog implements Runnable {
int times = 0;
@Override
public void run() {
while (true) {
System.out.println("汪汪" + (++times) + " 线程名:" + Thread.currentThread().getName());
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (times == 8) {
break;
}
}
}
}
复制代码
代理是为了不改变原来代码的基础上增强代码
静态代理:
package com.ThreadStudy;
/**
* @ClassName StaticProxy
* @Description TODO 静态代理
* @Author Mark
* @Date 2022/6/24 16:10
* @Version 1.0
*/
public class StaticProxy {
public static void main(String[] args) {
//代理是为了不改变原来代码的基础上增强代码
//在原来只能”嘿嘿嘿“的基础上增加了”准备婚礼“和”付尾款“
// WeddingCompany weddingCompany=new WeddingCompany(new You());//WeddingCompany代理You
// weddingCompany.Marry();
You you=new You();
new Thread(()-> System.out.println("我爱你")).start();
new WeddingCompany(you).Marry();
}
}
//功能接口
interface Marry{
//结婚方法
void Marry();
}
//You:真实对象,实现结婚接口
class You implements Marry{
@Override
public void Marry() {
System.out.println("嘿嘿嘿");
}
}
//WeddingCompany:代理角色,帮助You
class WeddingCompany implements Marry{
//帮助对象
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void Marry() {
before();
this.target.Marry();
after();
}
private void after() {
System.out.println("付尾款");
}
private void before() {
System.out.println("准备婚礼");
}
}
复制代码
方式一和方式二
对比
:
继承Thread类实现Runnable接口子类继承Thread类具备多线程能力实现接口Runnable具有多线程能力启动线程:子类对象.start()启动线程:线程对象(目标对象).start()不建议使用:避免OOP单继承局限性建议使用:避免单继承,灵活方便,方便同一个对象被多个线程使用案例:售票
通过继承Thread实现:
public class SellTicket {
public static void main(String[] args) {
SellTicket01 sellTicket01 = new SellTicket01();
SellTicket01 sellTicket02 = new SellTicket01();
SellTicket01 sellTicket03 = new SellTicket01();
sellTicket01.start();
sellTicket02.start();
sellTicket03.start();
}
}
class SellTicket01 extends Thread {
private static int ticketNum = 100;//让多个线程共享ticketNum
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("售票结束");
break;
}
//休眠50ms
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
" 剩余票数:" + (--ticketNum));
}
}
}
复制代码
对于函数式接口,我们可以通过Lambda表达式来创建该接口的对象
推导lambda表达式:
public class SellTicket02 {
public static void main(String[] args) {
SellTicketWindow sellTicketWindow=new SellTicketWindow();
new Thread(sellTicketWindow).start();
new Thread(sellTicketWindow).start();
new Thread(sellTicketWindow).start();
}
}
class SellTicketWindow implements Runnable {
private int ticketNum = 100;
@Override
public void run() {
while (true) {
if (ticketNum <= 0) {
System.out.println("end...");
break;
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("窗口 " + Thread.currentThread().getName() + "售出一张票" +
" 剩余票数:" + (--ticketNum));
}
}
}
复制代码
lambda简化:
public class ThreadDemo05 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);
} 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;
} else {
return false;
}
}
public static void main(String[] args) {
ThreadDemo05 threadDemo05 = new ThreadDemo05();
new Thread(threadDemo05, "小兔子").start();
new Thread(threadDemo05, "老乌龟").start();
}
}
复制代码
3. 线程状态
线程方法:
方法说明void setName(String name)将此线程的名称更改为等于参数 namevoid getName()返回此线程的名称void start()导致此线程开始执行; Java虚拟机调用此线程的run方法void run()调用该Runnable对象的run方法void setPriority(int newPeiority)更改此线程的优先级void getPriority()返回此线程的优先级void join()等待这个线程死亡。static void sleep(long millis)导致正在执行的线程以指定的毫秒数加上指定的纳秒数来暂停void interrupt()中断这个线程boolean isAilve()测试这个线程是否活着static void yield()暂停当前正在执行的线程对象,并执行其他线程
3.1 停止线程
不推荐使用JDK提供的stop()、destory()方法(@Deprecated(since="1.2"):已废弃)
推荐线程自己停止下来:将线程体写到循环内,利用次数终止循环,不建议死循环
建议用一个标志位进行终止变量。当flag=false,终止线程运行:通知方式
package com.ThreadCallable;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* @ClassName ThreadCallableDemo
* @Description TODO 线程创建方式三:实现Callable接口
* @Author Mark
* @Date 2022/6/23 22:06
* @Version 1.0
*/
//①实现Callable接口,需要返回值类型
public class ThreadCallableDemo implements Callable {
private String url;//网络地址
private String name;//保存的文件名
public ThreadCallableDemo(String url, String name) {
this.url = url;
this.name = name;
}
//②重写call方法,下载图片线程的执行体
@Override
public Boolean call() {
WebDownloader webDownloader = new WebDownloader();
webDownloader.downloader(url, name);
System.out.println("下载了的文件名为:" + name);
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//③创建目标对象
ThreadCallableDemo thread1 = new ThreadCallableDemo("https://pics0.baidu.com/feed/1b4c510fd9f9d72a803a1a357e5dae3e349bbb3a.jpeg", "fpx.jpeg");
ThreadCallableDemo thread2 = new ThreadCallableDemo("https://pics0.baidu.com/feed/0d338744ebf81a4c46cbca834bc6e653272da6f1.png", "mgn.jpeg");
ThreadCallableDemo thread3 = new ThreadCallableDemo("https://pic.rmb.bdstatic.com/bjh/down/e2cbad3b771358fec7de7727ca450426.png", "edg.png");
//④创建执行服务:
ExecutorService ser= Executors.newFixedThreadPool(1);
//⑤提交执行:
Future<Boolean> result1= ser.submit(thread1);
Future<Boolean> result2= ser.submit(thread2);
Future<Boolean> result3= ser.submit(thread3);
//⑥获取结果:
boolean rs1=result1.get();
boolean rs2=result2.get();
boolean rs3=result3.get();
//⑦关闭服务:
ser.shutdown();
}
}
//下载器
class WebDownloader {
//下载方法
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File("myThread\\src\\com\" + name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("IO异常,downloader方法出现异常");
}
}
}
复制代码
3.2 线程中断
public interface Runnable{
public abstract void run();
}
复制代码
3.3 线程休眠
sleep(时间)指定当前线程阻塞的毫秒数
sleep存在异常InterruptedException;
sleep时间达到后线程进入就绪状态
sleep可以模拟网络延迟、倒计时等
每个对象都有一个锁,sleep不会释放锁
[code]package com.ThreadStudy;import java.text.SimpleDateFormat;import java.util.Date;/** * @ClassName SleepThread2 * @Description TODO 倒计时+打印系统时间 * @Author Mark * @Date 2022/6/24 21:16 * @Version 1.0 */public class SleepThread2 { public static void main(String[] args) {// try {// timeDown();// } catch (InterruptedException e) {// e.printStackTrace();// } Date startTime =new Date(System.currentTimeMillis()); while(true){ try { System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime)); startTime =new Date(System.currentTimeMillis());//更新当前时间 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } //计时器// public static void timeDown() throws InterruptedException {// int num=10;// while(true){// Thread.sleep(1000);// System.out.println(num--);// if (num
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4