java多线程之简单介绍
2021/10/12 1:14:10
本文主要是介绍java多线程之简单介绍,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
多线程
1、简单介绍:
为什么需要多线程?
在早期的设计当中,是没有多线程的存在的。存在的就只有进程。对于一个程序来说,执行顺序就只是从上而下,依次进行执行的。
但是在这个过程中,本来程序中很多不相干的事情杂糅在一起,导致了CPU的利用率没有充分发挥。
所以为了充分压榨CPU,也为了程序运行的更加快速,从而出现了多线程。原来一个人需要做各种各样的事情来完成一个目标,现在是一群人一起互相帮助来达到相同的目标。
操作系统中的线程和java中的线程是极其类似的,只不过java在设计的时候,和操作系统中的线程有点区别。之后会来进行详细的说明和介绍
2、多线程
java提供了Thread类的API来操作线程,通过这个类可以获取得到多线程的各种信息。
先写代码,再一个一个慢慢解释:
2.1、创建线程
创建线程通常来说是有两种方式:1、实现Thread类;2、继承Runnale接口,实际上Thread类也是实现了Runnable接口。
所以总的来说,实现线程的方式最终也还是只有一种方式来进行实现:
@Slf4j public class CreateThreadDemoOne { public static void main(String[] args) { // 这里是主线程的位置 log.info("线程种执行的代码------------->我的线程名字是---->{}",Thread.currentThread().getName()); // 创建线程并执行 MyThread thread = new MyThread(); new Thread(thread).start(); } } /** * 通过继承的方式来实现我们自己的线程类 */ @Slf4j class MyThread extends Thread { /** * 一定要重写这个方法 */ @Override public void run() { log.info("线程种执行的代码------------->我的线程名字是---->{}",Thread.currentThread().getName()); } }
为什么说要重写run方法?这个在之后会来进行详细的说明。
通过实现Runnable接口来操作:
@Slf4j public class ThreadDemoOne { public static void main(String[] args) { // 这里是主线程的位置 log.info("线程种执行的代码------------->我的线程名字是---->{}",Thread.currentThread().getName()); // 创建线程并执行 MyThread thread = new MyThread(); new Thread(thread).start(); // 主线程睡一会儿 try { Thread.sleep(1000); log.info("我是{},睡醒了",Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } @Slf4j class MyThread implements Runnable{ @Override public void run() { log.info("I can run"); } }
通过观察,发现上面的代码是及其相似的,那么我们两种通过都会来使用。
但是在jdk8及其之后,更喜欢使用接口来实现,因为可以使用lambda的方式来快速实现一个线程的调用:
@Slf4j public class ThreadDemoOne { public static void main(String[] args) { log.info("当前线程是:{}",Thread.currentThread().getName()); new Thread(()->{ log.info("I can run"); }).start(); } }
在上面中创建的两个线程对象,最后都会传给Thread的构造中去,然后调用start方法;但是从上面可以看到,自定义的线程类实现或者是继承了接口,那么都重写了run方法,为什么不调用对象直接调用run方法而是调用start方法呢?看下代码,然后走下源码:
// 创建线程并执行 MyThread thread = new MyThread(); new Thread(thread).start();
从构造方法中开始进入,点击init方法,最终会走到下面的源码中来:
// 一定要看注释!不看注释就不知道干嘛的 /** * Initializes a Thread.功能介绍:创建一个线程 * 参数介绍 * @param g the Thread group * @param target the object whose run() method gets called 这个才是重点参数目标对象调用run方法,找到这个参数调用的地方即可 * @param name the name of the new Thread * @param stackSize the desired stack size for the new thread, or * zero to indicate that this parameter is to be ignored. * @param acc the AccessControlContext to inherit, or * AccessController.getContext() if null * @param inheritThreadLocals if {@code true}, inherit initial values for * inheritable thread-locals from the constructing thread */ // 现在其他的参数不跟!直接跟踪target private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc, boolean inheritThreadLocals) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name; Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it's an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /* If the security doesn't have a strong opinion of the matter use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); // 跟踪到了这里,发现只是给成员变量去进行赋值而已!但是上面说了,target变量会调用run方法! // 刚刚在进行继承或者是实现的时候,又重写了run方法,根据多态的性质,最终会调用自己实现的run方法 this.target = target; setPriority(priority); if (inheritThreadLocals && parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); }
// 上面的重点参数的注释!可以看到我们为什么要实现Runnable接口了,这里利用了多态 // 这里也是为何说要么是通过继承Thread(实现了Runnable),或者是实现Runnable接口 /* What will be run. */ private Runnable target;
// 导致这个线程开始运行,java虚拟机将会调用这个线程的run方法 // 也就是说我们创建的线程在调用start方法之后,会调用重写的run方法 /** * Causes this thread to begin execution; the Java Virtual Machine * calls the <code>run</code> method of this thread. * <p> * The result is that two threads are running concurrently: the * current thread (which returns from the call to the * <code>start</code> method) and the other thread (which executes its * <code>run</code> method). // 看看上面的介绍:结果就是将会调用两个线程来执行,一个调用start方法,另外一个线程调用run方法 * <p> * It is never legal to start a thread more than once. 通常来说,一个线程启动多次是合法的,在执行完成之后不会再次执行啦 * In particular, a thread may not be restarted once it has completed * execution. * * @exception IllegalThreadStateException if the thread was already * started. * @see #run() * @see #stop() */ public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented. */ group.add(this); boolean started = false; try { // 调用了本地方法 start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
private native void start0();
本地方法,看不到源码了。
// 创建线程并执行 MyThread thread = new MyThread(); new Thread(thread).start();
这段代码背后的原理:在自定义了线程类对象之后,重写了run方法,传递给Thread构造之后,调用了start方法后。首先保存当前的线程的引用,然后在调用start方法的之后,JVM将会调用当前线程(我们自定义)引用的run方法。
那么这里就是为何我们自定义的线程不去调用run方法来进行执行了。对象执行自己重写的run方法,最终的效果就是一个普通对象调用普通方法执行,而看不到线程的效果。所以线程的创建是由JVM通过系统调用向操作系统来实现的。
在操作系统章节里介绍了java使用的线程模型是KLT模型。
3、对于任务的理解
重写的run方法,可以理解成是我们程序对应的线程所要去做的事情。那么参考着之前只是在单线程中写的代码,也就是在主线程中写的代码。在main线程(没有写多线程的时候)的执行,在main线程种执行的代码就是main线程中在做一件事情。
对于多线程来说,也就是多个线程之间各自做各自需要来做的事情。多线程之间因为存在于同一个进程之中(在一个进程中开辟了多个线程),因为进程是资源分配的单位,而线程是处于进程之间的,还是CPU调度的最小执行单元。所以多线程之间会存在着协调关系、竞争关系等等问题。
4、线程之间的运行关系图
所以我们可以看到其他线程一定是在主线程中创建出来的。
5、守护式线程和非守护式线程
java中的线程分为了守护式线程和非守护式线程。
所谓守护线程是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。因 此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。反过来说,只要任何非守护线程还在运行,程序就不会终止。
守护线程和用户线程的没啥本质的区别:唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了。
将线程转换为守护线程可以通过调用Thread对象的setDaemon(true)方法来实现。在使用守护线程时需要注意以下几点:
(1) thread.setDaemon(true)必须在thread.start()之前设置,否则会跑出一个IllegalThreadStateException异常。你不能把正在运行的常规线程设置为守护线程。
(2) 在Daemon线程中产生的新线程也是Daemon的。
(3) 守护线程应该永远不去访问固有资源,如文件、数据库,因为它会在任何时候甚至在一个操作的中间发生中断。
所以我们创建的线程中,在线程启动的时候没有对其进行设置setDaemon(true),说明了我们设置的都是非守护式线程。
只有当所有的非守护式线程执行完成之后,JVM才会退出;没有执行完成,JVM就会继续执行,一直等到非守护式线程执行完成。
这篇关于java多线程之简单介绍的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-10-01基于Python+Vue开发的医院门诊预约挂号系统
- 2024-10-01基于Python+Vue开发的旅游景区管理系统
- 2024-10-01RestfulAPI入门指南:打造简单易懂的API接口
- 2024-10-01初学者指南:了解和使用Server Action
- 2024-10-01Server Component入门指南:搭建与配置详解
- 2024-10-01React 中使用 useRequest 实现数据请求
- 2024-10-01使用 golang 将ETH账户的资产平均分散到其他账户
- 2024-10-01JWT用户校验课程:从入门到实践
- 2024-10-01Server Component课程入门指南
- 2024-09-30Dnd-Kit学习:新手快速入门指南