前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java多线程基础知识

Java多线程基础知识

作者头像
害恶细君
发布2022-11-22 08:17:24
2350
发布2022-11-22 08:17:24
举报
文章被收录于专栏:编程技术总结分享

目录

一.进程和多线程的概述以及使用场景

二.并发和并行

三.线程的创建

1.Thread类实现多线程

 2.Runnable接口实现多线程

 3.Callable接口实现多线程

四.3种多线程实现方式的对比分析。

五.后台线程



一.进程和多线程的概述以及使用场景

进程:一个正在操作系统中运行的exe程序可以理解为一个进程,完全可以将运行在内存中的exe文件理解为进程-----进程就是受操作系统管理的基本运行单元。一个最简单的Java程序的运行也可以叫做一个进程。

所有的应用程序都是由CPU执行的,对于一个CPU而言,在某一个时间点只能运行一个程序,也就是说只能执行一个进程。操作系统会给每一个进程分配一段有限的cpu使用时间,cpu在这段时间中执行某个进程。由于CPU的执行速度非常快,能在极短的时间内在不同的进程之间进行切换,所以给人同时执行多个程序的感觉。

 线程:在一个进程中,还可以有多个执行单元同时运行,来同时完成一个或多个程序任务。这些执行单元可以看作是一条条线索,被称为线程。

多线程技术的使用场景:(1)阻塞。一旦系统出现了阻塞现象,则可以根据实际情况来使用多线程技术提高开发效率。(2)依赖。业务如果分为2个执行过程,分别是A和B。当A业务发生阻塞时,B业务的执行不依赖于A业务的执行结果,这时就可以通过多线程来提高运行效率。如果B业务不依赖于A业务的结果,则不必使用多线程技术,A业务出结果后再运行B业务,按顺来就行。

二.并发和并行

并发:指两个或多个事件在同一个时间段内发生,同一时刻只能发生一件事。

并行:指两个或多个事件在同一时刻发生。(同时发生)

特别注意:Java程序属于优先抢占式地调度,哪个线程的优先级高,哪个线程就有可能优先执行。优先级相同时,就随机选择执行。优先级大小只是指CPU被优先执行的概率的大小,并不一定优先级大就一定先被执行。

三.线程的创建

Java为多线程开发提供了非常优秀的支持,在java中,可以通过以下三个方式来实现多线程:

1.Thread类实现多线程

Thread类是java.lang包下的一个线程类,用来实现Java多线程。其实步骤非常简单。如下

(1)创建一个Thread线程类的子类,同时重写Thread类的run()方法。

(2)创建该子类的实例对象,并通过调用start()方法来启动线程。

start()方法使线程开始执行,Java虚拟机调用该线程的run()方法。结果是程序的main入口方法和创建的线程并发的运行。多次启动同一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动。

代码语言:javascript
复制
package demo01;

public class Test {
    public static void main(String[] args) {
        //创建MyThread1线程实例对象
        MyThread1 thread1=new MyThread1("thread1");
        thread1.start();
        MyThread1 thread2=new MyThread1("thread2");
        thread2.start();
    }
}

//定义一个线程类继承自Thread类
class MyThread1 extends Thread{
    //构造方法
    public MyThread1(String name){
        super(name);
    }
    //重写run()方法


    @Override
    public void run() {
        int i=0;
        while (i++<5){
            //打印输出当时运行的线程的名字
            System.out.println(Thread.currentThread().getName());
        }
    }
}

 2.Runnable接口实现多线程

通过继承Thread类来实现多线程的方式有一些局限性,因为java只支持类的单继承。在这种情况下就可以考虑通过实现Runnable接口的方式来实现多线程。

步骤如下:

(1)创建一个Runnable接口的实现类,同时重写接口中的run()方法。

(2)创建Runnable接口的实现类对象。

(3)使用Thread有参构造方法来创建线程实例,并将Runnable接口的实现类对象作为参数传入。

(4)调用线程实例的start()方法来启动线程

代码语言:javascript
复制
package demo02;

public class test {
    public static void main(String[] args) {
        //(2)创建Runnable接口的实现类对象。
        myThread mythread1=new myThread();
        myThread mythread2=new myThread();
        //(3)使用Thread有参构造方法来创建线程实例,并将Runnable接口的实现类对象作为参数传入。
        Thread thread1=new Thread(mythread1,"thread1");
        Thread thread2=new Thread(mythread2,"thread2");
        //(4)调用线程实例的start()方法来启动线程
        thread1.start();
        thread2.start();
    }
    
}

//(1)定义一个Runnable接口的实现类
class myThread implements Runnable{

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"的run()方法正在运行");
    }
}

 3.Callable接口实现多线程

通过Thread类和Runnable接口实现多线程时都要要重写run()方法。但是,该方法却没有返回值,因此无法从多个线程中获取返回值,但实际应用中肯定会用到返回值的。为了解决这个问题,从jdk5开始,java提供了一个Callable接口,来满足这种既能创建多线程,又能有返回值的需求。

通过这个方式实现多线程和Runnable的方式实现多线程差不多,都是通过Thread类的有参构造方法传入各自接口对象为参数来实现。只不过这里传入的不是Runnable对象了,而是Runnable类的子类FutureTask对象作为参数,而FutureTask对象中则封装带有返回值的Callable接口实现类。

实现步骤如下:

(1)创建一个Callable的实现类,同时重写Callable接口的call()方法。

(2)创建实现了Callable接口的实现类对象。

(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象

(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。

(5)调用线程实例的start()方法启动线程。

代码语言:javascript
复制
package demo03;

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

//(1)创建一个Callable的实现类,同时重写Callable接口的call()方法。
class MyThread implements Callable<Object>{

    @Override
    public Object call() throws Exception {
        int i=0;
        while (i++<5){
            //打印输出当时运行的线程的名字
            System.out.println(Thread.currentThread().getName());

        }
        return i;
    }
}




public class test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //(2)创建实现了Callable接口的实现类对象。
        MyThread mythread1=new MyThread();
        //(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
        FutureTask<Object> ft1=new FutureTask<>(mythread1);
        //(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。
        Thread thread1=new Thread(ft1,"thread1");
        //(5)调用线程实例的start()方法启动线程。
        thread1.start();

        //(2)创建实现了Callable接口的实现类对象。
        MyThread mythread2=new MyThread();
        //(3)通过FutureTask线程结果处理类的有参构造方法来封装Callable接口实现类对象
        FutureTask<Object> ft2=new FutureTask<>(mythread2);
        //(4)使用参数为FutureTask类对象的Thread有参构造方法创建Thread实例。
        Thread thread2=new Thread(ft2,"thread2");
        //(5)调用线程实例的start()方法启动线程。
        thread2.start();
        System.out.println("thread1返回的结果为:"+ft1.get());
        System.out.println("thread2返回的结果为:"+ft2.get());

    }
}

FutureTask实现了RunnableFuture接口,RunnableFuture接口继承自Runnable接口和Future接口。所以FutureTask的本质是Runnable接口和Future接口的实现类。

Future接口的方法:

boolean cancel(boolean running)用于取消任务,running参数为是否取消还在运行的任务

boolean isCancelled()用于判断任务是否被取消成功

boolean isDone()判断任务是否已经完成

V get()用于获取执行结果(返回值),这个方法会发生阻塞,一直等到任务执行完才会执行结果。

V get(long timeout,TimeUnit unit)如果指定时间内没有得到结果就返回null

四.3种多线程实现方式的对比分析。

实现Runnable接口比继承Thread类所更具有的优势:

 1.可以避免Java中的单继承的局限性

2.线程池只能放入实现Runnable或Callable类的线程,不能直接放入继承Thread类的线程

其它方面的对比,我通过下面一个多窗口售票的案例来对比分析更多优略:

4个窗口卖100张票,这100张票可以看作是共享资源。4个售票窗口可以看成是4个线程。

我们先用第1种方法(继承Thread类)来实现这个案例:

代码语言:javascript
复制
package demo04;

//定义一个继承自thread类的子类
class TicketWindow extends Thread{
    private int tickets=100;

    @Override
    public void run() {
        while (true){
            if (tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
            }
        }
    }
}

public class test01 {
    public static void main(String[] args) {
        //创建4个线程对象来作为窗口卖票
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
        new TicketWindow().start();
    }
}

 运行后会发现重复卖票了,也就是说票没有共享。同一张票被卖了四次。这很明显不是我们想要的答案吧。

这时候就要通过实现Runnable接口来实现多线程:

代码语言:javascript
复制
package demo04;

//定义一个实现了Runnable接口的实现类
class TicketWindow implements Runnable{
    private int tickets=100;
    @Override
    public void run() {
        while (true){
            if (tickets>0){
                System.out.println(Thread.currentThread().getName()+"正在发售第"+tickets--+"张票");
            }
        }
    }
}


public class test02 {
    public static void main(String[] args) {
        //创建TicketWindow实例对象
        TicketWindow ticketWindow=new TicketWindow();
        new Thread(ticketWindow,"窗口1").start();
        new Thread(ticketWindow,"窗口2").start();
        new Thread(ticketWindow,"窗口3").start();
        new Thread(ticketWindow,"窗口4").start();
    }
}

 这次就可以共享资源了,不会出现票被重复卖的情况。

五.后台线程

新创建的线程默认是前台线程,对于java程序来说,只要有前台线程在运行,这个进程就不会关闭。如果某个线程在启动之前(start()方法)调用了setDaemon(true)语句,这个线程就会变成一个后台线程。

java中,每次程序运行至少启动2个线程,1个是main线程,另一个是垃圾收集线程。因为每当执行一个java程序时,都会启动一个jvm,每一个jvm其实就是操作系统中启动了一个进程。

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2021-10-12,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一.进程和多线程的概述以及使用场景
  • 二.并发和并行
  • 三.线程的创建
    • 1.Thread类实现多线程
      •  2.Runnable接口实现多线程
        •  3.Callable接口实现多线程
        • 四.3种多线程实现方式的对比分析。
        • 五.后台线程
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档