Java基础之多线程没那么复杂!
多线程的引入
1.什么是多线程
线程是程序执行的一条路径,一个进程中可以包含多条线程;多线程并发执行可以提高程序的效率
</br>
2.进程和线程之间的关系
操作系统可以同时执行多个任务,每个任务就是进程;进程可以同时执行多个任务,每个任务就是线程。
<br>
3.多线程的应用场景
红蜘蛛同时共享屏幕给多个电脑
迅雷开启多条线程一起下载
QQ开启多人聊天
服务器同时处理多个客户的请求
<br>
多线程并行和并发的区别
- 并行性和并发性是两个概念,并行性指在同一时刻,有多条指令在多个处理器上同时执行<br>
- 并发性指的是同一时刻只有一条指令被执行,但多个进程指令被快速切换执行是的在宏观上具有多个进程被同时执行的效果
<br>
Java程序运行原理和JVM的启动是多线程的吗?
- java程序运行原理
- Java命令启动jvm,启动jvm等于启动一个应用程序,也就是启动了一个进程,该进程会自动启动一个“主线程”,然后主线程去调用某个类的main方法
- Jvm启动是多线程的的吗
- JVM启动至少启动了垃圾回收线程和主线程,所以是多线程
<br>
多线程的实现方式
1. 继承Thread类创建线程类
(1) 定义Thread的子类,并重写该类的run方法,该ru的执行体就代表了线程需要完成的任务,因此run()方法被称为线程执行体
(2) 创建Thread子类的实例,即创建了线程对象<br>
(3) 调用线程对象的start()方法启动该线程
package Demo;
public class Demo1__Thread {
public static void main(String[] args) {
ChThread t=new ChThread();
t.start();
for(int i=0;i<1000;i++) {
System.out.println("我是主方法!");
}
}
}
class ChThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<1000;i++) {
System.out.println("我是run方法");
}
}
}
上述代码的执行验证了多线程,如果上述程序的执行过程是多线程的话,会发现屏幕中的 ”我是主方法”和“我是run方法” 的字样是交替出现的,这说明了程序的的执行过程为并行执行Thread类的Start()方法启动run()方法的线程,和主方法中的执行同时进行。
<br>
2.实现Runnable接口创建线程类
(1) 定义一个实现了Runnable接口的实现类<br>
(2) 创建Runnable实现类的实例<br>
(3) 将创建的实例作为Thread类的target类创建Thread对象,该对象才是真正的线程对象 <br>
(4) 用创建的Thread对象启动线程
package xianchenhg;
public class Thread_Running implements Runnable {
private int i;
public void run() {
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new Thread(new Thread_Running(),"线程1").start();
new Thread(new Thread_Running(),"线程2").start();
}
}
}
}
查看API文档,会发现Runnable接口只定义了run()方法这一个抽象类,所以实现Runnable接口的实现类只有run()方法,仅作为线程执行体,所以,Runnable对象仅仅作为Thread对象的target,而实际的线程对象依然是Thread实例,Thread实例负责执行target的run()方法。
3.实现Callable接口和Future接口创建多线程
(1)Callable接口更像是Runnable接口的增强版,相比较Runable接口,Call()方法新增捕获和抛出异常的功能;Call()方法可以返回值<br>
(2)Future接口提供了一个实现类FutureTask实现类,FutureTaks类用来保存Call()方法的返回值,并作为Thread类的target。<br>
(3)调用FutureTask的get()方法来获取返回值
package Demo;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* (1)创建Callable接口的实现类,并重写call()方法,该call()方法作为线程的执行体,且有返回值
* (2)创建了Callable接口的实现类的实例,并用FutureTask()方法包装对象,该FutureTask()对象实现了
* 将对象的返回值包装的功能
* (3)使用FutureTask对象将Thread对象的target,创建并启动线程
* (4)调用FutureTask对象的get()方法获得子线程执行结束后的返回值
* */
public class Callable_Future implements Callable<Integer> {
@Override
public Integer call() throws Exception { //重写Callable接口中的call()方法
int i=0;
for(;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
return i;
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建Callable的对象
Callable_Future ca=new Callable_Future();
FutureTask<Integer> ft=new FutureTask<Integer>(ca);
for(int i=0;i<100;i++) {
//返回值主线程的名称和执行代号
System.out.println(Thread.currentThread().getName()+" "+i);
if(i==20) {
new Thread(ft,"Callable线程").start();
//该方法将导致主线程被阻塞,直到call()方法结束并返回为止
//System.out.println("子线程的返回值"+ft.get());
}
}
try{
System.out.println("子线程的返回值"+ft.get());
}catch (Exception e) {
e.printStackTrace();
}
}
}
上面的程序中,FutufeTask方法的get()方法将获得Call()方法的返回值,但是该方法将导致主线程受阻直到Call()方法结束并返回为止。
4.三种实现多线程的方式对比
| 实现方式 | 重写方法 | 启动线程的方式 | 优点 | 缺点 |
|---|---|---|---|---|
| 继承Thread类 | run() | Thread对象.start() | 编程简单,直接用this即可获取当前对象 | 继承Thread的子类不能继承其他的类 |
| 实现Runnable接口 | run() | Thread对象.start() | 1.实现类可以继承其他类<br> 2.多个对象共享一个target,形成了清晰的模型 | 编程复杂,必须使用Thread.currentThread()返回当前对象 |
| 实现Callable接口 | Call() | Thread对象.start() | 1.同Runnable接口<br>2.Call()方法有返回值并能抛出异常 | 同Runnable接口 |
通过上面的对比发现,一般在项目中,我们使用Runnable接或者Callable接口来实现多线程。
5.线程的生命周期
- 线程并不是创建过之后就开始执行的,也不是一直处于执行状态,一个线程的生命周期一个又五个:新建、就绪、运行、阻塞、死亡。<br>2.线程的这五种状态的原理参照《计算机组成原理2》多线程部分
6.控制线程
1.join线程
1.Thread类中的成员方法,让调用join()方法的线程处于等待状态,直到子线程执行结束<br>2.join()方法通常由使用线程的程序调用,用于将一个线程拆分成若干小的线程执行,执行结束最后由主线程进行进一步的操作
代码演示:
public class joinThread extends Thread {
//创建一个有参构造函数,用来为线程命名
public joinThread(String str) {
super(str);
}
//重写run方法
public void run() {
for(int i=1;i<100;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args)throws Exception {
//启动子线程
new joinThread("子线程").start();
for(int i=0;i<100;i++) {
if(i==20) {
joinThread jt=new joinThread("新线程的子线程");
jt.start(); //启动子线程
jt.join(); //让主线程处于等待状态
}
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
mian线程调用join之后,出去阻塞状态,两个子线程并发执行。直到两个子程序执行结束之后,main线程才开始执行。
join()线程有如下三种重载形式
1.join() :<br>
2.join(long millis) :被join的线程的时间最长为millis毫秒,如果超过了这个millis则线程不被执行<br>
3.join(long millis,int nanos) :被join的线程的时间最长为millis毫秒+nanos微秒,如果超过了这个时间则线程不被执行 <br>
第三种重载行驶一般用不到,因为无论是计算机系统还是计算机硬件,还没发精确到微秒
2.后台线程
后台线程运行于后台,任务是为其他线程提供服务,也被称为“守护线程”或“精灵线程”。JVM的垃圾回收机制就是一个典型的后台线程。<br> 通过调用Thread类的setDaemon(true)方法将线程设置为后台线程
代码演示:
public class DaemonThread extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++) {
System.out.println(getName()+" "+i);
}
}
public static void main(String[] args) {
DaemonThread dt=new DaemonThread();
dt.setDaemon(true);
dt.start();
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
运行结果:
1.Thread-0的后台进程本该当i=999的时候才停止进行,但是程序中只进行到114次,这是因为所有的前台进程结束之后,后天进程的存在也就是去了意义,所以后台进程也跟着死亡。<br>
2.前台线程死亡之后,JVM会通知后台线程死亡,但是从后台线程接收指令到做出反应需要一定的时间,这就是为什么上述程序中的后台进程在main线程死亡之后后台进程还进行到114的原因。
3.线程睡眠
如果需要线程停顿一段时间进入阻塞状态,可以调用Thread类的静态方法sleep(),sleep()有两种重载形式<br>
- sleep(long millis)
让当前正在执行的线程暂停mili毫秒,进入阻塞状态。该方法受到系统计时器和线程调度器精度的影响。- sleep(long millis,int nanos)
让当前正在执行的线程暂停milis毫秒+nanos微秒,进入阻塞状态。该方法受到系统计时器和线程调度器精度的影响(不常用)
代码演示:
public class SleepThread{
public static void main(String[] args) throws Exception {
for(int i=0;i<100;i++) {
System.out.println(Thread.currentThread().getName()+" "+i);
Thread.sleep(1000);
}
}
}
运行结果:
观察程序想运行过程会发现,每一个进程之间相隔1秒。
4.线程让步
调用Thrad类的静态方法 yield()方法可以实现线程让步,和sleep()方法类似,yield()方法也是让当前正在运行的线程暂停,但是不会使线程阻塞,而是让线程进入就绪状态,让线程调度器重新安排一次线程调度,完全有可能出现的状况是,刚刚调用yield()方法进入就绪状态的线程就被线程调度器重新调度出来重新执行。
代码演示:
public class YieldThread extends Thread{
public YieldThread(String str) {
super(str);
}
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<60;i++) {
System.out.println(getName()+" "+i);
if(i==20){
Thread.yield(); //线程让步
}
}
}
public static void main(String[] args) {
YieldThread yt1=new YieldThread("高级");
yt1.setPriority(MAX_PRIORITY); //将次线程设置成最高优先级
yt1.start(); //启动线程
YieldThread yt2=new YieldThread("低级");
yt2.setPriority(MIN_PRIORITY);
yt2.start();
}
}
1.线程调用yield()方法后将执行的机会让给优先级相同的线程<br>2.高优先级的线程调用yield()方法暂停之后,系统中没有与之相同优先级和更高的优先级的线程,则线程调度器会将该线程重新调度出来,重新执行。
sleep()方法和yield()方法比较
| 方法 | 执行机会 | 线程状态 | 是否抛出异常 | 可移植性 |
|---|---|---|---|---|
| sleep() | 线程暂停之后,会给其他线程执行的机会,不用理会线程的优先级 | 阻塞状态 | InterruptedExextion异常 | 优 |
| yield() | 线程暂停之后,只会给优先级相同或更高优先级的线程执行的机会 | 就绪状态 | 否 | 差 |
5.改变线程优先级
1.一般线程具有优先级,更高优先级的线程比优先级低的线程能获得更多的执行机会<br>2.每个线程默认的优先级和创建他们的父线程的优先级相同。<br> 3.Thread类提供了setPriorit(int newPriority)、getPriority() 方法来设置和获取线程的优先级<br>4.也可以使用Thread类的3个静态常量来设置线程的优先级:<br>
MAX_PRIORITY : 值是10<br> MIN_PRIORIT : 值是1<br> NORM_PRIORITY : 值是5
作者:幽幽山村一小生
链接:http://www.imooc.com/article/20677
来源:慕课网
Java基础之多线程没那么复杂!的更多相关文章
- Java基础技术多线程与并发面试【笔记】
Java基础技术多线程与并发 什么是线程死锁? 死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,我们就可以称 ...
- 关于java基础、多线程、JavaWeb基础、数据库、SSM、Springboot技术汇总
作者 : Stanley 罗昊 本人自行总结,纯手打,有疑问请在评论区留言 [转载请注明出处和署名,谢谢!] 一.java基础 1.多态有哪些体现形式? 重写.重载 2. Overriding的是什么 ...
- Java基础之多线程篇(线程创建与终止、互斥、通信、本地变量)
线程创建与终止 线程创建 Thread类与Runnable接口的关系 public interface Runnable { public abstract void run(); } public ...
- Java基础之多线程详细分析
在了解多线程之前,先来了解一下进程与线程之间的关系. 进程和线程: 进程是指在系统中正在执行的一个程序,每个进程之间是独立的. 线程是进程的一个基本执行单元.一个进程要想执行任务,必须得有线程(每1个 ...
- Java基础之多线程框架
一.进程与线程的区别 1.定义: 进程是具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位. 线程是进程的一个实体,是CPU调度和分派的基本单位,它是比 ...
- java基础知识 多线程
package org.base.practise9; import org.junit.Test; import java.awt.event.WindowAdapter; import java. ...
- Java基础之多线程
1.进程和线程: 进程:正在进行的程序.每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元. 线程:进程内部的一条执行路径或者一个控制单元. 两者的区别: 一个进程至少有一个线程 ...
- Java基础之 多线程
一.创建多线程程序的第一种方式: 继承(extends) Thread类 Thread类的子类: MyThread //1.创建一个Thread类的子类 public class MyThread e ...
- 面试【JAVA基础】多线程
本次整理的内容如下: 1.进程与线程的区别 进程是一个可执行的程序,是系统资源分配的基本单位:线程是进程内相对独立的可执行单元,是操作系统进行任务调度的基本单位. 2.进程间的通信方式 2.1.操作系 ...
随机推荐
- 【分布式架构】“spring cloud”与“dubbo”微服务的对比
秉承站在巨人的肩膀上,同时我也不想重复制造轮子,我发现了一系列关于“分布式架构”方面,我需要,同时能够解决我的一些疑惑.问题的博客,转载过来,原文链接: http://blog.csdn.net/ ...
- swift - 启动APP 黑屏
https://blog.csdn.net/chengkaizone/article/details/50478045
- console框脱离eclipse窗口
解决方案: 直接将视图重置. 在eclipse主窗口的最上层选项中,点击“window”选项,找到其中的“Reset Perspective”选项,点击确认即可.即视图重置.
- android 下拉刷新框架PullToRefreshScrollView(com.handmark.pulltorefresh)
很简单,实现OnRefreshListener这个监听器. mPullRefreshScrollView .setOnRefreshListener(new OnRefreshListener< ...
- 四元数运动学笔记(5)IMU驱动的运动误差方程
1.扩展卡尔曼滤波EKF1.1线性化卡尔曼滤波1.2偏差微分方程的推导1.3线性化卡尔曼滤波的流程1.4 离散EKF2.误差状态的运动方程2.1连续时间的IMU系统动态方程2.1.1相关变量2.1.2 ...
- 汇编中CMP的作用
假设现在AX寄存器中的数是0002H,BX寄存器中的数是0003H.执行的指令是:CMP AX, BX 执行这条指令时,先做用AX中的数减去BX中的数的减法运算.列出二进制运算式子: 0 ...
- threejs 世界坐标转化为屏幕坐标
网站: http://www.yanhuangxueyuan.com/Three.js_course/screen.html 方法.project 通过Vector3对象的方法project,方法的参 ...
- 使用 gitbook 写东西
会了markdown 不会用gitbook怎么可以呢 先安装 npm install gitbook -g npm install gitbook-cli -g cli使用代码是客户端的意思,要牢记 ...
- 数据库以及pymysql
1.pymysql模块操作数据库详细 import pymysql # user = 'chun' # psw = conn = pymysql.connect(host='localhost',us ...
- C++中的fstream,ifstream,oftream
https://blog.csdn.net/kingstar158/article/details/6859379 先mark一个大佬的随笔,有时间再回头看 总结: 使用ifstream和ofstre ...