Java多线程基础全解

文章目录

  • 并行与并发
  • 线程引入
  • 线程与进程的概念
  • 线程的生命周期
  • 多线程开发
    • Java程序运行的原理
    • 线程的创建方式
      • 方式1
        • run()与start()的区别
      • 方式2
      • 方式3
    • 线程的命名
    • 多线程复制文件
    • 线程优先级
      • Java中如何调度线程
  • 多线程下的线程控制
    • 休眠线程
    • 加入线程
    • 礼让线程
      • sleep()和yield()的区别
    • 守护线程
      • 用户线程和守护线程的区别
    • 中断线程

并行与并发

  • 概念解释:

1、解释一:并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生;
2、解释二:并行是在不同实体上的多个事件,并发是在同一实体上的多个事件;
3、解释三:并行是在一台处理器上"同时"处理多个任务,并发是在多台处理器上同时处理多个任务。如 hadoop 分布式集群;

所以并发编程的目标是充分的利用处理器的每一个核,以达到最高的处理性能;

  • 并行(parallel):指在同一时刻,有多条指令在多个处理器上同时执行;所以无论从微观还是从宏观来看,二者都是一起执行的;
    Java多线程基础全解_第1张图片
  • 并发(concurrency):指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行;

Java多线程基础全解_第2张图片

  • 并行与并发的应用环境:

1、并行在多处理器系统中存在,而并发可以在单处理器和多处理器系统中都存在;
2、并发能够在单处理器系统中存在是因为并发是并行的假象;
3、并行要求程序能够同时执行多个操作,而并发只是要求程序假装同时执行多个操作(每个小时间片执行一个操作,多个操作快速切换执行);

  • 并发并行运行的原理:

当有多个线程在操作时,如果系统只有一个 CPU,则它根本不可能真正同时进行一个以上的线程,它只能把 CPU 运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状态,这种方式我们称之为并发(Concurrent)
Java多线程基础全解_第3张图片
当系统有一个以上 CPU 时,则线程的操作有可能非并发。当一个 CPU 执行一个线程时,另一个 CPU 可以执行另一个线程,两个线程互不抢占 CPU 资源,可以同时进行,这种方式我们称之为并行(Parallel)
Java多线程基础全解_第4张图片

线程引入

  • 我们的之前的代码执行路径只有一条,就是main(),顺序执行代码,这种环境称之为单线程环境;但假如程序途中某个环节比较耗时(比如进行大的文件的操作),在他后面的程序必须等待这个耗时的程序执行完成之后再去执行,这样用户体验很不好;
  • 我们可以:
    Java多线程基础全解_第5张图片

线程与进程的概念

  • 进程:正在运行的应用程序(QQ、微信、IDEA、WPS),一个进程中最少需要有一个线程,如果线程数为0了,进程被迫也就停止了;
  • 线程:它依赖于进程存在,没有进程,谈不上线程

进程是拥有资源的基本单位,进程间的资源是不共享的;
线程是程序使用CPU的基本单位,也就是CPU调度的基本单位;

进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程;(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小;

进程和线程之间的关系:

比如QQ音乐运行了,这时候这个应用程序属于一个进程,QQ音乐里面可能需要执行很多不同的任务,比如歌曲下载、歌曲的播放……,这些任务都属于线程

现在的计算机,Windows电脑支持多进程,可以同时多个进程运行;我在电脑上开启了WPS和QQ音乐,我感觉我一边在编辑文字,一边在听歌,是同时进行的,那么 计算机在同一个时间点上,是同时进行多个进程的吗

  • 当然不是,因为计算机在同一时间点上只能运行一个进程,你的感觉是多个进程在同时执行,这是因为CPU在多个进程之间高速的切换,我们指的是 单核CPU用来处理任务的核心数量),你的人耳和眼睛根本感觉不到;当然,4核或者多核CPU可以实现真正意义上的同时运行多个进程;

多进程的意义在哪?

  • 多进程存在的意义不是提高了执行速度,而是 提高了CPU的使用率
  • 我们程序在运行时的使用,都是在抢CPU的时间片(执行权),如果是多线程的程序,那么在抢到CPU的执行权的概率应该比较单线程程序抢到的概率要大;也就是说,CPU在多线程程序中执行的时间要比单线程多,所以就提高了CPU的使用率;
  • 因为CPU本身作为硬件,一直在不断的提高自己的性能,因此我们支持多线程环境,以便于CPU的资源不被浪费;
  • 但是即使是多线程程序,那么他们中的哪个线程能抢占到CPU的资源呢,这个是不确定的,所以 多线程具有随机性

线程的生命周期

1、在程序开发当中,将一个对象从实例化完成,到这个对象使用结束,并被销毁,这样的过程称之为对象的一生,这就类似于人的一生;
2、线程的生命周期:一个线程被实例化完成,(线程在Java中也是一个对象),到这个线程销毁的过程;
3、线程的状态:

  • 新生态:New

一个线程被实例化完成,但是还没有做任何操作;

  • 就绪态:Ready

一个线程已经被开启,已经开始去争抢CPU时间片;

  • 运行态:Run

一个线程抢到了CPU时间片,开始运行这个线程的逻辑;

  • 阻塞态:Interrupt

一个线程在运行的过程中,受到某些操作的影响,放弃了已经获取到的CPU时间片,并且不再去参与CPU时间片的争抢,此时线程处于挂起状态(暂停);

  • 死亡态:Dead

一个线程对象需要被销毁;

4、生命周期图
Java多线程基础全解_第6张图片

多线程开发

Java程序运行的原理

  • Java程序运行原理:

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

  • JVM的启动是多线程的吗:

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

  • 进程是由系统创建,Java本身不能直接调用系统功能,也就没有办法实现多线程,但是Java给定一个类Thread来描述线程,它的底层与C/C++代码进行交互,由这些C/C++代码再与底层系统打交道,这个类辅助我们创建线程、开启线程;

线程的创建方式

方式1

自定义类继承Thread,重写该类里面的run();一般来说,我们把一些耗时的代码写在run(),而不会写到主线程中,阻塞主线程;
run()里面本质上就是需要并发执行的任务;
Java多线程基础全解_第7张图片

  • 代码演示:
---测试类:
public class MyTest {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.run();
        //这是一些耗时的代码
    }
}
---自定义线程类:
public class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("这是一些耗时的代码");
    }
}

这里注意,你没有发现吗?其实调用自定义线程类里面的run(),只是做了简单的创建自定义类,new对象,调方法,线程并没有真正意义上的开启,或者说线程不会进入就绪态;

run()与start()的区别

正确开启线程的方法:

  • new对象,使对象调用start()方法,是线程开启的正确方法
  • 当用start()开始一个线程后,线程就进入就绪状态,使线程所代表的虚拟处理机处于可运行状态,这意味着它可以由JVM调度并执行。但是这并不意味着线程就会立即运行。只有当cpu分配时间片时,这个线程获得时间片时,才开始执行run()方法
  • start()来启动线程,无需等待run()里面的代码执行完毕,可以直接继续执行下面的代码;这里把run()叫做线程体,它包含了要执行的这个线程的内容,run()运行结束,此线程终止,然后CPU再调度其他线程;
  • run()当做普通方法的方式调用,程序还是要顺序执行,要等待run()方法体执行完毕之后,才可以继续执行下面的代码;程序只有主线程这一个线程,其程序执行路径还是只有一条,这样就没有达到写线程的目的;
  • 需要注意的是,线程不能重复开启;
  • 代码演示:
public class MyTest1 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();
        System.out.println("这是后面的代码");
        /*这是后面的代码
        这是一些耗时的代码*/
    }
}

方式2

  • 实现Runnable接口,这种方式扩展性强,实现一个接口 还可以再去继承其他类
    a:如何获取线程名称
    b:如何给线程设置名称
    c:实现接口方式的好处
  • 可以避免由于Java单继承带来的局限性;
---测试类:
public class MyTest {
    public static void main(String[] args) {
        MyRunnable myRunnable = new MyRunnable();
        Thread thread = new Thread(myRunnable);
        thread.start();
        //Thread-0这是一些耗时代码
    }
}
----自定义类:
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "这是一些耗时代码");
    }
}

Runnable本质上就是一个任务,它是一个接口,里面只有一个run();
注意:这个类里面不能调用this.getName()来获取线程名字,因为他本质不是一个Thread;

方式3

  • 实现 Callable 接口。 相较于实现 Runnable 接口的方式,方法可以有返回值,并且可以抛出异常。
  • 执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果,( FutureTask 是 Future 接口的实现类);
  • 实现步骤:
    1.创建一个类实现Callable 接口
    2.创建一个FutureTask类将Callable接口的子类对象作为参数传进去
    3.创建Thread类,将FutureTask对象作为参数传进去
    4.开启线程
---测试类:
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        MyCallable myCallable = new MyCallable(5);
        FutureTask<Integer> task = new FutureTask<Integer>(myCallable);

        Thread thread = new Thread(task);
        thread.start();
        System.out.println("这个任务返回的结果是:" + task.get());
//        这是一个任务Thread-0
//        这个任务返回的结果是:10
    }
}
---自定义类:
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
    private int num = 0;

    public MyCallable(int num) {
        this.num = num;
    }

    @Override
    public Integer call() throws Exception {
        System.out.println("这是一个任务" + Thread.currentThread().getName());
        int sum = 0;
        for (int i = 0; i < num; i++) {
            sum += i;
        }
        return sum;
    }
}
  • Runnable 任务 让线程来执行 run() 没有返回值,这个方法不能抛出异常,只能抓;
  • Callable 任务 让线程来执行 call() 有返回值,而且可以抛出异常。

线程的命名

public final String getName()
返回该线程的名称;
public final void setName(String name)
改变线程名称,使之与参数 name 相同;
public Thread(String name)
线程的有参构造需要一个线程名字,可以直接给线程设置名字;当然,自定义线程类可以添加一个有参构造,将线程名字传递给父类构造super();

---测试类:
public class MyTest2 {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        MyThread myThread2 = new MyThread();

        myThread1.setName("1线程");
        myThread2.setName("2线程");

        myThread1.start();
        myThread2.start();

        System.out.println("主线程的代码");
        /*主线程的代码
        这是一些耗时的代码2线程
        这是一些耗时的代码1线程*/

        /*主线程的代码
        这是一些耗时的代码1线程
        这是一些耗时的代码2线程*/
    }
}
---自定义线程类:
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这是一些耗时的代码" + this.getName());
    }
}

两个线程交替执行,高速的切换,没有规律,线程的调度是随机性的;
多线程并发执行,抢占CPU执行权,哪个线程抢到,在某一时刻就执行哪个线程;

  • 获取线程的名称:

Thread.currentThread().getName():
静态方法,可以获取当前正在执行的线程名称;
线程对象.getName()
成员方法,只能使用对象调用,获取当前线程名称;

public class MyTest3 {
    public static void main(String[] args) {
        Thread thread = Thread.currentThread();
        //获取当前线程
        System.out.println(thread.getName());
        //main

        Thread thread1 = new Thread();
        System.out.println(thread1.getName());
        //Thread-0
    }
}
  • 上面我们给出的多线程,是属于同一类型的线程,它们都是new了同一个线程的对象,当然也可以存在不同类型的线程,它也属于多线程;
---测试类:
public class MyTest4 {
    public static void main(String[] args) {
        CopyText copyText = new CopyText();
        CopyVideo copyVideo = new CopyVideo();
        
        copyText.setName("text");
        copyVideo.setName("video");

        copyText.start();
        copyVideo.start();

        System.out.println("主线程后面的代码");
        /*主线程后面的代码
        这是复制文本文件的耗时操作text
        这是复制视频文件的耗时操作video*/
    }
}
----线程类:
public class CopyText extends Thread {
    @Override
    public void run() {
        System.out.println("这是复制文本文件的耗时操作" + this.getName());
    }
}
public class CopyVideo extends Thread {
    @Override
    public void run() {
        System.out.println("这是复制视频文件的耗时操作" + this.getName());
    }
}

多线程复制文件

需求:同一个种类的3个不同线程复制一个文件;
平均复制,分为3份,使用随机访问流;

代码实现:

---测试类:
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.RandomAccessFile;

public class MyTest {
    public static void main(String[] args) throws FileNotFoundException {
        //封装源文件
        File file = new File("D:\\桌面图标\\文件程序练习打包\\demo\\亲爱的旅人啊.mp3");
        //获取文件总大小
        long totalLenth = file.length();
        //定义线程数量
        int threadNum = 3;
        //计算平均每个线程要复制的文件的大小
        long aveSize = totalLenth / threadNum;

        long start = 0;
        long end = 0;

        for (int i = 0; i < threadNum; i++) {
            //定义复制文件的起始位置和结束位置
            start = aveSize * i;
            end = aveSize * (i + 1) - 1;

            new copyThread(start, end, file, new File("D:\\桌面图标\\copy.mp3")).start();

        }

        //如果不够平均分,剩余的字节使用另外一个线程完成
        if (totalLenth % threadNum != 0) {
            start = aveSize * threadNum;
            end = totalLenth;
            new copyThread(start, end, file, new File("D:\\桌面图标\\copy.mp3")).start();
        }

        System.out.println("复制完成了!");
    }
}
---复制文件的线程类
class copyThread extends Thread {

    private long end;
    private RandomAccessFile in = null;
    private RandomAccessFile out = null;
    private long start;

    /***
     *
     * @param start 要复制文件的起始位置
     * @param end 要复制文件的结束位置
     * @param srcFile 原文件夹
     * @param target 目标文件夹
     * @throws FileNotFoundException
     */
    public copyThread(long start, long end, File srcFile, File target) throws FileNotFoundException {

        this.start = start;
        this.end = end;

        in = new RandomAccessFile(srcFile, "rw");
        out = new RandomAccessFile(target, "rw");
    }

    @Override
    public void run() {
        //设置要复制文件与目标文件的起始指针位置
        try {
            in.seek(start);
            out.seek(start);

            int len = 0;
            byte[] bytes = new byte[1024 * 8];
            while (start < end && (len = in.read(bytes)) != -1) {
                start += len;
                out.write(bytes, 0, len);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
                out.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

线程优先级

  • 线程的执行

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

Java中如何调度线程

分时调度:UNIX;
抢占式调度:Windows、Java;

在上面的程序执行当中,有这样一个现象,无论子线程如何争抢CPU资源,随机调度,主线程的代码总会在子线程之前执行,这是为什么?

  • 其实这是由于线程的优先级;
  • 关于线程的优先级
  • 在一个线程中开启另外一个新线程,则新开线程称为该线程的子线程,子线程初始优先级与父线程相同,不过主线程先启动占用了cpu资源,这就造成了主线程不会被阻塞;
  • 如果存在主线程和子线程争抢cpu执行权的话,看运气,谁抢到就让谁执行;
  • 如何设置线程的优先级?

public final int getPriority()
返回线程的优先级;
public final void setPriority(int newPriority)
更改线程的优先级;

  • 线程的优先级用1-10之间的整数表示,数值越大优先级越高,默认的优先级为5;
  • 其实设置了优先级,也无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的并非没机会执行;
  • 因为线程的优先级的大小仅仅表示这个线程被CPU执行的概率增大了,但是我们都知道多线程具有随机性,所以有的时候一两次的运行说明不了问题;
---测试类:
public class MyTest {
    public static void main(String[] args) {
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        MyThread th3 = new MyThread();

        //设置线程的优先级 范围1--10
        th1.setPriority(Thread.MIN_PRIORITY);
        th3.setPriority(Thread.MAX_PRIORITY);

        //多个线程并发执行()多个抢占CPU的执行权,哪个线程抢到,在某一个时刻,就会执行哪个线程。线程的执行具有随机性。
        //获取线程的优先级
        int priority1 = th1.getPriority();
        int priority2 = th2.getPriority();
        int priority3 = th3.getPriority();
        //线程默认的优先级是5
        //Thread.MAX_PRIORITY;
        //Thread.MIN_PRIORITY;
        //Thread.NORM_PRIORITY

        System.out.println("线程的优先级" + priority1);
        //线程的优先级1
        System.out.println("线程的优先级" + priority2);
        //线程的优先级5
        System.out.println("线程的优先级" + priority3);
        //线程的优先级10
        
        th2.setPriority(2);

        th1.setName("zhangsan");
        th2.setName("lisi");
        th3.setName("wangwu");
        th1.start();
        th2.start();
        th3.start();
//        这是一些耗时的代码wangwu
//        这是一些耗时的代码zhangsan
//        这是一些耗时的代码lisi
    }
}
----自定义线程类:
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这是一些耗时的代码" + this.getName());
    }
}

多线程下的线程控制

休眠线程

让当前正在运行的线程休眠,睡一会;

public static void sleep(long millis) throws InterruptedException
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响,该线程不丢失任何监视器的所属权。sleep()平台移植性好

public class MyTest1 {
    public static void main(String[] args) throws InterruptedException {
        //单位是毫秒
        //让当前的主线程睡眠2秒
        Thread.sleep(2000);

        System.out.println("下面的代码");
    }
}

需求:我们在浏览一些网页或者打开APP的时候。总是会出现3s或者5s的广告,这个就可以通过线程休眠来做;

----测试类:
public class MyTest2 {
    public static void main(String[] args) throws InterruptedException {

        //线程休眠:可以让当前正在执行的线程, 睡一会。
        //Thread(String name) 分配新的 Thread 对象。
        //通过有参构造,可以给线程起个名字
        MyThread th1 = new MyThread("广告线程");
        MyThread th2 = new MyThread("主页线程");

        th1.setPriority(Thread.MAX_PRIORITY);
        th2.setPriority(Thread.MIN_PRIORITY);

        th1.start();
        th2.start();
//        广告线程执行了
//        主页线程执行了

    }
}
---线程类:
public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        try {
            if(this.getName().equals("广告线程")){
                Thread.sleep(3000); //单位是毫秒
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(this.getName() + "执行了");
    }
}

加入线程

加入线程: public final void join()
意思就是: 等待该线程执行完毕了以后,其他线程才能再次执行;
注意事项: 在线程启动之后,再调用方法;

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        MyThread th3 = new MyThread();
        th1.setName("刘备");
        th2.setName("关羽");
        th3.setName("张飞");
        th1.start();
        //注意:在线程启动之后, 在调用join()方法
        th1.join();
        th2.start();
        th2.join();
        th3.start();
        th3.join();
//        这是一些耗时的代码刘备
//        这是一些耗时的代码关羽
//        这是一些耗时的代码张飞
    }
}
public class MyThread extends Thread {
    @Override
    public void run() {
        System.out.println("这是一些耗时的代码" + this.getName());
    }
}
  • 这个方法的 作用 在于:

在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了;

没有加Join()

public class MyTest {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主线程运行开始!");
        MyThread th1 = new MyThread("A");
        MyThread th2 = new MyThread("B");
        th1.start();
        th2.start();
        System.out.println(Thread.currentThread().getName() + "主线程运行结束!");

    }
}

class MyThread extends Thread {
    private String name;

    public MyThread(String name) {
        super(name);
        this.name = name;
    }

    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程" + name + "运行 : " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
    }
}
main主线程运行开始!
main主线程运行结束!
A 线程运行开始!
子线程A运行 : 0
B 线程运行开始!
子线程B运行 : 0
子线程A运行 : 1
子线程B运行 : 1
子线程A运行 : 2
子线程B运行 : 2
子线程A运行 : 3
子线程B运行 : 3
子线程A运行 : 4
子线程B运行 : 4
A 线程运行结束!
B 线程运行结束!

会发现主线程比子线程结束的要早;

加入Join()

public class MyTest1 {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName() + "主线程运行开始!");
        MyThread th1 = new MyThread("A");
        MyThread th2 = new MyThread("B");
        th1.start();
        th2.start();
        try {
            th1.join();
            th2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "主线程运行结束!");
    }
}
main主线程运行开始!
A 线程运行开始!
B 线程运行开始!
子线程B运行 : 0
子线程A运行 : 0
子线程B运行 : 1
子线程A运行 : 1
子线程B运行 : 2
子线程A运行 : 2
子线程B运行 : 3
子线程A运行 : 3
子线程B运行 : 4
子线程A运行 : 4
B 线程运行结束!
A 线程运行结束!
main主线程运行结束!

主线程一定会等子线程都结束了才结束;

礼让线程

礼让线程: public static void yield():

  • 暂停当前正在执行的线程对象,并执行其他线程;
  • yield()应该做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
public class MyTest {
    public static void main(String[] args) {
        threadYield();
    }

    //线程的礼让:指的就是让当前的运行状态的线程释放自己的CPU资源,由运行状态回到就绪状态
    public static void threadYield() {
        Runnable r = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "=======" + i);
                    if (i == 3) {
                        Thread.yield();
                    }
                }
            }
        };
        Thread thread1 = new Thread(r, "thread-1");
        Thread thread2 = new Thread(r, "thread-2");

        thread1.start();
        thread2.start();
    }
}
某一次运行结果:
        thread-1=======0
        thread-2=======0
        thread-2=======1
        thread-2=======2
        thread-1=======1
        thread-2=======3
        thread-1=======2
        thread-1=======3
        thread-2=======4
        thread-2=======5
        thread-2=======6
        thread-2=======7
        thread-2=======8
        thread-2=======9
        thread-1=======4
        thread-1=======5
        thread-1=======6
        thread-1=======7
        thread-1=======8
        thread-1=======9
另外一次运行结果:
 		thread-1=======0
        thread-1=======1
        thread-1=======2
        thread-1=======3
        thread-1=======4
        thread-1=======5
        thread-2=======0
        thread-2=======1
        thread-2=======2
        thread-2=======3
        thread-1=======6
        thread-1=======7
        thread-1=======8
        thread-1=======9
        thread-2=======4
        thread-2=======5
        thread-2=======6
        thread-2=======7
        thread-2=======8
        thread-2=======9
  • 按照我们的想法,这个礼让应该是一个线程执行一次,但是通过我们的测试,效果好像不太明显;那是为什么呢?
    这个礼让是要暂停当前正在执行的线程,放弃了当前抢到的CPU时间片,这个暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,那么这个时候这个线程应该再次和其他线程抢占CPU的执行权;因此不是说A礼让了就一定要执行B;

sleep()和yield()的区别

①:sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行;yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。
②:sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出 CPU 占有权,但让出的时间是不可设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把 CPU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程;
③:另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield() 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得 CPU 占有权。在一个运行系统中,如果较高优先级的线程没有调用 sleep 方法,又没有受到 I/O 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行;

守护线程

守护线程: public final void setDaemon(boolean on):

  • true代表为守护线程,默认为false;
  • 将该线程标记为守护线程或用户线程,当正在运行的线程都是守护线程时,Java 虚拟机退出;
  • 该方法必须在启动线程前调用;
  • 一般子线程是守护线程,主线程死亡,守护线程立马挂掉;
---测试程序:
public class MyTest {
    public static void main(String[] args) {

        MyThread th1 = new MyThread();
        MyThread th2 = new MyThread();
        th1.setName("张飞");
        th2.setName("关羽");
        //设置为守护线程 当主线程死亡后,守护线程要立马死亡掉。
        //注意:setDaemon(true)该方法必须在启动线程前调用。
        th1.setDaemon(true);
        th2.setDaemon(true);
        th1.start();
        th2.start();

        Thread.currentThread().setName("刘备:主线程");
        for (int i = 0; i < 2; i++) {
            System.out.println(Thread.currentThread().getName() + "==" + i);
        }

        System.out.println(Thread.currentThread().getName() + "退出了");
//        刘备:主线程==0
//        刘备:主线程==1
//        张飞0
//        刘备:主线程退出了
//        张飞1
//        张飞2 
//        张飞3
//        张飞4
    }
}
----自定义线程类:
public class MyThread extends Thread {
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + i);
        }
    }
}

用户线程和守护线程的区别

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

  • 用户线程和守护线程的适用场景
    由两者的区别及dead时间点可知,守护线程不适合用于输入输出或计算等操作,因为用户线程执行完毕,程序就dead了,适用于辅助用户线程的场景,如JVM的垃圾回收,内存管理都是守护线程,还有就是在做数据库应用的时候,使用的数据库连接池,连接池本身也包含着很多后台线程,监听连接个数、超时时间、状态等。
  • 创建守护线程
    调用线程对象的方法setDaemon(true),设置线程为守护线程。
    1、thread.setDaemon(true)必须在thread.start()之前设置;
    2、在Daemon线程中产生的新线程也是Daemon的;
    3、不是所有的应用都可以分配给Daemon线程来进行服务,比如读写操作或者计算逻辑;因为Daemon Thread还没来得及进行操作,虚拟机可能已经退出了。
  • Java守护线程和Linux守护进程
    两者不是一个概念,Linux守护进程是后台服务进程,没有控制台。
    在Windows中,你可以运行javaw来达到释放控制台的目的,在Unix下你加&在命令的最后就行了,所以守护进程并非一定需要的;

中断线程

public final void stop():
停止线程的运行
public void interrupt():
中断线程,查看API可得当线程调用wait()、sleep(long time)方法的时候处于阻塞状态,可以通过这个方法清除阻塞;

public class MyTest {
    public static void main(String[] args) throws InterruptedException {
        MyThread th1 = new MyThread();
        th1.setName("张飞");
        th1.start();
        Thread.sleep(2000);
        //让线程死亡
        th1.stop();
        //清除线程的阻塞状态
        //th1.interrupt();
    }
}
  • interrupt():不要以为它是中断某个线程,它只是给线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的;

你可能感兴趣的:(JavaSE)