一、前言
计算机的操作系统大多采用任务和分时设计,多任务是指在一个操作系统中可以同时运行多个程序,例如,可以在使用qq聊天的同时听音乐,即有多个独立运行的任务,每个任务对应一个进程,每个进程又可以产生多个线程。
1.进程
进程是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程。操作系统同时管理一个计算机系统中的多个进程,让计算机系统中的多个进程轮流使用CPU资源。
进程的特点:
- 进程是系统运行程序的基本单位
- 每一个进程都有自己独立的一块内存空间、一组系统资源
- 每一个进程的内部数据和状态都是完全独立的
2.线程
线程是进程中执行运算的最小单位,一个进程在其执行过程中可以产生多个线程而线程必须在某个进程内执行。
线程是进程内部的一个执行单元,是可完成一个独立任务的顺序控制流程,如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为多线程。
线程按处理级别可以分为核心级线程和用户及线程
1.核心机线程
核心级线程是和系统任务相关的额线程,他负责处理不同进程之间的多个线程。允许不同进程中的线程按照同一相对优先调度方法对线程进行调度,使他们有条不紊的工作,可以发挥多处理器的并发优势,以充分利用计算机的软/硬件资源
2.用户级线程
在开发程序时,由于程序的需要而编写的线程即用户级线程,这些线程的创建,执行和消亡都是在编写应用时进行控制的。对于用户级线程的切换,通常发生在一个应用程序的诸多线程之间,如迅雷中的多线程下载就属于用户线程
3.线程和进程的联系以及区别
1.一个进程中至少有一个线程
2.资源分配给进程,同一个进程的所有线程共享该进程的所有资源
3.处理机分配给线程,即真正在处理机上运行的是线程
4.多线程的优势
1.多线程程序可以带来更好的用户体验,避免因程序执行过慢而导致出现计算机死机或者白屏的情况。
2.多线程可以最大限度地提高计算机系统的利用效率,如迅雷的多线程下载。
二、编写线程类
每个程序至少自动拥有一个线程,称为主线程。当程序加载到内存时启动主线程。java程序中的main方法时主线程的入口,运行java程序时,会先执行这个方法。开发中,用户编写的线程一般都是指除了主线程之外的其他线程
使用一个线程的过程一般有以下4个步骤
- 定义一个线程,同时指明这个线程所要执行的代码,即期望完成的功能
- 创建线程对象
- 启动线程
- 终止线程
定义一个线程通常由两种方法,分别是继承java.lang.Thread类和java.lang.Runnable接口
1.使用Thread类创建线程
Thread类的常用方法:
方法 | 说明 |
---|---|
void run() | 执行任务操作的方法 |
void start() | 使该线程开始执行 |
void sleep(long mils) | 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行) |
String getName() | 返回该线程的名称 |
int getPririt() | 返回线程的优先级 |
void setPriority(int newPoriority) | 更改线程的优先级 |
Thread.State getState() | 返回该线程的状态 |
boolean is Alive() | 测试线成是否处于活动状态 |
void join() | 等待该线程终止 |
void interrupt() | 中断线程 |
void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
创建线程时继承Thread类并重写Thread类run()方法。其中run方法时线程要执行操作任务的方法,所以线程要执行的操作代码都要写在run方法中,并通过调用start方法来启动线程。
示例:使用继承Thread类的方式来创建线程,在线程中输入1-100的整数
实现步骤:
1.定义一个类来继承Thread类,重写run方法,在run方法中实现数据输出
2.创建线程对象
3.调用start方法启动线程
public class MyThread extends Thread{
//示例:使用继承Thread类的方式来创建线程,在线程中输入1-100的整数
private int count = 0;
//重写run方法
public void run(){
for (int i = 1; i <=100 ; i++) {
System.out.println(i);
}
}
}
启动线程
package xc.test1;
public class Test {
public static void main(String[] args) {
//实例化线程对象
MyThread mt = new MyThread();
//启动线程
mt.start();
}
}
2.使用Runnable接口创建线程
虽然Thread类的方式创建线程简单明了符合大家的习惯,但他也有一个缺点,如果定义的类已经继承了其他类则无法再继承Thread类。使用Runnable接口创建线程的方式可以解决上述问题。
Runnable接口中声明了一个run方法(),即public void run()。一个类可以通过实现Runnable接口并实现其run()方法完成线程的所有活动,已实现的run方法称为该对象的线程体。任何实现runable接口的对象都可以作为一个线程的目标对象。
示例:使用继承Thread类的方式来创建线程,在线程中输入1-100的整数
实现步骤:
1.定义了MyThread类实现Runable接口,并实现Runnable接口的run方法,在run方法中输出数据
2.创建线程对象
3.调用start方法启动线程
public class MyThread implements Runnable{
//示例:使用继承Thread类的方式来创建线程,在线程中输入1-100的整数
private int count = 0;
//重写run方法
public void run(){
for (int i = 1; i <=100 ; i++) {
System.out.println(i);
}
}
}
启动start
package xc.test1;
public class Test {
public static void main(String[] args) {
//实例化线程对象
Thread thread = new Thread(new MyThread());
//启动线程
thread.start();
}
}
三、线程的状态
线程生命周期4阶段:新生状态,可运行状态,阻塞状态,死亡状态
1.新生状态
线程在尚未调用start方法之前就有了生命,线程仅仅是一个空对象,系统没有为其分配资源,此时只能启动和终止线程,任何其他操作都会发生异常。
2.可运行状态
当调用start方法后启动线程后,系统为该线程分配出CPU外的所需资源,这时线程就处于可运行的状态。
当然在这个状态中,线程也可能未运行。对于只有一个CPU的机器而言,任何时刻只能有一个处于可运行状态的线程占用处理机
3.阻塞状态
一个正在运行的线程因某种原因不能继续运行时,进入阻塞状态,阻塞状态是不可运行的状态。
导致阻塞状态的原因
- 调用了Thread的静态方法sleep()
- 一个线程执行到一个I/O操作时,I/O操作尚未完成
- 如果一个线程的执行需要得到一个对象锁,而这个对象的锁正在被别的线程用,那么会导致阻塞
- 线程的suspend()方法被调用而使线程被挂起时,线程进入阻塞状态
3.死亡状态
当一个线程的run方法运行完毕,stop方法被调用或者在运行过程中出现未捕获的异常时,线程进入死亡状态。
四、线程调度
当同时有多个线程处于可运行状态,他们需要排队等待CPU资源,每个线程会自动 获得一个线程的优先级,优先级的高低反映出线程的重要或紧急程度,可运行状态的线程按优先级排队。线程调度依据建立在优先级基础上的“先到先服务”原则。
线程调度室抢占式调度,即在当前线程执行过程中如果有一个更高优先级的线程进入可运行状态,则这个更高优先级的线程立即被调度执行。
1.线程优先级
线程的优先级用1~10表示,10表示优先级最高,默认值是5,每个优先级对应一个Thread类的公用静态常量
例如:public static final int NORM_PRIORITY=5;
每个线程的优先级都介于Thread.MIN_PRIORITY和Thread.MAX_PRIORITY之间
线程的优先级可以通过setPrioity(int grade)方法更改
2.实现线程调度的方法
实现线程调度的方法
1.join()方法
join方法使当前线程暂停执行,等待调用该方法的线程结束后再继续执行本线程。
它有3中重载形式
public final void join()
public final void join(long mills)
public final void join(long mills,int nanos)
示例:使用join()阻塞线程
实现步骤:
1.定义线程类,输出5次当前线程的名称
2.定义测试类,使用join方法阻塞主线程
package xc.test2;
public class MyThread extends Thread {
public MyThread(String name){
super(name);
}
public void run(){
for (int i = 0; i <5 ; i++) {
//输出当前线程的名称
System.out.println(Thread.currentThread().getName()+""+i);
}
}
}
package xc.test2;
public class Test {
public static void main(String[] args) {
//主线程运行五次后,开始运行MyThread线程
for (int i = 0; i <10 ; i++) {
if (i==5){
MyThread t = new MyThread("MyThread");
try {
t.start();
t.join();//把该线程通过join方法插入到主线程面前
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+""+i);
}
}
}
示例:使用sleep()方法阻塞线程
实现步骤:
定义线程
在run()方法中使用sleep()方法阻塞线程
定义测试类
package xc.test3;
public class Wait {
//==示例:使用sleep()方法阻塞线程==
public static void bySec(long s){
for (int i = 0; i < s; i++) {
System.out.println((i+1)+"秒");
}
try {
Thread.sleep(1000);//括号中的是毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
package xc.test3;
public class Test {
public static void main(String[] args) {
System.out.println("wait");//提示等待
Wait.bySec(5);//让主线程等待五秒再执行
System.out.println("start");//提示恢复执行
}
}
2.yield()方法
语法格式:
- public static void yield()
yield方法可让当前线程暂停执行,允许其他线程执行,但该线程仍处于可运行状态,并不变为阻塞状态。此时,系统选择其他相同或更高优先级线程执行,若无其他相同或更高优先级线程,则该线程继续执行。
示例:使用yield方法暂停线程
实现步骤:
1.定义两个线程
2.在run方法中使用yield方法暂停线程
3.定义测试类
package xc.test4;
public class FirstThread extends Thread{
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println("第一个线程的第"+(i+1)+"次运行");
Thread.yield();//暂停线程
}
}
}
package xc.test4;
public class SecThread extends Thread{
public void run(){
for (int i = 0; i < 5; i++) {
System.out.println("第二个线程的第"+(i+1)+"次运行");
Thread.yield();
}
}
}
package xc.test4;
public class Test {
public static void main(String[] args) {
FirstThread f = new FirstThread();
SecThread s = new SecThread();
f.start();
s.start();
}
}
sleep方法与yield方法的区别
sleep()方法 | yield()方法 |
---|---|
使当前线程进入被阻塞的状态 | 使当前线程进入暂停执行的状态 |
即使没有其他等待运行的线程,当前线程也会等待指定的时间 | 如果没有其他等待执行的线程,当前线程会马上恢复执行 |
其他等待执行的线程的机会是均等的 | 会运行优先级相同或更高的线程 |
最后
感谢你看到这里,看完有什么的不懂的可以在评论区问我,觉得文章对你有帮助的话记得给我点个赞,每天都会分享java相关技术文章或行业资讯,欢迎大家关注和转发文章!