JavaSE-10笔记【多线程1(+2024新)】

news2025/2/27 4:41:25

文章目录

  • 1.进程与线程
  • 2.并发与并行
  • 3.线程的调度模型
  • 4.实现线程
    • 4.1 第一种方式:继承Thread
    • 4.2 第二种方式:实现Runnable接口
    • 4.3 t.start()和t.run()的本质区别?
    • 4.4 线程常用的三个方法
  • 5.线程的生命周期(把生命周期图背会)
    • 5.1 Thread.sleep()方法
      • 5.1.1 关于Thread.sleep()的面试题
    • 5.2 如何中断一个线程的睡眠
    • 5.3 如何强行终止一个线程
      • 5.3.1 已过时的方式
      • 5.3.2 推荐的方式
    • 5.4 守护线程
    • 5.5 定时器
    • 5.6 线程合并
    • 5.7 线程优先级
    • 5.8 线程让位

重点掌握:线程安全和线程通信。

1.进程与线程

  • 进程是指操作系统中的一段程序,它是一个正在执行中的程序实例,具有独立的内存空间和系统资源,如文件、网络端口等。在计算机程序执行时,先创建进程,再在进程中进行程序的执行。一般来说,一个进程可以包含多个线程。
  • 线程是指进程中的一个执行单元,是进程的一部分,它负责在进程中执行程序代码。每个线程都有自己的栈和程序计数器,并且可以共享进程的资源。多个线程可以在同一时刻执行不同的操作,从而提高了程序的执行效率。
  • 现代的操作系统是支持多进程的,也就是可以启动多个软件,一个软件就是一个进程。称为:多进程并发。而通常一个进程都是可以启动多个线程的。称为:多线程并发。
  • 多线程的作用:提高处理效率。(多线程的优点之一是能够使 CPU 在处理一个任务时同时处理多个线程,这样可以充分利用 CPU 的资源,提高 CPU 的利用效率。)
  • JVM规范中规定:堆内存、方法区 是线程共享的。虚拟机栈、本地方法栈、程序计数器 是每个线程私有的。
  • 关于Java程序的运行原理
    ①“java HelloWorld”执行后,会启动JVM,JVM的启动表示一个进程启动了。
    ②JVM进程会首先启动一个主线程(main-thread),主线程负责调用main方法。因此main方法是在主线程中运行的。
    ③除了主线程之外,还启动了一个垃圾回收线程。因此启动JVM,至少启动了两个线程。
    ④在main方法的执行过程中,程序员可以手动创建其他线程对象并启动。

在这里插入图片描述

2.并发与并行

  1. 并发(concurrency)

使用单核CPU的时候,同一时刻只能有一条指令执行,但多个指令被快速的轮换执行,使得在宏观上具有多个指令同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干端,使多个指令快速交替的执行。
在这里插入图片描述
如上图所示,假设只有一个CPU资源,线程之间要竞争得到执行机会。图中的第一个阶段,在A执行的过程中,B、C不会执行,因为这段时间内这个CPU资源被A竞争到了,同理,第二阶段只有B在执行,第三阶段只有C在执行。其实,并发过程中,A、B、C并不是同时进行的(微观角度),但又是同时进行的(宏观角度)。
在同一个时间点上,一个CPU只能支持一个线程在执行。因为CPU运行的速度很快,CPU使用抢占式调度模式在多个线程间进行着高速的切换,因此我们看起来的感觉就像是多线程一样,也就是看上去就是在同一时刻运行。

  1. 并行(parallellism)
    使用多核CPU的时候,同一时刻,有多条指令在多个CPU上同时执行。
    在这里插入图片描述
    如图所示,在同一时刻,ABC都是同时执行(微观、宏观)。

  2. 并发编程与并行编程
    ① 在CPU比较繁忙(假设为单核CPU),如果开启了很多个线程,则只能为一个线程分配仅有的CPU资源,这些线程就会为自己尽量多抢时间片,这就是通过多线程实现并发,线程之间会竞争CPU资源争取执行机会。
    ② 在CPU资源比较充足的时候,一个进程内的多个线程,可以被分配到不同的CPU资源,这就是通过多线程实现并行。
    ③ 至于多线程实现的是并发还是并行?上面所说,所写多线程可能被分配到一个CPU内核中执行,也可能被分配到不同CPU执行,分配过程是操作系统所为,不可人为控制。所以,如果有人问所写的多线程程序是并发还是并行的?答案其实是都有可能。

总结: 单核CPU上的多线程,只是由操作系统来完成多任务间对CPU的运行切换,并非真正意义上的并发。随着多核CPU的出现,也就意味着不同的线程能被不同的CPU核得到真正意义的并行执行,故而多线程技术得到广泛应用。
不管并发还是并行,都提高了程序对CPU资源的利用率,最大限度地利用CPU资源,而我们使用多线程的目的就是为了提高CPU资源的利用率。

3.线程的调度模型

如果多个线程被分配到一个CPU内核中执行,则同一时刻只能允许有一个线程能获得CPU的执行权,那么进程中的多个线程就会抢夺CPU的执行权,这就是涉及到线程调度策略。
两种调度策略:

  • 分时调度模型
    所有线程轮流使用CPU的执行权,并且平均的分配每个线程占用的CPU的时间。
  • 抢占式调度模型
    让优先级高的线程以较大的概率优先获得CPU的执行权,如果线程的优先级相同,那么就会随机选择一个线程获得CPU的执行权,而Java采用的就是抢占式调用。

4.实现线程

4.1 第一种方式:继承Thread

①编写一个类继承Thread,重写run方法。
②创建线程对象:Thread t = new MyThread();
③启动线程:t.start();

示例代码:

package threadtest;

/**
 * 在java语言中,实现线程有两种方式,第一种方式:
 * 第一步:编写一个类继承java.lang.Thread
 * 第二步:重写run()方法
 * 第三步:new线程对象
 * 第四步:调用线程对象的start()方法来启动线程
 */
public class ThreadTest01 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t = new MyThread();

        //直接调用run()方法,不会启动新的线程
        //java中有一个语法规则:对于方法体重的代码,必须遵循自上而下的顺序逐次执行
        //run()方法不结束,main方法是无法继续执行的
        //t.run();
        
        //调用start()方法,启动线程
        //java中有一个语法规则:对于方法体中的代码,必须遵循自上而下的顺序逐次执行
        //start()方法不结束,main方法是无法继续执行的
        //start()方法瞬间就会结束,因为这个方法的作用是启动一个新的线程,只要新线程启动成功了,start()就结束了
        t.start();

        //下面的代码在main方法中,因此其属于在主线程中
        for (int i = 0; i < 100; i++) {
            System.out.println("main-->" + i);
        }
    }
}

/**
 * 自定义一个线程类
 * java.lang.Thread本身就是一个线程。
 * MyThread继承Thread,因此MyThread本身也是一个线程。
 */
class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("Thread-->" + i);
        }
    }
}

运行结果:

Thread-->0
Thread-->1
Thread-->2
Thread-->3
Thread-->4
Thread-->5
Thread-->6
Thread-->7
Thread-->8
Thread-->9
Thread-->10
Thread-->11
Thread-->12
main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
main-->11
Thread-->13
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19
main-->20
main-->21
main-->22
main-->23
main-->24
main-->25
Thread-->14
Thread-->15
Thread-->16
Thread-->17
Thread-->18
Thread-->19
Thread-->20
main-->26
Thread-->21
Thread-->22
Thread-->23
Thread-->24
Thread-->25
Thread-->26
Thread-->27
Thread-->28
Thread-->29
Thread-->30
Thread-->31
Thread-->32
Thread-->33
Thread-->34
Thread-->35
Thread-->36
Thread-->37
Thread-->38
Thread-->39
Thread-->40
Thread-->41
Thread-->42
Thread-->43
Thread-->44
Thread-->45
Thread-->46
Thread-->47
Thread-->48
Thread-->49
Thread-->50
Thread-->51
Thread-->52
Thread-->53
Thread-->54
Thread-->55
Thread-->56
Thread-->57
Thread-->58
Thread-->59
main-->27
Thread-->60
Thread-->61
Thread-->62
Thread-->63
Thread-->64
Thread-->65
Thread-->66
Thread-->67
main-->28
main-->29
main-->30
main-->31
main-->32
main-->33
main-->34
main-->35
main-->36
main-->37
main-->38
main-->39
main-->40
main-->41
main-->42
main-->43
Thread-->68
Thread-->69
Thread-->70
Thread-->71
Thread-->72
Thread-->73
Thread-->74
main-->44
Thread-->75
Thread-->76
Thread-->77
Thread-->78
Thread-->79
Thread-->80
main-->45
main-->46
Thread-->81
Thread-->82
Thread-->83
Thread-->84
Thread-->85
Thread-->86
Thread-->87
Thread-->88
Thread-->89
Thread-->90
main-->47
main-->48
Thread-->91
Thread-->92
Thread-->93
Thread-->94
main-->49
Thread-->95
Thread-->96
Thread-->97
Thread-->98
Thread-->99
main-->50
main-->51
main-->52
main-->53
main-->54
main-->55
main-->56
main-->57
main-->58
main-->59
main-->60
main-->61
main-->62
main-->63
main-->64
main-->65
main-->66
main-->67
main-->68
main-->69
main-->70
main-->71
main-->72
main-->73
main-->74
main-->75
main-->76
main-->77
main-->78
main-->79
main-->80
main-->81
main-->82
main-->83
main-->84
main-->85
main-->86
main-->87
main-->88
main-->89
main-->90
main-->91
main-->92
main-->93
main-->94
main-->95
main-->96
main-->97
main-->98
main-->99

Process finished with exit code 0

直接调用run()方法的内存结构图:
在这里插入图片描述

调用start()方法的内存结构图:
在这里插入图片描述

4.2 第二种方式:实现Runnable接口

①编写一个类实现Runnable接口,实现run方法。
②创建线程对象:Thread t = new Thread(new MyRunnable());
③启动线程:t.start();

示例代码:

package threadtest;

/**
 * 在java语言中,实现线程有两种方式,第二种方式:
 * 第一步:编写一个类实现java.lang.Runnable接口(可运行接口)
 * 第二步:实现接口中的run方法
 * 第三步:new线程对象
 * 第四步:调用线程的start方法启动线程
 */
public class ThreadTest02 {
    public static void main(String[] args) {
        //创建Runnable对象
        Runnable r = new MyRunnable();

        //创建线程对象
        Thread t = new Thread(r);

        //启动线程
        t.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("main-->" + i);
        }
    }
}

/**
 * 创建一个类实现Runnable接口
 */
class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("Thread-->" + i);
        }
    }
}

运行结果:

main-->0
Thread-->0
Thread-->1
Thread-->2
Thread-->3
Thread-->4
Thread-->5
Thread-->6
Thread-->7
Thread-->8
Thread-->9
Thread-->10
Thread-->11
Thread-->12
Thread-->13
main-->1
main-->2
Thread-->14
Thread-->15
Thread-->16
Thread-->17
Thread-->18
Thread-->19
Thread-->20
Thread-->21
Thread-->22
Thread-->23
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
Thread-->24
Thread-->25
Thread-->26
Thread-->27
Thread-->28
Thread-->29
Thread-->30
Thread-->31
Thread-->32
Thread-->33
Thread-->34
Thread-->35
Thread-->36
Thread-->37
Thread-->38
Thread-->39
Thread-->40
Thread-->41
Thread-->42
Thread-->43
Thread-->44
Thread-->45
Thread-->46
Thread-->47
Thread-->48
Thread-->49
Thread-->50
Thread-->51
Thread-->52
Thread-->53
Thread-->54
Thread-->55
Thread-->56
Thread-->57
Thread-->58
Thread-->59
Thread-->60
Thread-->61
Thread-->62
Thread-->63
Thread-->64
Thread-->65
main-->19
main-->20
Thread-->66
Thread-->67
Thread-->68
Thread-->69
Thread-->70
Thread-->71
Thread-->72
Thread-->73
Thread-->74
Thread-->75
Thread-->76
Thread-->77
Thread-->78
Thread-->79
Thread-->80
Thread-->81
Thread-->82
Thread-->83
Thread-->84
Thread-->85
Thread-->86
Thread-->87
Thread-->88
Thread-->89
Thread-->90
Thread-->91
Thread-->92
Thread-->93
Thread-->94
Thread-->95
Thread-->96
Thread-->97
Thread-->98
Thread-->99
main-->21
main-->22
main-->23
main-->24
main-->25
main-->26
main-->27
main-->28
main-->29
main-->30
main-->31
main-->32
main-->33
main-->34
main-->35
main-->36
main-->37
main-->38
main-->39
main-->40
main-->41
main-->42
main-->43
main-->44
main-->45
main-->46
main-->47
main-->48
main-->49
main-->50
main-->51
main-->52
main-->53
main-->54
main-->55
main-->56
main-->57
main-->58
main-->59
main-->60
main-->61
main-->62
main-->63
main-->64
main-->65
main-->66
main-->67
main-->68
main-->69
main-->70
main-->71
main-->72
main-->73
main-->74
main-->75
main-->76
main-->77
main-->78
main-->79
main-->80
main-->81
main-->82
main-->83
main-->84
main-->85
main-->86
main-->87
main-->88
main-->89
main-->90
main-->91
main-->92
main-->93
main-->94
main-->95
main-->96
main-->97
main-->98
main-->99

Process finished with exit code 0

总结:
优先选择第二种方式:因为实现接口的同时,保留了类的继承。
第二种方式也可以使用匿名内部类如下:

Thread t = new Thread(new Runnable() {
  @Override
       public void run() {
           for (int i = 0; i < 100; i++) {
               System.out.println("main-->" + i);
           }  
       }
   });
t.start();

4.3 t.start()和t.run()的本质区别?

本质上没有区别,都是普通方法调用。只不过两个方法完成的任务不同。

  • t.run()是调用run方法。执行run方法中的业务代码。
  • t.start()是启动线程,只要线程启动了,start()方法就执行结束了。

4.4 线程常用的三个方法

实例方法:
①String getName(); 获取线程对象的名字;
②void setName(String name);修改线程对象的名字。
静态方法:
①static Thread currentThread();获取当前线程对象的引用。

package threadtest;

public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t1 = new MyNewThread();
        t1.start();

        Thread t2 = new MyNewThread();
        t2.start();

        Thread thread = Thread.currentThread();
        System.out.println("当前运行线程:" + thread.getName());

    }
}


class MyNewThread extends Thread{
    @Override
    public void run() {
        //获取当前运行线程的引用
        Thread t = Thread.currentThread();

        //获取当前运行线程的名字
        System.out.println("当前运行线程:" + t.getName()); 
    }
}

运行结果:
在这里插入图片描述
创建线程时,若没有给线程指定名字,则会给对应线程赋上默认的名字:Thread-xx
可以修改线程名字:

package threadtest;

public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t1 = new MyNewThread();
        t1.setName("t1");
        t1.start();

        Thread t2 = new MyNewThread();
        t2.setName("t2");
        t2.start();

        Thread thread = Thread.currentThread();
        System.out.println("当前运行线程:" + thread.getName());

    }
}


class MyNewThread extends Thread{
    @Override
    public void run() {
        //获取当前运行线程的引用
        Thread t = Thread.currentThread();

        //获取当前运行线程的名字
        System.out.println("当前运行线程:" + t.getName());
    }
}

运行结果:
在这里插入图片描述

也可以在创建线程时,通过构造方法为线程指定名字:

package threadtest;

public class ThreadTest03 {
    public static void main(String[] args) {
        Thread t1 = new MyNewThread("tt1");
//        t1.setName("t1");
        t1.start();

        Thread t2 = new MyNewThread("tt2");
//        t2.setName("t2");
        t2.start();

        Thread thread = Thread.currentThread();
        System.out.println("当前运行线程:" + thread.getName());

    }
}


class MyNewThread extends Thread{
    public MyNewThread() {
    }

	//有参构造方法
    public MyNewThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        //获取当前运行线程的引用
        Thread t = Thread.currentThread();

        //获取当前运行线程的名字
        System.out.println("当前运行线程:" + t.getName());
    }
}

运行结果:
在这里插入图片描述

5.线程的生命周期(把生命周期图背会)

线程的生命周期有7个状态:

  1. 新建状态;
  2. 就绪状态;
  3. 运行状态
  4. 超时等待状态;
  5. 等待状态(wait()方法);
  6. 阻塞状态(线程同步机制);
  7. 终止状态。
    在这里插入图片描述

5.1 Thread.sleep()方法

static void sleep(long millis):静态方法,没有返回值,参数是为毫秒单位的时间,1秒=1000毫秒。
作用:让当前线程进入休眠,即让当前线程放弃占有的CPU时间片,使其进入超时等待状态。等待时间以设定的毫秒数为准,在该指定的时间范围内,当前线程没有权利抢夺CPU时间片。
如何理解“当前线程”:Thread.sleep(1000),这个代码出现在哪个线程中,这个线程就是“当前线程”。

示例代码:

package threadtest;

public class ThreadTest04 {
    public static void main(String[] args) {
        //让主线程睡眠5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "====>" + i);
        }

        Runnable r = new MyNewRunnable();
        Thread t = new Thread(r);
        t.start();

    }


}

class MyNewRunnable implements Runnable{

    //run方法在重写时不能在方法声明位置使用 throws 抛出异常,子类不能比父类抛出更多异常,所以这里只能try...catch
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "====>" + i);
            //让当前线程睡眠1秒再进入下一次循环
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

5.1.1 关于Thread.sleep()的面试题

package threadtest;

public class ThreadTest05 {
    public static void main(String[] args) {
        MyLatestThread t = new MyLatestThread();
        t.setName("t");
        t.start();

        try {
            t.sleep(3000);  //当前代码是让t线程睡眠3秒吗?
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "====>" + i);
        }
    }
}


class MyLatestThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "====>" + i);
        }
    }
}

以上t.sleep(3000);并不是让t线程睡眠,而是让当前线程,即主线程main线程睡眠。

5.2 如何中断一个线程的睡眠

语法:线程对象.interrupt(true);
示例代码:

package threadtest.thread06;

public class ThreadTest06 {
    public static void main(String[] args) {
        //创建线程
        Thread t = new MyThread();
        t.setName("t");

        //启动线程
        t.start();

        //要求5秒之后睡眠的t线程起来干活
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //终止t线程的睡眠
        //其底层原理是利用了异常处理机制:当调用这个方法时,如果t线程正在睡眠,必然会抛出:InterruptedException,然后捕捉异常,终止睡眠
        t.interrupt();
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--> begin");
        try {
            //睡眠一年
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            System.out.println("睡眠中断了!");
            e.printStackTrace();

        }
        //睡眠一年之后,起来干活了
        System.out.println(Thread.currentThread().getName() + " --> work!");

    }
}

运行结果:
在这里插入图片描述

5.3 如何强行终止一个线程

5.3.1 已过时的方式

语法:线程对象.stop();
如下代码:

package threadtest.thread07;

public class ThreadTest07 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnable());
        t.setName("t");
        t.start();

        //当前线程睡眠5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //终止t线程的执行,从java2开始就不建议使用了,因为这种方式是强行终止线程,容易导致数据丢失。
        t.stop();
    }
}

class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }
    }
}

运行结果:
在这里插入图片描述

5.3.2 推荐的方式

设置标记。
如下代码:

package threadtest.thread08;

public class ThreadTest08 {
    public static void main(String[] args) {
        MyRunnable r = new MyRunnable();
        Thread t = new Thread(r);
        t.setName("t");
        t.start();

        //当前线程睡眠5秒
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //修改标记,终止线程t的执行
        r.ifRun = false;
    }
}

class MyRunnable implements Runnable{
    //设置标记
    boolean  ifRun = true;
    @Override
    public void run() {

        for (int i = 0; i < 10; i++) {
            if(ifRun){
                System.out.println(Thread.currentThread().getName() + "-->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else{
                return;
            }
        }
    }
}

运行结果:
在这里插入图片描述

5.4 守护线程

在java语言中,线程被分为两大类:

  1. 用户线程(非守护线程)【上面例子中定义的线程,包括主线程,都是用户线程】;
  2. 守护线程(后台线程)。

在JVM中有一个隐藏的守护线程:GC线程。
守护线程的特点:所有用户线程结束之后,守护线程自动退出/结束。

设置一个线程为守护线程(需要在线程启动前设置):线程对象.setDaemon(true);

示例代码:

package threadtest.thread09;

public class ThreadTest09 {
    public static void main(String[] args) {
        Thread t = new MyThread();
        t.setName("t");

        //需要再t线程启动前,设置t线程为守护线程,所有用户线程结束守护线程自动结束
        t.setDaemon(true);

        //启动t线程
        t.start();

        //设置main用户线程10秒后结束
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }   
    }
}


class MyThread extends Thread{
    @Override
    public void run() {
        int i = 0;
        //死循环
        while (true){
            System.out.println(Thread.currentThread().getName() + " --> " + (++i));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

运行结果:
在这里插入图片描述

5.5 定时器

JDK中提供的定时任务:

  • java.util.Timer:定时器
  • java.util.TimerTask:定时任务
    定时器+定时任务可以帮我们在程序中完成:指定时间开始,每间隔多久执行一次某段程序。

Timer的构造方法:

  • Timer();
  • Timer(boolean isDaemon): isDaemon为true表示该定时器是一个守护线程。

Timer的一个方法:

  • schedule(TimerTask task, Date firstTime, long period):task为需要执行的任务,firstTime为第一次执行时间,period为每隔多久执行一次(单位:毫秒)。
package threadtest.thread10;

import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.TimerTask;

/**
 * 定时任务类TimerTask是一个抽象类,其实现了Runnable接口,所以继承该类需要覆写run()方法
 */
public class LogTimerTask extends TimerTask {

    int i = 1;
    SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");

    @Override
    public void run() {
        Date now = new Date();
        String strTime = dateFormat.format(now);
        System.out.println(strTime + " " + (i++));
    }
}
package threadtest.thread10;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;

public class ThreadTest10 {
    public static void main(String[] args) throws Exception {
        //创建定时器对象(其本质上就是一个线程)
        //如果这个定时器执行的任务是一个后台任务,建议将其定义为守护线程
        Timer timer = new Timer(true);

        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date firstTime = dateFormat.parse("2024-03-26 17:45:00");

        //指定定时任务为LogTimerTask,第一次执行时间为firstTime,每间隔1秒执行一次。
        timer.schedule(new LogTimerTask(), firstTime,1000);


        //main线程5秒后结束,则上述守护线程也随之结束
        for (int i = 0; i < 5; i++) {
            Thread.sleep(1000);
        }
    }
}

运行结果:
在这里插入图片描述

5.6 线程合并

join()方法是一个实例方法,线程对象调用join()方法完成线程合并。

Thread t = new Thread();
t.setName("t");
t.join();

t.join方法其实是让当前线程进入阻塞状态,直到t线程结束,当前线程阻塞解除。
假设在main方法(main线程)中调用了t.join()方法,则为将t线程合并到main线程中,main线程进入阻塞状态,直到t线程执行结束,main线程解除阻塞。

join()方法和sleep()方法的联系和区别:

  1. sleep()方法和join()方法都是让当前线程进入阻塞状态(超时等待状态因也可以看作是一种阻塞状态);
  2. sleep()方法是静态方法,而join()方法是实例方法;
  3. sleep()方法可以指定睡眠的时长,而join()方法不能保证阻塞的时长;
  4. sleep()方法的阻塞解除条件:设置的时间过去了;join()方法的阻塞解除条件:调用join()方法的那个线程结束了。

示例代码:

package threadtest.thread11;

public class ThreadTest11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.setName("t");
        t.start();

        //合并线程,将t线程合并到main线程中,main线程收到阻塞,t线程继续执行,直到t线程结束,main线程阻塞解除
        t.join();
        System.out.println("main begin");
        //主线程
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
        System.out.println("main over");
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

运行结果:

t-->0
t-->1
t-->2
t-->3
t-->4
t-->5
t-->6
t-->7
t-->8
t-->9
t-->10
t-->11
t-->12
t-->13
t-->14
t-->15
t-->16
t-->17
t-->18
t-->19
main begin
main-->0
main-->1
main-->2
main-->3
main-->4
main-->5
main-->6
main-->7
main-->8
main-->9
main-->10
main-->11
main-->12
main-->13
main-->14
main-->15
main-->16
main-->17
main-->18
main-->19
main over

Process finished with exit code 0

join()方法也可以有参数,参数是毫秒,表示线程合并时长。

package threadtest.thread11;

public class ThreadTest11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.setName("t");
        t.start();

        //合并线程,将t线程合并到main线程中,main线程受到阻塞,t线程继续执行,直到t线程结束,main线程阻塞解除
        //合并时长为10毫秒,即只阻塞当前线程10毫秒,但并不一定能达到设置的阻塞时长,如果在指定的时间内,t线程早就结束了,则阻塞就立即解除了。
        t.join(10);
        System.out.println("main begin");
        //主线程
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
        System.out.println("main over");

    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

5.7 线程优先级

线程是可以设置优先级的,JVM采用的是抢占式调度模型,优先级高的线程获取CPU时间片的总体概率会高一些。
默认情况下一个线程的优先级是5,最低为1,最高为10。

package threadtest.thread12;

public class ThreadTest12 {
    public static void main(String[] args) {
        System.out.println("最低优先级" + Thread.MIN_PRIORITY);
        System.out.println("最高优先级" + Thread.MAX_PRIORITY);
        System.out.println("默认优先级" + Thread.NORM_PRIORITY);

        //获取线程的优先级
        System.out.println("当前线程优先级:" + Thread.currentThread().getPriority());

        //设置优先级
        Thread.currentThread().setPriority(10);
        System.out.println("当前线程优先级:" + Thread.currentThread().getPriority());
    }
}

运行结果:
在这里插入图片描述
给多个线程设置不同优先级:

package threadtest.thread12;

public class ThreadTest12 {
    public static void main(String[] args) {
        Thread t1 = new MyThread();
        t1.setName("t1");

        Thread t2 = new MyThread();
        t2.setName("t2");

        //设置t1为最高优先级,t2为最低优先级,相对来说t1指定的概率高一些
        t1.setPriority(Thread.MAX_PRIORITY); 
        t2.setPriority(Thread.MIN_PRIORITY);
        
        t1.start();
        t2.start();

    }
}


class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

5.8 线程让位

静态方法:Thread.yield(),当前线程让位。
注意:让位不会使当前线程进入阻塞状态,只是放弃目前占有的CPU时间片,进入就绪状态,继续抢夺CPU时间片。
示例代码:

package threadtest.thread13;

/**
 * 线程让位
 */
public class ThreadTest13 {
    public static void main(String[] args) {
        Thread t1 = new MyThread();
        t1.setName("t1");

        Thread t2 = new MyThread();
        t2.setName("t2");

        t1.start();
        t2.start();
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            if(Thread.currentThread().getName().equals("t1") && i%10 == 0){ //当前线程为t1线程,且i为10的倍数时,进行线程让位
                System.out.println(Thread.currentThread().getName() + "让位了,此时i=" + i);
                //注意:t1线程让位只是放弃当前占有的时间片,至于后续时间片被哪个线程抢占并不一定,也有可能t1会继续抢到
                Thread.yield();
            }
            System.out.println(Thread.currentThread().getName() + "==>" + i);
        }
    }
}

运行结果就不贴了。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1571688.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

考研回忆录【二本->211】

备考时长差不多快一年半&#xff0c;从22年的11月底开始陆陆续续地准备考研&#xff0c;因为开始的早所以整个备考过程显得压力不是很大&#xff0c;中途还去一些地方旅游&#xff0c;我不喜欢把自己绷得太紧。虽然考的不是很好&#xff0c;考完我甚至都没准备复试&#xff0c;…

《QT实用小工具·十三》FlatUI辅助类之各种炫酷的控件集合

1、概述 源码放在文章末尾 FlatUI辅助类之各种炫酷的控件集合 按钮样式设置。文本框样式设置。进度条样式。滑块条样式。单选框样式。滚动条样式。可自由设置对象的高度宽度大小等。自带默认参数值。 下面是demo演示&#xff1a; 项目部分代码如下所示&#xff1a; #ifnd…

《QT实用小工具·十四》面板容器控件和图形字体示例

1、概述 源码放在文章末尾 面板容器控件包含如下功能&#xff1a; 支持所有widget子类对象&#xff0c;自动产生滚动条。 支持自动拉伸自动填充。 提供接口获取容器内的所有对象的指针。 可设置是否自动拉伸宽度高度。 可设置设备面板之间的间距和边距。 超级图形字体类…

论文笔记:Large Language Models as Analogical Reasoners

iclr 2024 reviewer打分5558 1 intro 基于CoT prompt的大模型能够更好地解决复杂推理问题 然而传统CoT需要提供相关的例子作为指导&#xff0c;这就增加了人工标注的成本——>Zero-shot CoT避免了人工标注来引导推理 但是对于一些复杂的任务难以完成推理&#xff0c;例如c…

2024最全AI绘画Midjourney绘画提示词Prompt大全,AI换脸、垫图混图【宝藏级收藏】

一、AI绘画工具 SparkAi创作系统是基于ChatGPT进行开发的Ai智能问答系统和Midjourney绘画系统&#xff0c;支持OpenAI-GPT全模型国内AI全模型。本期针对源码系统整体测试下来非常完美&#xff0c;那么如何搭建部署AI创作ChatGPT&#xff1f;小编这里写一个详细图文教程吧。已支…

Win10 下 git error unable to create file Invalid argument 踩坑实录

原始解决方案参看&#xff1a;https://stackoverflow.com/questions/26097568/git-pull-error-unable-to-create-file-invalid-argument 本问题解决于 2024-02-18&#xff0c;使用 git 版本 2.28.0.windows.1 解决方案 看 Git 抛出的出错的具体信息&#xff0c;比如如下都来自…

7(8)-2-CSS 盒子模型

个人主页&#xff1a;学习前端的小z 个人专栏&#xff1a;HTML5和CSS3悦读 本专栏旨在分享记录每日学习的前端知识和学习笔记的归纳总结&#xff0c;欢迎大家在评论区交流讨论&#xff01; 文章目录 CSS 盒子模型1 盒子模型&#xff08;Box Model&#xff09;组成2 边框&#x…

Git 如何去使用

目录 1. Git暂存区的使用 1.1. 暂存区的作用 1.2. 暂存区覆盖工作区&#xff08;注意&#xff1a;完全确认覆盖时使用&#xff09; 1.3. 暂存区移除文件 1.4. 练习 2. Git回退版本 2.1. 概念 2.2. 查看提交历史 2.3. 回退命令 2.4. 注意 3. Git删除文件 3.1. 需求 …

docker安装Nexus,maven私服

文章目录 前言安装创建文件夹设置文件夹权限docker创建指令制作docker-compose.yaml文件 查看网站访问网页查看密码 前言 nexus作为私服的maven仓库&#xff0c;在企业级应用中&#xff0c;提供了依赖来源的稳定性&#xff0c;为构建庞大的微服务体系&#xff0c;打下基础 安…

Day83:服务攻防-开发组件安全JacksonFastJson各版本XStreamCVE环境复现

目录 J2EE-组件Jackson-本地demo&CVE 代码执行 (CVE-2020-8840) 代码执行 (CVE-2020-35728&#xff09; J2EE-组件FastJson-本地demo&CVE FastJson < 1.2.24 FastJson < 1.2.47 FastJson < 1.2.80 (利用条件比较苛刻) J2EE-组件XStream-靶场&CVE …

Java_自定义实体类的列表List<T>调用remove()失败讲解

示例1 前提&#xff1a; 新建一个主类Demo1。 需求&#xff1a; 在一个列表中有三条String的数据&#xff0c;想要使用remove(Object o)删掉其中一条。 结果&#xff1a; remove(Object o)成功把数据删掉。 示例2 前提&#xff1a; 新建一个自定义实体类DataExample和一个主…

PC发送指令给单片机控制LED(与上一篇文章相反)

此时要重新配置寄存器 &#xff0c;实现电脑往单片机传输数据 1、配置SCON寄存器的REN 即 REN 1 2、有TI&#xff08;发送中断&#xff09;就有RI&#xff08;接收中断&#xff09; 3、优化 发现发送 o 时&#xff0c;D5亮灯会有延迟 下面就是做到真正的无延迟的全双工通信 …

STC89C51学习笔记(四)

STC89C51学习笔记&#xff08;四&#xff09; 综述&#xff1a;本文讲述了在STC89C51中数码管、模块化编程、LCD1602的使用。 一、数码管 1.数码管显示原理 位选&#xff1a;对74HC138芯片的输入端的配置&#xff08;P22、P23、P24&#xff09;&#xff0c;来选择实现位选&…

Android 代码自定义drawble文件实现View圆角背景

简介 相信大多数Android开发都会遇到一个场景&#xff0c;给TextView或Button添加背景颜色&#xff0c;修改圆角&#xff0c;描边等需求。一看到这样的实现效果&#xff0c;自然就是创建drawble文件&#xff0c;设置相关属性shap&#xff0c;color&#xff0c;radius等。然后将…

第二节课《轻松玩转书生·浦语大模型趣味 Demo》

比较匆忙&#xff0c;假期前仿照第一期课程的内容好像被清空了&#xff0c;重新搭建一次。 https://github.com/InternLM/Tutorial/blob/camp2/helloworld/hello_world.md 按照那老师写好的&#xff0c;一步步复制就好了 浦语灵笔2的大概率是会超出显存&#xff0c;先不测试了…

JavaScript权威指南(第7版) 笔记 - 扩展操作符总结

扩展操作符 ... &#xff0c;不是真正意义上的JavaScript操作符。 let str "0123ABC" console.log(typeof ...str);// Uncaught SyntaxError: Unexpected token ... 上面的第2行代码会报错&#xff0c;… 操作符只能在数组字面量、对象字面量、函数调用中使用。 在…

JS详解-设计模式

工厂模式&#xff1a; 单例模式&#xff1a; // 1、定义一个类class SingleTon{// 2、添加私有静态属性static #instance// 3、添加静态方法static getInstance(){// 4、判断实例是否存在if(!this.#instance){// 5、实例不存在&#xff0c;创建实例this.#instance new Single…

Redis从入门到精通(七)Redis实战(四)库存超卖、一人一单与Redis分布式锁

↑↑↑请在文章开头处下载测试项目源代码↑↑↑ 文章目录 前言4.3 优惠券秒杀4.3.4 库存超卖问题及其解决4.3.4.1 问题分析4.3.4.2 问题解决 4.3.5 一人一单需求4.3.5.1 需求分析4.3.5.2 代码实现4.3.5.3 并发问题4.3.5.4 悲观锁解决并发问题4.3.5.5 集群环境下的并发问题 4.3.…

C语言【编译和链接】

1.程序执行过程 C语言的编译和链接是将源代码转换为可执行程序的过程。下面是C语言编译和链接的基本步骤&#xff1a; 预处理&#xff1a;在编译前&#xff0c;预处理器会对源代码进行。它会处理以"#"开头的预处理指令&#xff0c;#include和#define&#xff0c;并将…

【浅尝C++】继承机制=>虚基表/菱形虚继承/继承的概念、定义/基类与派生类对象赋值转换/派生类的默认成员函数等详解

&#x1f3e0;专栏介绍&#xff1a;浅尝C专栏是用于记录C语法基础、STL及内存剖析等。 &#x1f3af;每日格言&#xff1a;每日努力一点点&#xff0c;技术变化看得见。 文章目录 继承的概念及定义继承的概念继承的定义定义格式继承关系与访问限定符 基类和派生类对象赋值转换继…