多线程的一些基础
java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流(一段顺序执行的代码)。java使用线程执行体来代表这段程序流。
一、进程和线程的定义和区别
进程:正在进行的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。
线程:进程内部的一条执行路径或者一个控制单元。
两者的区别:一个进程至少有一个线程,进程在执行过程中拥有独立的内存单元,而多个线程共享内存。
多线程并行和并发的区别:
* 并行就是两个任务同时运行,就是甲任务进行的同时,乙任务也在进行。(需要多核CPU)
* 并发是指两个任务都请求运行,而处理器只能按受一个任务,就把这两个任务安排轮流进行,由于时间间隔较短,使人感觉两个任务都在运行。
多线程:一个进程中有多个线程,称为多线程(默认是指并发)
二、实现多线程的方法:实现多线程可以通过继承Thread类和实现Runnable接口。
方法一:继承Thread类创建线程类
通过继承Thread类来创建并启动多线程的步骤如下:
1:定义Thread类的子类,并重写该类的run()方法,该方法的方法体就代表了线程需要完成的任务。因此把run()方法称为线程执行体。
2:创建Thread子类的实例。即创建了线程的对象。
3:调用线程对象的start()方法来启动线程。
(ps.关于一些疑问这里边的一些疑问,讲完第二种实现多线程的方法在统一来解决。)
//通过继承Thread类实现多线程
public class ThreadDemo extends Thread {
private int count=0;//系统访问次数
public void run() {
count++;
System.out.println("第"+count+"位访客来啦!");
}
public static void main(String[] args) {
ThreadDemo rd=new ThreadDemo();
for(int i=0;i<50;i++){
Thread thread=new Thread(rd);
thread.start();//一定要注意不是通过.run()启动线程
}
}
}
方法二:实现Runnable接口创建线程类
实现Runnable接口来创建并启动多线程的步骤如下。
1:定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体。
2:创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
3:调用线程对象的start()方法来启动该线程
//通过Runnable接口实现多线程
public class RunnableDemo implements Runnable{
private int count=0;//系统访问次数
@Override
public void run() {
count++;
System.out.println("第"+count+"位访客来啦!");
}
public static void main(String[] args) {
RunnableDemo rd=new RunnableDemo();
for(int i=0;i<50;i++){
Thread thread=new Thread(rd);
thread.start();//一定要注意不是通过.run()启动线程
}
}
}
回答上边可能会产生的疑问:
①为什么运行结果每次都不同?
因为多个线程都获取CPU的执行权,CPU执行到谁,谁就运行。需要注意的是:在某一个时刻,只能有一个程序在运行。(多核CPU除外) 只不过是CPU在做着快速切换,已达到看上去是同时运行的效果。 我们可以形象的把多线程的运行看作是在互相抢夺CPU的执行权。这是CPU的一个特性:随机性。谁抢到CPU的资源谁就执行,至于运行多长时间,CPU说了算。(如果要使多线程实现同步:可以再run()方法前边加上synchronized(中文 叫:同步锁),举个栗子:public synchronized void run() {......})
②为什么要覆盖Run方法?
Thread类用于描述线程。该类就定义了一个功能(方法),用于存储线程要运行的代码,该存储功能就是run方法。run()方法中的内容称为线程体,它就是这个线程需要执行的工作。
③为什么要调用线程对象的start()方法?start()和run()方法有什么区别?
调用start方法方可启动线程,而run方法只是thread的一个普通方法,调用run方法不能实现多线程。
start()方法:
start()方法用来启动线程,实现了多线程运行,这时无需等待run方法体代码执行完毕而直接继续执行下面的代码。通过调用Thread类的start()方法来启动一个线程,这时此线程处于就绪(可运行)状态,并没有运行,一旦得到cpu时间片(执行权),就开始执行run()方法,这里方法run()称为线程体,它包含了要执行的这个线程的内容,Run方法运行结束,此线程随即终止。
run()方法:
run()方法只是Thread类的一个普通方法,如果直接调用Run方法,程序中依然只有主线程这一个线程,其程序执行路径还是只有一条,还是要等待run方法体执行完毕后才可继续执行下面的代码,这样就没有达到多线程的目的。
start()方法和run()方法区别对比:https://blog.csdn.net/kwame211/article/details/79030255
此外可以了解一下:
java5提供了Callable接口,该接口怎么看看都像是Runable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更加强大。
1:call()方法可以有返回值
2:call()方法可以声明抛出异常
因此完全可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法。问题是,Callable接口是java5新增的接口,而且它不是Runnable接口的子接口。所以Collable对象不能直接作为Thread的target.而且call()方法还有一个返回值,call方法并不是直接调用,他是作为线程执行体被调用的那么如何获取call()的返回值呢?
jiava5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个实现类FutureTask。该实现类实现了Future接口,并实现了Runnable接口。可以作为Thread类的target.
Callable接口有泛型的限制,Callable接口里的泛型形参类型与call()方法的返回值类型相同。而且Callable接口是函数式接口,因此可使用Lanbda表达式创建Callable对象。
创建并启动有返回值的线程步骤如下:
1:创建Callable接口的实现类,并实现call()方法,该方法将作为线程执行体,且该方法有返回值,再创建Callable实现类的实例。
2:使用FutureTask类包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值
3:使用FutureTask对象作为Thread对象的target创建并启动新线程
4:调用FutureTask对象的get()方法来获得子线程执行结束后的返回值。
1:多线程
(1)多线程:一个应用程序有多条执行路径
进程:正在执行的应用程序
线程:进程的执行单元,执行路径
单线程:一个应用程序只有一条执行路径
多线程:一个应用程序有多条执行路径
多进程的意义? 提高CPU的使用率
多线程的意义? 提高应用程序的使用率
(2)Java程序的运行原理及JVM的启动是多线程的吗?
A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
(3)多线程的实现方案(自己补齐步骤及代码掌握)
A:继承Thread类
B:实现Runnable接口
(4)线程的调度和优先级问题
A:线程的调度
a:分时调度
b:抢占式调度 (Java采用的是该调度方式)
B:获取和设置线程优先级
a:默认是5
b:范围是1-10
(5)线程的控制(常见方法)
A:休眠线程
B:加入线程
C:礼让线程
D:后台线程
E:终止线程
(6)线程的生命周期
A:新建
B:就绪
C:运行
D:阻塞
E:死亡
多线程安全问题的原因(也是我们以后判断一个程序是否有线程安全问题的依据)
A:是否有多线程环境
B:是否有共享数据
C:是否有多条语句操作共享数据
同步解决线程安全问题
A:同步代码块
synchronized(对象) {
需要被同步的代码;
}
这里的锁对象可以是任意对象。
B:同步方法
把同步加在方法上。
这里的锁对象是this
C:静态同步方法
把同步加在方法上。
这里的锁对象是该类的字节码(比如Demo.class)