⭐写在前面⭐
🧭Java 多线程
📢今天我们进行 Java 多线程介绍及线程创建 的学习,感谢你的阅读,内容若有不当之处,希望大家多多指正,一起进步!!!
♨️如果觉得博主文章还不错,可以👍三连支持⭐一下哦😀
文章目录
Java 多线程介绍及线程创建
线程相关概念
程序
程序
是为完成特定任务、用某种语言编写的一组指令的集合。简单的来说,就是我们写的代码。
进程
进程
是程序的一次执行过程,或是正在运行的一个程序,是一个 动态 的过程。比如我们启动QQ,就启动了一个进程,操作系统就会为该进程分配内存空间和系统资源,再次启动谷歌浏览器,就又启动一个进程,操作系统也将会为其分配内存空间和系统资源。我们可以通过任务管理器来查看和管理进程。
线程
线程
是由进程创建的,是进程的一个实体,可以理解为线程是进程的一条执行路径,一个进程可以拥有多个线程。比如我们使用QQ,可以向好友发消息,同时也可以接收消息,再同时还可以视频语音聊天,这都是线程帮我们完成的。
并发
并发
:同一时刻,多个任务交替执行,造成一种“貌似同时执行”的错觉,在早期的计算机系统中,CPU是单核的,在同一时刻只能执行一个进程,但是我们也可以一边聊天🐧一边🌨️网易云🎶,这就是CPU轮流为进程服务的结果,只不过CPU切换进程速度特别快,我们察觉不到而已!!
并行
并行
:同一个时刻,多个任务同时执行,多核CPU可以实现同时执行多个任务。
获取当前电脑CPU核数
调用Runtime
类下的availableProcessors
方法可以获取当前电脑CPU核数。
public class CpuNumber {
public static void main(String[] args) {
Runtime runtime = Runtime.getRuntime();
int i = runtime.availableProcessors();
System.out.println("当前电脑CPU核数为:" + i);
}
}
运行结果:
创建线程
在Java中创建线程有三种方式:
● 继承Thread类
● 实现Runnable接口
● 实现Callable接口
继承Thread类
Thread类
class Thread implements Runnable
Thread
类是Runnable
接口的一个实现类,它重写了Runnable接口的Run方法,一个类可以继承Thread
类重写其 Run 方法来创建一个线程。
案例: 编写代码,用继承Thread类的方式创建一个线程,每隔1s打印一个"a"及其线程名,共打印5次,同时显示第几次打印。在主线程中每隔1.5s打印一个"主线程"及其线程名,共打印3次,同时显示第几次打印。
public class TestThread01 {
public static void main(String[] args) {
A a = new A();
a.start();//开启线程
//打印主线程
int count = 0;
while (true) {
System.out.println("主线程" + Thread.currentThread().getName() + "第" + ++count + "次");
if (count == 3) {
break;
}
try {
Thread.sleep(1500); //休眠1.5秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class A extends Thread{
@Override
public void run() {
int count = 0;
while (true) {
System.out.println("a " + Thread.currentThread().getName() + "第" + ++count + "次");
if (count == 5) {//打印5次退出
break;
}
try {
Thread.sleep(1000);//休眠1秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
主线程和Thread-0线程交替执行。在主方法中,并没有因为Thread-0线程的执行而阻塞,此时两个线程在并发执行。
start方法
在上述示例中,我们并没有在主方法中调用A类中的Run方法,那么是如何启动一个线程的呢?
事实上,我们如果直接调用Run方法的话,并不会启动一个线程,只是调用了一个方法而已,当然也不会出现交替执行的现象,程序会阻塞那里,此时只有一个主线程,程序会顺序向下执行,图示如下:
那么问题来了,我们如何是通过start
方法来启动线程的呢?
查看源码:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
private native void start0();
通过源码发现,我们发现在start
方法中会调用一个start0
方法,而这个start0
方法是一个native
修饰的本地方法,其底层是用C++实现的,所以run
方法是底层帮我们调用的,归根其底在Java中创建一个线程还是通过底层实现的。
实现Runnable接口
Runnable接口
public interface Runnable {
public abstract void run();
}
Runnable
接口的一个实现类重写其Run
方法可以创建一个线程。
案例: 编写代码,用实现Runnable接口的的方式创建一个线程,每隔1s打印一个"b"及其线程名,共打印5次,同时显示第几次打印。在主线程中每隔1.5s打印一个"主线程"及其线程名,共打印3次,同时显示第几次打印。
public class TestThread02 {
public static void main(String[] args) {
B b = new B();
Thread thread = new Thread(b);
thread.start();//开启一个线程
//主线程打印
int count = 0;
while (true) {
System.out.println("主线程" + Thread.currentThread().getName() + "第" + ++count + "次");
if (count == 3) {//打印3次退出
break;
}
try {
Thread.sleep(1500); //休眠1.5秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class B implements Runnable {
@Override
public void run() {
int count = 0;
while (true) {
System.out.println("b " + Thread.currentThread().getName() + "第" + ++count + "次");
if (count == 5) {//打印5次退出
break;
}
try {
Thread.sleep(1000);//休眠1秒中
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
运行结果:
🚀解读
在主方法中,我们是通过把B
类的一个实例交给了Thread
的一个实例,然后调用start
方法来启动一个线程的。那么为什么要交给Thread
类的实例呢?其实在这里Thread
类充当了一个代理类的身份。
查看源码
Thread类
public class Thread implements Runnable {
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
通过源码我们发现,Thread
类中的Run
方法中有一个target
对象,而这个target
对象其实就是我们在创建Thread
实例时传入的对象。当Thread
调用start
方法后,会在start
方法中调用本地方法start0
,然后会在底层调用其run
方法,最后在run
方法中会通过动态绑定调用run
方法执行业务逻辑。
实现Callable接口
Callable接口
在Callable
接口中提供了一个call
方法,该方法会抛出一个异常,该接口通过泛型来定义。
public interface Callable<V> {
V call() throws Exception;
}
该接口的实现类是无法直接使用,需要借助于FutureTask
类,该类能接收Callable
接口的实现类。
public class FutureTask<V> implements RunnableFuture<V> {
public FutureTask(Callable<V> callable) {
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
}
案例: 编写代码,用实现Callable接口的的方式创建一个线程,每隔1s打印一个"c"及其线程名,共打印5次,同时显示第几次打印。在主线程中每隔1.5s打印一个"主线程"及其线程名,共打印3次,同时显示第几次打印。
import java.io.Flushable;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class TestThread03 {
public static void main(String[] args) {
C c = new C();
FutureTask<String> futureTask = new FutureTask<String>(c);
Thread thread = new Thread(futureTask);
thread.start();//开启线程
//打印主线程
int count = 0;
while (true) {
System.out.println("主线程" + Thread.currentThread().getName() + "第" + ++count + "次");
if (count == 3) {
break;
}
try {
Thread.sleep(1500); //休眠1.5秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class C implements Callable {
@Override
public Object call() throws Exception {
int count = 0;
while (true) {
System.out.println("c " + Thread.currentThread().getName() + "第" + ++count + "次");
if (count == 5) {//打印5次退出
break;
}
try {
Thread.sleep(1000);//休眠1秒中
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return null;
}
}
运行结果: