java中多线程的基础知识

导读:本篇文章讲解 java中多线程的基础知识,希望对大家有帮助,欢迎收藏,转发!站点地址:www.bmabk.com

Process与Thread:

程序是指一段静态的代码,是指令和数据的有序集合,其本身没有任何运行的含义,它能够完成某些功能,它是应用软件执行的蓝本,它是一个静态的概念

进程是关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元,也是程序的一次动态执行过程,进程经历了从代码加载、运行到执行完毕的一个完整的过程,这个过程也是进程本身从产生、发展到最终消亡的过程。

进程的特点:

进程具有动态性、并发性、独立性和异步性

(1)动态性:进程的实质是进程实体的执行过程。
因此,动态性就是进程的最基本特性,动态性还表现在“它由创建而产生,由调度而执行,撤销而消亡”
(2)并发性:是指多个进程实体同时存于内存中,且能在一段时间内同时运行
(3)独立性:进程实体是一个能独立运行、独立获得资源和独立接收调度的单位
(4)异步性:进程按照各自独立、不可预知的速度向前推进。

由于多个进程在并发执行时共享系统资源,致使它们在运行过程中呈现间断性的运行规律,所以进程在其生命周期中存在多种状态

一般情况下,每一个进程应该处于就绪(Ready)状态、执行(Running)状态和阻塞(Block)状态这3种状态中的一种

线程:

线程是进程中划分出来的更小的执行单位一个进程在其执行过程中可以产生多个线程,每个线程代表一条执行线索,如下图所示:
在这里插入图片描述

每个线程都有它各自产生存在和消亡的过程

一个进程中所产生的多个线程之间共享由操作系统分配给所属进程的内存资源(包括代码和数据),通过这些共享资源来实现数据交换、实时通信和同步操作。

但与进程不同的是,线程的中断与恢复可以节省系统的开销,通过多线程进行程序设计可以更好地描述并解决现实世界中的问题,现实世界中的某个活动需要多个对象同时参与并完成,一个线程个体就可以描述现实中的一个对象。

线程是CPU调度和执行的的单位

注意:
很多多线程是模拟出来的,真正的多线程是指有多个cpu,即多核,如服务器,如果是模拟出来的多线程,即在一个cpu的情况下,在同一个时间点,cpu只能执行一个代码,因为切换的很快,所以就有同时执行的错觉.

java中的多线程:

Java语言内置了对多线程的支持,开发人员通过创建 Thread 或其子类对象就可以快速地创建线程对象并使用。

通过在一个进程中创建多个线程并执行多个线索,可以使得开发人员很方便地开发出具有多线程功能、能同时处理多个任务的功能强大的应用程序。

虽然执行线程给人一种几个事件同时发生的感觉,但实际上计算机在任何给定的时刻只能执行多个线程中的某一个线程。但由于 Java 虚拟机能够非常快速地将执行的控制权由一个线程切换到另一个线程,使得这些线程可以轮流执行,给人们的感觉就好像是这些线程同时执行一样

主线程[main线程]:

Java应用程序的入口是 main方法,当JVM加载main方法所属的字节码后,就会启动一个线程执行main方法,这个线程我们称为主线程,也是缺省的主线程

假如 main 方法中再没有创建并启动其他的线程,那么主线程就从 main 方法的第一句代码开始执行,一直到main 方法中的最后一句代码执行完毕,此时主线程就结束了,应用程序也就结束了。

如果main 方法中创建并启动了其他的线程对象,此时JVM 就会在主线程和其他的线程之间轮流切换,确保每个线程都有机会使用 CPU 资源,当main 方法的最后一句代码执行完毕,此时只是主线程结束执行,Java 应用程序并没有结束,只有当其他所有的线程都执行完毕,此时 Java应用程序才会结束执行

Java 多线程的调度策略:

Java 虚拟机的一项任务就是负责线程的调度,线程的调度是指按照特定的机制为多个线程分配 CPU 的使用权,有两种调度模型:分时调度模型和抢占式调度模型,分时调度模型是指让所有线程轮流获得 CPU 的使用权,并且平均分配每个线程占用CPU 的时间片。

Java 虚拟机采用抢占式调度模型,是指优先让等待队列中处于就绪态的高优先级的线程对象获取 CPU使用权(JVM 规范中规定每个线程都有优先级,且优先级越高越优先执行,但优先级高并不代表能独自占用执行时间片,可能是优先级高的得到越多的执行时间片;反之,优先级低的分到的执行时间少,但不会分配不到执行时间)。

如果等待队列中线程的优先级相同,那么就随机选择一个线程,使其占用 CPU,处于运行状态的线程会一直执行,直至它不得不放弃CPU一个线程会因为以下原因放弃 CPU:

(1)Java虚拟机让当前线程暂时放弃 CPU,转到就绪态,让其他线程获得运行机会
(2)当前线程因为某些原因而处于阻塞状态
(3)线程运行结束

注意:

(1)线程的调度不是跨平台的,它不仅取决于 Java 虚拟机,还依赖于操作系统,在某些操作系统中,即使运行中的线程没有遇到阻塞,也会在运行一段时间后放弃 CPU,给其他线程运行的机会。
(2)Java 线程的调度不是分时的,同时启动多个线程后,不能保证各个线程轮流获得均等的时间片

线程的状态与生命周期:

和其他有生命的物质一样,线程也有它完整的生命周期,Java 语言使用 Thread 类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历新建态、就绪态、运行态、阻塞态和消亡态这几种状态。下图展示了线程生命周期中状态的转换关系

在这里插入图片描述

1.新建状态:

通过 new 关键字创建一个线程对象后,就处于新建状态

此时,线程被分配内存空间,数据已经被初始化,但该线程对象还未被激活,不能获取 CPU 使用权。

注:此时也可以把该线程对象变成消亡状态,但一般不会这样做。

2.就绪状态:

处于新建状态的线程调用 start()方法可以将线程的状态转换成就绪状态

进入就绪状态的线程对象就会进入 CPU 的等待队列,等待获取 CPU 的使用权,此时该线程对象已具备了运行的条件但还没有真正运行。

3.运行状态:

当处于就绪状态的线程被调度并获得 CPU 的使用权后,线程对象便进入运行状态在运行状态执行线程对象的 run()方法,这个方法是线程对象最重要的方法,因为该方法定义了线程对象需要完成的任务

4.阻塞状态:

在某种特殊情况下线程对象需要让出CPU的使用权,暂停 run()方法的执行,便进入阻塞状态,让线程进入阻塞状态的原因通常有以下几个:

(1)JVM将CPU使用权从当前线程对象切换到另一个线程对象
(2)当前线程对象执行了sleep(int millsecond)方法进人阻塞状态,当millsecond时间过后,线程对象就会重新“苏醒”,重新进入就绪状态,等待下一次获得 CPU的使用权,接着执行剩余的run()方法
(3)线程对象在运行状态时执行了 wait()方法使得当前线程进入等待(阻塞)状态,进入等待状态的线程对象不会主动进入就绪队列排队等待CPU资源,必须由其他线程对象调用notify()将线程对象从等待状态“唤醒”,被“唤醒”的线程对象才能重新进入就绪队列排队等待CPU资源
(4)线程对象在运行状态执行了其他导致其进入阻塞状态的方法,例如 IO 操作。当引起阻塞的原因消除时,线程才重新进入到就绪队列中等待 CPU 资源,以便从原来阻塞处的代码往后继续执行
(5)消亡(死亡)状态处于消亡状态的线程已经不具备运行的能力,等待被JVM 垃圾收集器回收并释放内存。当线程对象执行完run()方法中的最后一句代码或者被强制性地终止run()方法的执行后,线程对象就会进入消亡状态。

Thread:

java.lang.Thread类及其子类对象表示一个线程对象,由于java.lang 包自动被导人每个java程序中,所以在使用Thread时,无需在java程序中编写import语,一个类在继承了Thread类之后,就具备了多线程的操作功能

使用继承Thread的方法创建并执行一个线程,需要执行以下4个步骤:

定义类继承Thread类,并扩展新的功能;
重写run()方法
通过new关键字实例化一个新对象;
调用start()方法启动线程

线程是程序中执行的线程。 Java虚拟机允许应用程序同时执行多个执行线程。

注:线程不一定立即执行,CPU安排调度

创建线程的方式1:继承Thread类

Thread()	//创建一个新的线程对象,采用默认的名称
Thread(String name)	//创建一个名为name的线程对象
Thread(Runnable runner)	//创建以实现Runnable接口的类的对象为参数的线程对象
Thread(Runnable runner,String name)	//创建以实现Runnable接口的类的对象为参数的线程对象,线程名为name

举例:

以下实例包含了4中不同的为线程取名的方法

package MyThread;


//编写学生类---->通过Thread(String name)的方式设置线程名称
public class Student extends Thread {
    String student_name;
    public Student(String name){
        this.student_name=name;
    }
    @Override
    public void run() {
        for(int i=1;i<=10;i++)
            System.out.println("当前线程是->" + Thread.currentThread().getName() + "我在学习java");
    }
}


//编写教师类---->通过调用父类构造方法给线程起名
class teacher extends Thread{
    public teacher(String  name) {
        super(name);
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("当前线程是->" + Thread.currentThread().getName() + "我在教java");
        }
    }
}

//编写anmial类通过---->Thread.currentThread().setName()方法给线程起名
class anmial extends Thread{
    public anmial() {
    }
    @Override
    public void run() {
        Thread.currentThread().setName("小狗");
        for (int i = 1; i <= 10; i++) {
            System.out.println("当前线程是->" + Thread.currentThread().getName() + "我在吃骨头");
        }
    }
}


//编写car类通过---->setName()方法给线程起名
class car extends Thread{
    public car() {
    }
    @Override
    public void run() {
        for (int i = 1; i <= 10; i++) {
            System.out.println("当前线程是->" + Thread.currentThread().getName() + "我在马路上行驶");
        }
    }
}


class test1{
    public static void main(String[] args) {
        Student student=new Student("小明");
        new Thread(student,student.student_name).start();
        anmial dog=new anmial();
        dog.start();
        teacher teacher=new teacher("皮特");
        teacher.start();
        car car=new car();
        car.setName("卡车");
        car.start();
    }
}

输出:

当前线程是->皮特我在教java
当前线程是->卡车我在马路上行驶
当前线程是->小明我在学习java
当前线程是->小明我在学习java
当前线程是->小狗我在吃骨头
当前线程是->小明我在学习java
当前线程是->卡车我在马路上行驶
当前线程是->皮特我在教java
当前线程是->卡车我在马路上行驶
当前线程是->小明我在学习java
当前线程是->小狗我在吃骨头
当前线程是->小明我在学习java
当前线程是->卡车我在马路上行驶
当前线程是->皮特我在教java
当前线程是->卡车我在马路上行驶
当前线程是->小明我在学习java
当前线程是->小狗我在吃骨头
当前线程是->小明我在学习java
当前线程是->卡车我在马路上行驶
当前线程是->皮特我在教java
当前线程是->卡车我在马路上行驶
当前线程是->小明我在学习java
当前线程是->小狗我在吃骨头
当前线程是->小明我在学习java
当前线程是->卡车我在马路上行驶
当前线程是->皮特我在教java
当前线程是->卡车我在马路上行驶
当前线程是->皮特我在教java
当前线程是->小明我在学习java
当前线程是->小狗我在吃骨头
当前线程是->皮特我在教java
当前线程是->卡车我在马路上行驶
当前线程是->皮特我在教java
当前线程是->小狗我在吃骨头
当前线程是->皮特我在教java
当前线程是->皮特我在教java
当前线程是->小狗我在吃骨头
当前线程是->小狗我在吃骨头
当前线程是->小狗我在吃骨头
当前线程是->小狗我在吃骨头

普通方法调用和多线程:

在这里插入图片描述

以下实例中包含了Thread各个方法:

package Thread;

public class Thread_test {
    public static void main(String[] args) throws InterruptedException {
    Thread_test thread =new Thread_test();
    thread.start(); //start()方法启动线程
    thread.sleep(10);   //sleep()方法线程休眠一段时间,单位为毫秒,线程进入阻塞状态
    thread.run();   //run()方法使线程获得CPU使用权后所执行的方法
    thread.interrupt(); //中断线程
    System.out.println(thread.currentThread()); //返回当前正在使用CPU的线程对象
    thread.yield(); //将CPU控制权主要移交到下一个线程
    System.out.println(thread.isAlive());   //判断当前线程对象是都还存活,只有处于就绪状态,运行态和阻塞态的线程对象返回true
    thread.setPriority(2);  //设置线程对象的优先级,n的取值为1-10
    thread.join();  //在当前线程中等待调用此方法的线程执行完毕
    thread.join(10);    //在当前线程中等待调用此方法的线程执行,等待时间为x毫秒
    }
}

Thread有关方法的简单应用:

例一:

package MyThread;

class Thread_test1{
    public static void main(String[] args) throws InterruptedException {
        Thread_test thread_test=new Thread_test();
        thread_test.start();
        try {
            Thread.sleep(5000);
        }catch (InterruptedException e) {
            e.printStackTrace();
        }
        if(thread_test.isAlive())
        {
            System.out.println("线程还存活");
            try {
                thread_test.join();//在当前线程中等待调用此方法的线程执行完毕
            }catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println("main线程执行结束");
    }
}
public class Thread_test extends Thread{
    @Override
    public void run() {
        Thread.currentThread().setName("thread_test");
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i=0;i<10;i++){
            System.out.println("当前线程是->" + Thread.currentThread().getName() + "正在运行");
        }
    }
}

输出:

当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
当前线程是->thread_test正在运行
main线程执行结束			//大概等待5秒钟左右会出现该行输出语句

执行过程:
在这里插入图片描述

例二:

package Thread;

public class Thread2 extends Thread{
    public Thread2(String name) {
        super(name);
    }

    @Override
    public void run() {
    //第一步--->main 线程启动 thread2 线程后进入休眠状态让出 CPU使用权,此时thread2线程获得CPU使用权,并休眠1秒钟又让出CPU使用权
        try {
            Thread.sleep(1000);	//休眠一秒钟
        } catch (InterruptedException e) {
        //第三步--->thread2 线程被中断后执行InterruptedException 异常的catch代码块中逻辑
            System.out.println(Thread.currentThread().getName()+"的休眠状态被打断");
            return ;//从run方法返回
        }
        //上述被中断,所以结果中输出“线程t1的休眠被打断”,并从run()方法返回,t2线程在循环变量i为5时执行 yield()方法
        for(int i=0;i<10;i++){
            System.out.println("当前线程:"+Thread.currentThread().getName()+"正在运行");
            if(i==5){
                Thread t=Thread.currentThread();
                t.yield();	//当前线程执行让步操作
                System.out.println(t.getName()+"执行让步操作");
            }
        }
    }
}
class Thread2_test{
    public static void main(String[] args) throws InterruptedException {
    
        Thread2 thread2=new Thread2("线程1");
        Thread2 thread2s=new Thread2("线程2");
        thread2.start();	//启动thread2线程
        Thread.sleep(5);
        
		//第二步--->main 线继续执行启动 t2线程并执行t线程的interrupt()方法
        thread2s.start();	//启动thread2s线程
        thread2.interrupt();//对thread2线程执行中断操作
    }
}

输出:

线程1的休眠状态被打断
当前线程:线程2正在运行
当前线程:线程2正在运行
当前线程:线程2正在运行
当前线程:线程2正在运行
当前线程:线程2正在运行
当前线程:线程2正在运行
线程2执行让步操作
当前线程:线程2正在运行
当前线程:线程2正在运行
当前线程:线程2正在运行
当前线程:线程2正在运行

通过实现 Runnable 接口实现线程:

由于Java单继承的限制,当一个类已经继承了一个类(不是Thread类),但这个类还想要实现线程的功能,此时已经不能再继承Thread类。在这种情况下这个类可以实现 Runnable接口

Runnable接口只包含一个run()方法,代码如下所示:

class MyThread implements Runnable{
public void run(){
//线程对象被调度后所执行的方法
	}
}

利用Runnable接口创建并运行线程的编程步骤如下:

(1)创建一个实现了Runnable接口的类的对象调用Thread类带 Runnable形参的构造方法,把 Runnable接口实现类对象传递给 Thread类的对象,为新线程提供代码和数据:

MyThread tl=new MyThread ();//MyThread实现了Runnable接口
Thread t2= newThread(t1);

(2)使用线程对象调用start()方法启动线程:

package Runnable;


//教师线程实现Runnable接口
public class Runnable_test implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("当前线程是->"+Thread.currentThread().getName()+"我要布置作业");
        }
    }
}



//学生线程实现Runnable接口
class Runnable_tests implements  Runnable{
    @Override
    public void run() {
        for(int i=0;i<10;i++){
            System.out.println("当前线程是->"+Thread.currentThread().getName()+"我要好好学习");
        }
    }
}



//测试类
class test{
    public static void main(String[] args) {
        //创建实现Runnable接口实现类的实例化对象
        Runnable_test runnable_test=new Runnable_test();
        Runnable_tests runnable_tests=new Runnable_tests();

        //创建Thread类对象,并为线程起名
        Thread thread=new Thread(runnable_test,"教师线程");
        thread.start();
        Thread threads=new Thread(runnable_tests,"学生线程");
        threads.start();
    }
}

输出:

当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->教师线程我要布置作业
当前线程是->学生线程我要好好学习
当前线程是->学生线程我要好好学习

由于 Runnable接口中的run()方法没有返回值,在某些实际应用中需要线程对象有返回值,所以在java.util.concurrent 包中提供了一个Callable<V>接口,该接口提供了一个call()方法,其中V表示 call()方法的返回值,而且可以抛出异常

语法如下:

V call() throws Exception;

因为Thread类的构造方法中不包含Callable接口类型的参数,所以实现Callable接口的对象无法直接与Thread类进行联系,因此中间需要借助 FutureTask类,FutureTask类实现了Runnable接口,FutureTask类的构造方法可以接受Callable接口类型的参数

FutureTask类位于java.utilconcurrent包中,用来表示一个异步计算的结果,通过该类的get()方法可以得到Callable接口中call()方法的返回值

举例:

package Runnable;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

//实现Callable接口
public class Mytask implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        Integer sum=0;
        for(int i=0;i<=100;i++){
            sum+=i;
        }
        return sum;
    }
}


 class Mytask_test{
     public static void main(String[] args) throws ExecutionException, InterruptedException {
         //创建FutureTask类的对象,参数为Callable接口类型
         FutureTask<Integer> futureTask=new FutureTask<>(new Mytask());
         //创建线程对象,参数类型是FutureTask,因为FutureTask实现了Runnable
         Thread mythread=new Thread(futureTask);
         mythread.start();
         //得到线程对象的计算结果,也就是call()方法的返回值
         Integer result=futureTask.get();
         System.out.println("线程的运算结果是:"+result);
     }
 }

输出:

线程的运算结果是:5050

建立线程方法的比较:

继承Thread类

这种方式创建一个类继承 Thread类,并且覆盖了Thread()类的 run()方法,描述线程对象所执行的操作

优点是: Thread 类的子类对象就是线程对象,能够使用 Thread 类声明的方法且具有线程体。

缺点是:不能实现多继承,不建议使用: 避免OOP单继承局限性

实现Runnable接口

当一个类已经继承了一个类,但还想要以线程方式运行时,就需要实现 Runnable 接口,一个实现 Runnable接口的类的对象本身不是线程的对象。它作为一个线程对象的目标对象使用,因此,使用时需要声明一个 Thread 类对象,可以实现资源的共享.

推荐使用: 避免单继承局限性,灵活方便,方便同一个对象被多个线程使用

实现Callable 接口

Callable 接口的 call()方法可以有返回值,而且能够抛出异常。Callable 接口无法直接作为参数来创建线程对象,需要借助 FutureTask类将实现 Callable 接口的类的对象作为参数创建FutureTask对象,再将 FutureTask 对象作为参数创建线程对象,所以实现 Callable接口的类的对象也可以作为线程对象的目标对象使用

注意:

当一个类只为了修改run()方法,而其他方法不变的情况下,推荐使用 Runnable接口的方式;

当一个类继承了另外一个类,而又想实现多线程的情况下,可以使用实现Runnable 接的方式,也可以使用实现 Callable 接口的方式(此方式可以得到子线程的运行结果),Java 不支持多继承;

当多个线程要共享同一个资源时,推荐使用Runnable接口的方式。

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

文章由极客之音整理,本文链接:https://www.bmabk.com/index.php/post/81391.html

(0)
小半的头像小半

相关推荐

极客之音——专业性很强的中文编程技术网站,欢迎收藏到浏览器,订阅我们!