我是靠谱客的博主 俭朴耳机,最近开发中收集的这篇文章主要介绍JavaEE | 多线程基础1 多线程与多进程2 多线程实现的三种方式3 线程的设置4 线程控制5 用内部类开启线程,觉得挺不错的,现在分享给大家,希望可以做个参考。

概述

JavaEE | 多线程基础

  • 1 多线程与多进程
    • 1.1 进程
    • 1.2 线程
    • 1.3 线程与进程
    • 1.4 并行和并发
    • 1.5 JVM的启动是多线程的
  • 2 多线程实现的三种方式
    • 2.1 方式一:继承Thread类
      • 2.1.1 实现步骤
      • 2.1.2 实例
    • 2.2 方式二:Runnable接口 (避免单继承的局限性,run方法没有返回值)
      • 2.2.1 实现步骤
      • 2.2.2 实例
    • 2.3 方式三:Callable 接口(call方法有返回值)
      • 2.3.1 实现步骤
      • 2.2.2 实例
  • 3 线程的设置
    • 3.1 获取和设置线程对象名称
    • 3.2 线程调度与线程的优先级
  • 4 线程控制
    • 4.1 休眠线程(sleep)
    • 4.2 加入线程(join)
    • 4.3 礼让线程(yield)
    • 4.4 守护线程(setDaemon)
    • 4.6 中断线程(interrupt)
  • 5 用内部类开启线程

1 多线程与多进程

1.1 进程

进程的定义:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位
每一个进程都有它自己的内存空间和系统资源。

1.2 线程

线程的定义:在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。

多线程的作用:多线程的作用不是提高执行速度,而是为了提高应用程序的使用率。

我们程序在运行的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到 CPU的执行权的概率应该比较单线程程序抢到的概率要大.那么也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了程序的使用率.但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以多线程具有随机性。

1.3 线程与进程

进程是拥有资源的基本单位, 线程是CPU调度的基本单位。

我们一般是进行多线程代码编写,而不是多进程程序,这是因为不同进程之间共享数据是异常困难的,而在同一个进程中不同的线程之间,方便进行资源的共享。

1.4 并行和并发

并行:指应用能够同时执行不同的任务
并发:指应用能够交替执行不同的任务,

并发类似于Java多线程的原理, 多线程并非是如果你开两个线程同时执行多个任务, 执行, 就是在你几乎不可能察觉到的速度不断去切换这两个任务, 已达到"同时执行效果"

1.5 JVM的启动是多线程的

Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。 该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。 所以 main方法运行在主线程中。

JVM的启动是多线程的:JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的

2 多线程实现的三种方式

2.1 方式一:继承Thread类

2.1.1 实现步骤

  1. 定义一个类,继承Thread类,创建线程
  2. 重写Thread类中的run方法
    run方法中的代码的书写原则: 一般是比较耗时的代码
  3. 创建定义的类的对象
  4. 开启线程

注意事项:

  1. 调用run方法并没有开启线程。调用run()方法,仅仅是使用一个对象,调用一个方法,让这个方法执行而已。
  2. 正确开启线程的方式是调用start() 开启线程,start()方法会先做一些准备工作,如线程的栈、线程的状态等信息后,再由线程去调用run()去执行run方法里面的代码。
  3. 同一个线程不要多次开启,会抛异常:IllegalThreadStateException
  4. 定义的类(本例中指的是MyThread类)中,可以写其他的方法,但是只有run方法中的代码被线程执行

2.1.2 实例

public class MyThread extends Thread {
    @Override
    public void run() {
        //这个run方法就是需要线程来执行的代码,一般耗时的操作,我们就会写在run方法里面,让线程去执行
        Thread thread = Thread.currentThread();
        String name = thread.getName();

        for (int i = 0; i < 1000; i++) {
            System.out.println(name+":"+i);
        }
    }
}

public class MyTest1 {
    public static void main(String[] args) {
           MyThread myThread = new MyThread();
        myThread.start();//此行代码,开启线程

    }
}

2.2 方式二:Runnable接口 (避免单继承的局限性,run方法没有返回值)

2.2.1 实现步骤

  1. 创建一个类,实现Runnable接口,重写改接口中的run方法
  2. 创建Thread类,对象,将Runnable接口的子类对象传递进来
  3. 调用start()方法开启线程

实现Runnable接口方式的好处:可以避免由于Java单继承带来的局限性,即MyRunnable1 还可以继承别的类

2.2.2 实例

public class MyTest1 {
    public static void main(String[] args) {
    
        MyRunnable1 myRunnable1 = new MyRunnable1();
        Thread thread = new Thread(myRunnable1);
        thread.setName("A");
        thread.start();
    }
}

public class MyRunnable1 implements Runnable{
    @Override
    public void run() {
        //只能用Thread静态方法调用线程的名字
        String name = Thread.currentThread().getName();
        //不能this.getname方法,因为MyRunnable1并不是Thread的子类
        System.out.println(name+"线程执行了");
    }
}

2.3 方式三:Callable 接口(call方法有返回值)

2.3.1 实现步骤

  1. 创建一个类实现Callable 接口
  2. 创建一个FutureTask类将Callable接口的子类对象作为参数传进去(执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果)
  3. 创建Thread类,将FutureTask对象作为参数传进去
  4. 开启线程

特点:相较于实现 Runnable 接口的方式2,Callable 接口可以有返回值,并且可以抛出异常

2.2.2 实例


public class MyTest1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
        Thread thread = new Thread(futureTask);
        thread.setName("A");
        thread.start();
        //获取线程的返回值
        Integer integer = futureTask.get();
        System.out.println(integer);

    }
}


public class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        //call方法就是线程要执行的方法
        String name = Thread.currentThread().getName();
        System.out.println(name+"线程执行了");
        int sum=0;
        for (int i = 0; i < 100; i++) {
            sum+=i;
        }
        return sum;
    }
}

3 线程的设置

3.1 获取和设置线程对象名称

  1. Thread类的基本获取和设置方法:
    public final String getName():获取线程名称
    public final void setName(String name):设置线程名称

  2. 获取当前所在的线程名称
    public static Thread currentThread():获取当前执行的线程

3.2 线程调度与线程的优先级

  1. 线程调度

    假如我们的计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到 CPU时间片,也就是使用权,才可以执行指令。那么Java是如何对线程进行调用的呢?

  2. 两种调度模型

    a) 分时调度模型 :所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片

    b) 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。

    Java使用的是抢占式调度模型。

  3. 线程优先级的设置和获取
    public final int getPriority():获取线程的优先级
    public final void setPriority(int newPriority):设置线程的优先级 (范围:1-10 ;默认优先级:5)

4 线程控制

4.1 休眠线程(sleep)

线程休眠(sleep)指让线程处于阻塞状态。

  • public static void sleep(long millis)
public class MyTest4 {
    public static void main(String[] args) throws InterruptedException {

        //线程控制之休眠线程
        //public static void sleep(long millis)

        MyThread2 th = new MyThread2("线程");
        th.start();

        //让主线程休眠
        Thread.sleep(1000);
        System.out.println("主线程");
    }
}



public class MyThread2 extends Thread{
        //通过构造,给线程设置名字
    public MyThread2(String name){
        super(name);
    }

    @Override
    public void run() {
        //获取当前线程名字的两种方法
        String name = this.getName();
        String name1 = Thread.currentThread().getName();

        //sleep方法的异常只能抓,不能抛,因为其父类Thread的run方法没有抛出异常
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 1000; i++) {
            System.out.println(name+":"+i);
        }

    }
}

4.2 加入线程(join)

public final void join(): 等待该线程执行完毕了以后,其他线程才能再次执行,实际实现的功能是将并发执行变为串行。

注意:在线程启动(start方法)之后,再调用join方法。

public class MyTest5 {
    public static void main(String[] args) throws InterruptedException {
        //线程控制之加入线程
        //public final void join()
        //等待该线程执行完毕了以后,其他线程才能再次执行
        //注意:在线程启动之后,再调用join方法

        //A、B、C三个线程串行执行
        MyThread2 th1 = new MyThread2("A");
        MyThread2 th2 = new MyThread2("B");
        MyThread2 th3 = new MyThread2("C");
        th1.start();
        th1.join();
        th2.start();
        th2.join();
        th3.start();
        th3.join();
    }
}

4.3 礼让线程(yield)

public static void yield():暂停当前正在执行的线程对象,并执行其他线程。

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread("张飞");
        MyThread th2 = new MyThread("刘备");
        th1.start();
        th2.start();
    }
}


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

    @Override
    public void run(){
        //String name = this.getName();
        String name1 = Thread.currentThread().getName();

        for (int i = 0; i < 100; i++) {
            Thread.yield();
            System.out.println(name1+i);
        }
    }
}

按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显.

原因:
这个礼让是要暂停当前正在执行的线程,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权.

4.4 守护线程(setDaemon)

public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。当正在运行的线程都是守护线程时,Java 虚拟机退出。

注意事项:

  • 调用线程对象的方法setDaemon(true),设置线程为守护线程。
  • thread.setDaemon(true)必须在thread.start()之前设置。
  • 在Daemon线程中产生的新线程也是Daemon的。
  • 不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑。
    因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
public class MyTest7 {
    public static void main(String[] args) {
        MyThread3 th1 = new MyThread3("A");
        MyThread3 th2 = new MyThread3("B");
        MyThread3 th3 = new MyThread3("C");
        th1.setDaemon(true);
        th2.setDaemon(true);
        th3.setDaemon(true);
        th1.start();
        th2.start();
        th3.start();
        for (int i = 0; i < 100; i++) {
            String name = Thread.currentThread().getName();
            System.out.println(name+":"+i);
        }
    }
}


public class MyThread3 extends Thread{
    public MyThread3(String name) {
        super(name);
    }
    @Override
    public void run() {
        String name = this.getName();
        for (int i = 0; i < 1000; i++) {
            Thread.yield();
            System.out.println(name+":"+i);
        }
    }
}

1.用户线程和守护线程的区别
用户线程和守护线程都是线程,区别是Java虚拟机在所有用户线程dead后,程序就会结束,而不管是否还有守护线程还在运行,若守护线程还在运行,则会马上结束。很好理解,守护线程是用来辅助用户线程的,如公司的保安和员工,各司其职,当员工都离开后,保安自然下班了。

2.用户线程和守护线程的适用场景
由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。

4.Java守护线程和Linux守护进程
两者不是一个概念。Linux守护进程是后台服务进程,没有控制台。
在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了。所以守护进程并非一定需要的

4.6 中断线程(interrupt)

public final void stop(): 停止线程的运行(该方法已废弃,不推荐使用!)
public void interrupt(): 中断线程,作用是打破线程的阻塞状态。
(当线程调用wait(),sleep(long time)方法的时候处于阻塞状态,可以通过interrupt方法清除阻塞)

        MyThread2 th = new MyThread2("A");
        th.start(); //th中阻塞了3s
        th.interrupt();//此行代码可以打断th的阻塞状态

5 用内部类开启线程

public class MyTest {
    public static void main(String[] args) {
        //用内部类开启线程


        //方法一:重新Runnable内部类的run方法
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("aaa");
            }
        }).start();


        //方法二:重新Thread的run方法
        new Thread(){
            @Override
            public void run() {
                System.out.println("bbb");
            }
        }.start();



        Thread th = new Thread() {
            @Override
            public void run() {
                System.out.println("ccc");
            }
        };
        th.start();


    }
}

最后

以上就是俭朴耳机为你收集整理的JavaEE | 多线程基础1 多线程与多进程2 多线程实现的三种方式3 线程的设置4 线程控制5 用内部类开启线程的全部内容,希望文章能够帮你解决JavaEE | 多线程基础1 多线程与多进程2 多线程实现的三种方式3 线程的设置4 线程控制5 用内部类开启线程所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(40)

评论列表共有 0 条评论

立即
投稿
返回
顶部