一.概述
线程简介
总而言之,就是在同一时间,做了不同的事情,正所谓一石二鸟,一箭双雕,赔了夫人又折兵
生活中很多事情都可以看作是多线程的例子。比如:
- 煮饭:煮饭需要同时加热米饭和煮菜,如果只有一个火力,就只能按照一定的顺序依次进行,而使用多个火力则可以同时加热多个部分,从而节省时间。
- 洗衣服:洗衣服需要同时洗涤多个衣物,如果只有一个洗衣机,就只能按照一定的顺序依次进行,而使用多个洗衣机则可以同时洗涤多个衣物,从而节省时间。
- 开车:开车需要同时控制加速、刹车、方向盘等多个部分,如果只有一个人,就只能按照一定的顺序依次进行,而使用多个人则可以同时控制多个部分,从而提高驾驶的安全性和效率。
然后,我们使用官方点的语言解释一下什么是多线程:
多线程是一种计算机程序设计技术,它可以同时执行多个线程(或者说子任务),以实现更高效的计算机程序。在单线程的计算机程序中,程序只能按照一定的顺序执行,而在多线程的程序中,多个线程可以在同一时间内并发执行,从而提高程序的并发性和执行效率。
举个例子,如果你正在使用电脑进行网页浏览,当你点击某个链接时,网页就会从服务器端获取相应的内容并在你的电脑上显示出来。如果网页的内容非常多,而你的电脑只有一个处理器,那么你就只能等待这些内容全部加载完成后才能浏览网页。但如果你使用的是多线程的浏览器,就可以同时加载多个网页的内容,从而提高浏览网页的速度。
多线程技术在现代计算机系统中非常重要,广泛应用于各种计算机程序中,如Web服务器、操作系统、数据库等等。它可以帮助计算机程序更好地利用多核CPU,提高程序的响应速度和并发能力。
总的来说,多线程技术可以帮助我们更好地利用多核CPU,提高程序的响应速度和并发能力,从而提高我们的生活效率。
普通方法调用和多线程对比
线程和进程的联系与区别
进程和线程都是操作系统中的基本概念,但它们有很不同的特点和用途。
相同点:
- 进程和线程都可以作为程序的执行单位;
- 一个进程可以包含多个线程;
- 进程和线程都具有并发性。
不同点:
- 进程是操作系统资源分配的基本单位,而线程是CPU调度的基本单位。进程拥有自己独立的地址空间、系统资源(如文件句柄、打开网络连接等)和内核对象(如进程句柄、信号处理函数等),一个进程无法访问另一个进程的资源,而线程则共享所属进程的资源,如打开的文件、上下文环境等;
- 创建或销毁进程时,操作系统需要完成大量的工作,比如分配和初始化各种数据结构、加载程序代码和数据等,这将花费较大的时间和系统开销。而创建或销毁线程时,操作系统只需实现简单的数据结构即可,工作量远远小于进程,因此更加轻量级;
- 进程之间不能直接通信,必须通过一些机制(如管道、消息队列、共享内存等)来进行通信,而线程间可以直接共享相同的内存空间,并通过读写同一变量等方式来进行通信,这种通信的效率更高;
- 进程启动的时间长,切换上下文所需的时间较多,线程则启动快、上下文切换快。
总结来说,进程和线程是操作系统中不同层级的并发调度单位,进程之间相互隔离,线程之间共享资源,根据具体应用场景选择合适的方案可提高程序性能。
线程实现
Java多线程有以下五种实现方法:
- 继承Thread类
继承Thread类是实现多线程最常用的方法。通过继承Thread类,可以重写run()方法,在run()方法中编写多线程的逻辑代码。如下所示:
public class MyThread extends Thread { public void run() { // 多线程的逻辑代码 } }
- 实现Runnable接口
实现Runnable接口也是实现多线程的一种方式。通过实现Runnable接口,可以创建多个实例对象,然后将它们作为参数传递给Thread类的构造方法中。如下所示:
public class MyRunnable implements Runnable { public void run() { // 多线程的逻辑代码 } } public class MyThread extends Thread { private MyRunnable mRunnable; public void setRunnable(MyRunnable mRunnable) { this.mRunnable = mRunnable; } @Override public void run() { mRunnable.run(); } }
- 实现Callable接口
实现Callable接口与实现Runnable接口类似,不同之处在于Callable接口可以返回一个结果。Callable接口可以用来执行一些需要返回结果的任务。如下所示:
public class MyCallable implements Callable<Integer> { public Integer call() throws Exception { // 多线程的逻辑代码,返回一个整数结果 return result; } } public class MyThread extends Thread { private MyCallable mCallable; public void setCallable(MyCallable mCallable) { this.mCallable = mCallable; } @Override public Integer call() throws Exception { return mCallable.call(); } }
- 实现Future接口
Future接口可以用来获取异步计算的结果。通过实现Future接口,可以在调用异步方法时获取到异步计算的结果。如下所示:
public class MyFuture implements Future<Integer> { private Integer result; public void setResult(Integer result) { this.result = result; } @Override public Integer get() throws Exception { // 在获取结果前可以进行一些操作,如等待一段时间等操作。这里直接返回计算结果。如果计算还没有完成,则会一直阻塞等待。如果计算已经完成,则会立即返回计算结果。如果出现异常,则会抛出异常。如下所示: while (!isDone()) {} // 这里是一个无限循环,直到isDone()返回true才跳出循环。isDone()表示异步计算是否已经完成。如果isDone()返回true,则说明异步计算已经完成。否则,在下一次循环中继续等待。这里使用了一个while循环来等待计算结果的返回。这种方式适用于不需要知道异步计算何时完成的情况。如果需要知道异步计算何时完成,可以使用FutureTask类来进行异步计算并获取异步计算的结果。如下所示:
在这里,我们再稍微多讲一点
Java多线程五种实现方法可以混合使用。
在实际开发中,我们可以根据需要选择不同的多线程实现方式。例如,如果我们需要执行一个异步计算的任务,可以使用Callable接口和Future接口来实现;如果我们需要执行一个有返回值的任务,可以使用Runnable接口和Future接口来实现;如果我们需要执行一个线程池中的任务,可以使用Thread类或Runnable接口来实现。
同时,我们也可以将多种多线程实现方式进行组合使用。例如,我们可以将Runnable接口和Callable接口进行组合使用,来实现一个既可以执行任务又可以返回结果的多线程程序。如下所示:
public class MyRunnable implements Runnable, Callable<Integer> { private Integer result; public void run() { // 多线程的逻辑代码 result = 123; // 这里可以设置异步计算的结果 } @Override public Integer call() throws Exception { // 在获取结果前可以进行一些操作,如等待一段时间等操作。这里直接返回计算结果。如果计算还没有完成,则会一直阻塞等待。如果计算已经完成,则会立即返回计算结果。如果出现异常,则会抛出异常。如下所示: while (!isDone()) {} // 这里是一个无限循环,直到isDone()返回true才跳出循环。isDone()表示异步计算是否已经完成。如果isDone()返回true,则说明异步计算已经完成。否则,在下一次循环中继续等待。这里使用了一个while循环来等待计算结果的返回。这种方式适用于不需要知道异步计算何时完成的情况。如果需要知道异步计算何时完成,可以使用FutureTask类来进行异步计算并获取异步计算的结果。如下所示: while (!isDone()) {} // 这里是一个无限循环,直到isDone()返回true才跳出循环。isDone()表示异步计算是否已经完成。如果isDone()返回true,则说明异数
线程状态
常见的线程状态包括:
- 新建(New):当一个线程被创建但还没有开始运行时,它的状态为新建状态。
- 就绪(Runnable):当一个线程已经创建,且它的所有前置条件都已完成,它等待CPU时间片分配以执行任务时,它的状态为就绪状态。
- 运行(Running):当一个线程得到了处理器分配时间并正在执行任务时,它的状态为运行状态。
- 阻塞(Blocked):当一个线程由于某些原因无法获得所需资源而阻塞时,它的状态为阻塞状态。这可能是因为它正在等待磁盘或网络I / O操作、等待锁或等待其他线程完成等。
- 等待(Waiting):当一个线程通过调用wait()方法进入无限期等待状态时,它的状态为等待状态,这通常是在等待另一个线程通知所等待的条件已被满足。
- 超时等待(Timed Waiting):当一个线程通过调用带有超时参数的sleep()、wait()或join()方法等待一段时间后进入此状态,它的状态为超时等待状态。
- 终止(Terminated):当一个线程已经完成它的任务并退出运行时,或者因未处理异常而突然退出运行时,它的状态为终止状态。
这些不同的线程状态可以在多线程应用程序中解决并发问题。了解线程的状态和各种状态之间的转换是编写高效且正确的多线程代码的关键。
现场同步
线程现场同步是指在多线程编程中,为了保证多个线程访问共享资源时的正确性,需要对这些资源进行加锁和解锁的操作。
当多个线程同时访问同一个共享资源时,如果没有进行同步处理,就可能出现数据不一致、死锁等问题。因此,在多线程编程中,需要使用一些同步机制来控制线程的并发访问。
常见的线程同步机制包括:
互斥锁(Mutex):用于保护临界区,同一时间只能有一个线程进入该区域。
读写锁(Read-Write Lock):允许多个线程同时读取共享资源,但只允许一个线程写入共享资源。
信号量(Semaphore):用于限制同时访问某个资源的线程数量。
原子操作(Atomic Operations):可以保证多个线程之间的数据操作是原子性的,从而避免了数据竞争的问题。
通过使用适当的线程同步机制,可以有效地避免多线程编程中的数据竞争和死锁等问题,提高程序的可靠性和稳定性。
线程通信问题
线程通信问题是指在多线程编程中,不同线程之间如何进行通信和协调。由于多个线程可以同时运行,因此如果没有进行适当的同步和互斥操作,就可能出现数据竞争、死锁等问题。
以下是一些常见的线程通信问题:
竞态条件(Race Condition):当多个线程同时访问共享资源时,如果没有进行同步控制,就可能出现竞态条件。例如,两个线程同时对一个计数器进行递增操作,就可能导致计数器的值不正确。
死锁(Deadlock):当多个线程相互等待对方释放资源时,就可能出现死锁。例如,线程A持有资源R1并等待获取资源R2,同时线程B持有资源R2并等待获取资源R1,就会出现死锁。
饥饿(Starvation):当某个线程一直被其他线程抢占资源而无法获得足够的资源时,就可能出现饥饿。例如,线程A持有资源R1并等待获取资源R2,但同时线程B持有资源R2并一直占用,导致线程A一直无法获取资源R2。
为了解决线程通信问题,可以使用一些同步机制和工具,如互斥锁、读写锁、信号量、原子操作等。此外,还需要注意避免循环依赖、共享数据结构等问题,以确保程序的正确性和稳定性。
高级主题
线程池:线程池是一种管理和重用线程的机制。它可以提高程序的性能和稳定性,因为线程池可以控制并发线程的数量,避免线程过多导致系统资源耗尽的问题。
并发集合类:Java提供了一些并发集合类,如ConcurrentHashMap、CopyOnWriteArrayList等,它们可以在多线程环境下安全地进行操作。这些集合类的设计考虑了线程安全和性能之间的平衡。
原子操作:原子操作是一种不可中断的操作,它可以保证多个线程之间的数据操作是原子性的,从而避免了数据竞争的问题。Java提供了一些原子操作类,如AtomicInteger、AtomicLong等。
线程调度:线程调度是指操作系统如何选择和管理线程的执行时间。Java使用了一个优先级队列来管理线程的执行时间,称为调度器(Scheduler)。通过调整线程的优先级和调度策略,可以优化程序的性能和响应速度。
线程安全的容器和算法:在多线程环境下,使用不安全的容器或算法可能导致数据竞争和死锁等问题。因此,需要使用线程安全的容器和算法来保证程序的正确性和稳定性。例如,ConcurrentHashMap、CopyOnWriteArrayList、CountDownLatch等。
如上,就是对于线程的一些概念性描述和解释,我们一起作为了解,下来,我们将会进行展开详细描述!!!
二. 线程创建
在这里,我们需要着重注意一下Runable接口
继承Thread创建线程
创建线程方式一:继承Thread类,重写run()方法,调用开启start开启线程
基础案例一:
package com.success.day06;
public class TestThread1 extends Thread {
/**
* @Description 重写run方法
* @Author IT小辉同学
* @Date 2023/05/24
*/
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("上天不负有心——————" + i);
}
}
/**
* @Description main主线程
* @Author IT小辉同学
* @Date 2023/05/24
*/
public static void main(String[] args) {
//创建一个线程对象
TestThread1 thread1 = new TestThread1();
//调用start()开启线程
thread1.start();
for (int i = 0; i < 2000; i++) {
System.out.println("我们都是追梦人——————" + i);
}
}
}
基础案例二:
package com.success.day06;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @Description 多线程下载网络图片
* @Author IT小辉同学
* @Date 2023/05/24
*/
public class TestThread2 extends Thread {
//图片链接地址
private String imgUrl;
//文件重命名
private String imgName;
public TestThread2(String imgUrl, String imgName) {
this.imgUrl = imgUrl;
this.imgName = imgName;
}
/**
* @Description 重写run方法,添加下载任务
* @Author IT小辉同学
* @Date 2023/05/24
*/
@Override
public void run() {
ImgDownloader downloader = new ImgDownloader();
downloader.downloader(imgUrl, imgName);
System.out.println("成功下载了文件:" + imgName);
}
/**
* @param args
* @Description 创建三个线程,并发
* @Author IT小辉同学
* @Date 2023/05/24
*/
public static void main(String[] args) {
TestThread2 t1 = new TestThread2("https://pic.netbian.com/uploads/allimg/161003/105551-14754633518f18.jpg", "哆啦A梦1.png");
TestThread2 t2 = new TestThread2("https://pic.netbian.com/uploads/allimg/161003/104615-1475462775c01d.jpg", "哆啦A梦2.png");
TestThread2 t3 = new TestThread2("https://pic.netbian.com/uploads/allimg/161003/104021-14754624214a25.jpg", "哆啦A梦3.png");
t1.start();
t2.start();
t3.start();
}
}
/**
* @Description 图片下载器
* @Author IT小辉同学
* @Date 2023/05/24
*/
class ImgDownloader {
/**
* @param url 链接
* @param name 名字
* @Description 图片下载方法
* @Author IT小辉同学
* @Date 2023/05/24
*/
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件下载异常!!!");
}
}
}
注:此处引入了一个依赖包
<!-- https://mvnrepository.com/artifact/commons-io/commons-io -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
通过运行实例我们发现,并不是按照1,2,3的顺序去下载蓝胖子,而是随机的,可见这是一个多线程并发实例!
最后,对于代码结构进行逐行剖析
- 定义了一个名为TestThread2的类,继承自Thread类。
- 在类中定义了两个成员变量:imgUrl表示图片链接地址,imgName表示文件重命名。
- 提供了一个构造方法,接收imgUrl和imgName参数。
- 在run方法中创建了一个ImgDownloader类的实例downloader。
- 在downloader方法中调用FileUtils的copyURLToFile方法下载网络图片到本地文件中,并打印出成功下载的信息。
- main方法中创建了三个TestThread2对象,分别传入不同的url和filename参数,然后调用start方法启动线程。
实现Runnable接口创建线程
基础案例一:
package com.success.day06;
/**
* @Description 实现Runnable接口,重写 run 方法
* @Author IT小辉同学
* @Date 2023/05/24
*/
public class TestRunnable1 implements Runnable {
/**
* @Description 重写run方法
* @Author IT小辉同学
* @Date 2023/05/24
*/
@Override
public void run() {
// run方法线程体
for (int i = 0; i < 20; i++) {
System.out.println("上天不负有心——————" + i);
}
}
/**
* @Description main主线程
* @Author IT小辉同学
* @Date 2023/05/24
*/
public static void main(String[] args) {
//创建实现Runnable接口的实现类对象
TestRunnable1 testRunnable1 = new TestRunnable1();
//创建一个线程对象,代理
Thread thread = new Thread(testRunnable1);
//调用start()开启线程
thread.start();
//如上两句可精简为
//new Thread((testRunnable1)).start();
for (int i = 0; i < 2000; i++) {
System.out.println("我们都是追梦人——————" + i);
}
}
}
基础案例二:
package com.success.day06;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
/**
* @Description 多线程下载网络图片
* @Author IT小辉同学
* @Date 2023/05/24
*/
public class TestRunnable2 implements Runnable {
//图片链接地址
private String imgUrl;
//文件重命名
private String imgName;
public TestRunnable2(String imgUrl, String imgName) {
this.imgUrl = imgUrl;
this.imgName = imgName;
}
/**
* @Description 重写run过程,添加下载任务
* @Author IT小辉同学
* @Date 2023/05/24
*/
@Override
public void run() {
ImgDownloader downloader = new ImgDownloader();
downloader.downloader(imgUrl, imgName);
System.out.println("成功下载了文件:" + imgName);
}
/**
* @param args
* @Description 创建三个线程,并发
* @Author IT小辉同学
* @Date 2023/05/24
*/
public static void main(String[] args) {
TestRunnable2 t1 = new TestRunnable2("https://pic.netbian.com/uploads/allimg/161003/105551-14754633518f18.jpg", "哆啦A梦1.png");
TestRunnable2 t2 = new TestRunnable2("https://pic.netbian.com/uploads/allimg/161003/104615-1475462775c01d.jpg", "哆啦A梦2.png");
TestRunnable2 t3 = new TestRunnable2("https://pic.netbian.com/uploads/allimg/161003/104021-14754624214a25.jpg", "哆啦A梦3.png");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
}
}
/**
* @Description 图片下载器
* @Author IT小辉同学
* @Date 2023/05/24
*/
class ImgDownloader {
/**
* @param url 链接
* @param name 名字
* @Description 图片下载方法
* @Author IT小辉同学
* @Date 2023/05/24
*/
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件下载异常!!!");
}
}
}
注意:与第一种创建线程的方法进行对比,总结,得出自己的结论!
提升案例一:
package com.success.day06;
/**
* @Description 模拟多线程并发火车票售票
* @Author IT小辉同学
* @Date 2023/05/24
*/
public class TestRunnable3 implements Runnable{
//火车票总数
private int ticketNums=20;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
//模拟延时
try {
Thread.sleep(200);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(Thread.currentThread().getName()+"获得第"+ticketNums--+"张火车票!!!");
}
}
public static void main(String[] args) {
TestRunnable3 ticket=new TestRunnable3();
new Thread(ticket,"IT小辉同学001").start();
new Thread(ticket,"IT小辉同学002").start();
new Thread(ticket,"IT小辉同学003").start();
new Thread(ticket,"IT小辉同学004").start();
new Thread(ticket,"IT小辉同学005").start();
new Thread(ticket,"IT小辉同学006").start();
}
}
这里我们就会发现多线程高并发产生了线程安全问题,后面我们解决这个问题,这里主要演示一下Runnable接口的线程实现方法!!!
提升案例二:
package com.success.day06;
/**
* @Description 模拟龟兔赛跑
* @Author IT小辉同学
* @Date 2023/05/24
*/
public class TestRunnable4 implements Runnable{
private static String winner;
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {
//模拟兔子睡觉
if (Thread.currentThread().getName().equals("兔子")&&i%20==0){
try {
//睡眠时间根据自己笔记本性能自定义
Thread.sleep(1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
//判断比赛是否结束,结束停止比赛
boolean flag=RaceGame(i);
if (flag){
break;
}
System.out.println(Thread.currentThread().getName()+"——跑了"+i+"步!!!");
}
}
/**
* @param steps 步数
* @return boolean
* @Description 游戏规则
* @Author IT小辉同学
* @Date 2023/05/24
*/
private boolean RaceGame(int steps){
//判断是否有胜利者
if (winner!=null){
return true;
}else {
if (steps>=100){
winner=Thread.currentThread().getName();
System.out.println(winner+"胜出!!!");
return true;
}
}
return false;
}
public static void main(String[] args) {
TestRunnable4 race=new TestRunnable4();
new Thread(race,"兔子").start();
new Thread(race,"乌龟").start();
}
}
实现Callable接口创建线程
package com.success.day07;
import org.apache.commons.io.FileUtils;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.*;
/**
* @Description 多线程下载网络图片
* @Author IT小辉同学
* @Date 2023/05/24
*/
public class TestCallable1 implements Callable<Boolean> {
//图片链接地址
private String imgUrl;
//文件重命名
private String imgName;
public TestCallable1(String imgUrl, String imgName) {
this.imgUrl = imgUrl;
this.imgName = imgName;
}
/**
* @Description 重写run过程,添加下载任务
* @Author IT小辉同学
* @Date 2023/05/24
*/
@Override
public Boolean call() {
ImgDownloader downloader = new ImgDownloader();
downloader.downloader(imgUrl, imgName);
System.out.println("成功下载了文件:" + imgName);
return true;
}
/**
* @param args
* @Description 创建三个线程,并发
* @Author IT小辉同学
* @Date 2023/05/24
*/
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable1 t1 = new TestCallable1("https://pic.netbian.com/uploads/allimg/161003/105551-14754633518f18.jpg", "哆啦A梦1.png");
TestCallable1 t2 = new TestCallable1("https://pic.netbian.com/uploads/allimg/161003/104615-1475462775c01d.jpg", "哆啦A梦2.png");
TestCallable1 t3 = new TestCallable1("https://pic.netbian.com/uploads/allimg/161003/104021-14754624214a25.jpg", "哆啦A梦3.png");
//创建服务(3个线程池)
ExecutorService service = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> r1 = service.submit(t1);
Future<Boolean> r2 = service.submit(t2);
Future<Boolean> r3 = service.submit(t3);
//获取结果
boolean rs1 = r1.get();
boolean rs2 = r2.get();
boolean rs3 = r3.get();
//关闭服务
service.shutdownNow();
}
}
/**
* @Description 图片下载器
* @Author IT小辉同学
* @Date 2023/05/24
*/
class ImgDownloader {
/**
* @param url 链接
* @param name 名字
* @Description 图片下载方法
* @Author IT小辉同学
* @Date 2023/05/24
*/
public void downloader(String url, String name) {
try {
FileUtils.copyURLToFile(new URL(url), new File(name));
} catch (IOException e) {
e.printStackTrace();
System.out.println("文件下载异常!!!");
}
}
}
持续更新中。。。。。。