• Java 线程由浅入深 持续学习


    线程状态

    在Thread.State 中关于线程状态的解释

    • NEW

      Thread state for a thread which has not yet started.

    • RUNNABLE

      Thread state for a runnable thread.
      A thread in the runnable state is executing in the Java virtual machine but it may be waiting for other resources from the operating system such as processor.

    • BLOCKED

      Thread state for a thread blocked waiting for a monitor lock.
      A thread in the blocked state is waiting for a monitor lock to enter a synchronized block/method or reenter a synchronized block/method after calling Object.wait().

    • WAITING

      Thread state for a waiting thread.
      A thread is in the waiting state due to calling one of the following methods:

      • Object.wait() with no timeout
      • Thread.join() with no timeout
      • LockSupport.park()

      A thread in the waiting state is waiting for another thread to perform a particular action.
      For example, a thread that has called Object.wait() on an object is waiting for another thread to call Object.notify() or Object.notifyAll() on that object. A thread that has called Thread.join() is waiting for a specified thread to terminate.

    • TIMED_WAITING

      Thread state for a waiting thread with a specified waiting time.
      A thread is in the timed waiting state due to calling one of the following methods with a specified positive waiting time:

      • Thread.sleep
      • Object.wait(long)with timeout
      • Thread.join(long)with timeout
      • LockSupport.parkNanos
      • LockSupport.parkUntil
    • TERMINATED

      Thread state for a terminated thread.
      The thread has completed execution.

    线程优先级

    每个线程都有一个优先级以便操作系统确定线程的调度顺序
    根据Thrad类初始化方法,每个线程在初始化时都会从当前线程中取出一些属性来设置给自身,其中包括线程的priority。在Thread中同时定义有NORM_PRIORITY默认优先级5,以及MIN_PRIORITY最小1,MAX_PRIORITY最大10
    较高优先级的线程会在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序

    线程创建

    三种创建线程的方法:

    • 实现Runnable接口
    • 继承Thread类
    • 通过 Callable 和 Future 创建线程

    实现Runnable接口创建线程

    创建类实现Runnable接口,实现run 方法

    public class MyThreadImplRunnable implements Runnable {
    
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + Thread.currentThread().getState());
            for (int i = 0 ; i < 100 ;i++) {
                System.out.println(Thread.currentThread().getName() + "-" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "exit");
        }
    }
    

    创建启动线程

    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThreadImplRunnable(),"thread1");
        System.out.println("thread1" + t1.getState());
        t1.start();
        Thread t2 = new Thread(new MyThreadImplRunnable(),"thread2");
        System.out.println("thread2" + t2.getState());
        t2.start();
    }
    

    执行结果

    thread1NEW
    thread2NEW
    thread1RUNNABLE
    thread1-0
    thread2RUNNABLE
    thread2-0
    thread1-1
    thread2-1
    thread2-2
    thread1-2
    thread1-3
    thread2-3
    thread1-4
    thread2-4
    thread1exit
    thread2exit
    

    继承Thread创建线程

    创建类来继承Thread,实现run方法

    public class MyThreadExtThread extends Thread {
        public MyThreadExtThread(String threadName) {
            super(threadName);
        }
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + Thread.currentThread().getState());
            for (int i = 0 ; i < 5 ;i++) {
                System.out.println(Thread.currentThread().getName() + "-" + i);
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println(Thread.currentThread().getName() + "exit");
        }
    }
    

    启动线程

    public static void main(String[] args) {
        Thread t1 = new MyThreadExtThread("thread1");
        System.out.println("thread1" + t1.getState());
        t1.start();
        Thread t2 = new MyThreadExtThread("thread2");
        System.out.println("thread2" + t2.getState());
        t2.start();
    }
    

    执行结果

    thread1NEW
    thread2NEW
    thread1RUNNABLE
    thread1-0
    thread2RUNNABLE
    thread2-0
    thread1-1
    thread2-1
    thread2-2
    thread1-2
    thread1-3
    thread2-3
    thread1-4
    thread2-4
    thread1exit
    thread2exit
    

    通过 Callable 和 Future 创建线程

    创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。

    public class MyCallable implements Callable<Integer> {
    
        @Override
        public Integer call() throws Exception {
            int sum = 0 ;
            for (int i = 0 ; i < 100;i++) {
                sum += i;
            }
            return sum;
        }
    }
    

    创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。配合ExecutorService 启动使用线程,调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值

    public static void main(String[] args) {
        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);
    
        ExecutorService executorService = Executors.newFixedThreadPool(1);
        executorService.submit(futureTask);
    
        try {
            System.out.println(futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    
    

    在执行线程测试的时候发现一个有趣的现象,使用junit进行测试时,有时能够执行完达到预期的效果,有时并不能。
    经过百度后发现原来Junit只管自己的运行,就是说当Junit执行完毕后,就会关闭程序,不会关心是否还有自己启动的后台线程在运行。当Junit运行完毕后,如果后台线程还没有执行完毕,那么也是不会再执行了

    创建线程的三种方式的对比

    1. 采用实现 Runnable、Callable 接口的方式创建多线程时,线程类只是实现了 Runnable 接口或 Callable 接口,还可以继承其他类。
    2. 使用继承 Thread 类的方式创建多线程时,编写简单,如果需要访问当前线程,则无需使用 Thread.currentThread() 方法,直接使用 this 即可获得当前线程。
    3. Runnable、Thread 这2种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果。

    Thread 类的方法

    序号 方法描述
    1 public void start()
    使该线程开始执行;
    Java 虚拟机调用该线程的 run 方法。
    2 public void run()
    如果该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;
    否则,该方法不执行任何操作并返回。
    3 public final void setName(String name)
    改变线程名称,使之与参数 name 相同。
    4 public final void setPriority(int priority)
    更改线程的优先级。
    5 public final void setDaemon(boolean on)
    将该线程标记为守护线程或用户线程。
    6 public final void join(long millisec)
    等待该线程终止的时间最长为 millis 毫秒。
    7 public void interrupt()
    中断线程。
    8 public final boolean isAlive()
    测试线程是否处于活动状态。

    Thread类的静态方法

    序号 方法描述
    1 public static void yield()
    暂停当前正在执行的线程对象,并执行其他线程。
    2 public static void sleep(long millisec)
    在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),此操作受到系统计时器和调度程序精度和准确性的影响。
    3 public static boolean holdsLock(Object x)
    当且仅当当前线程在指定的对象上保持监视器锁时,才返回 true。
    4 public static Thread currentThread()
    返回对当前正在执行的线程对象的引用。
    5 public static void dumpStack()
    将当前线程的堆栈跟踪打印至标准错误流。

    线程睡眠 -- 使用sleep方法之后,线程进入阻塞状态,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不可能精准的去干涉它,所以如果调用Thread.sleep(1000)使得线程睡眠1秒,可能结果会大于1秒.
    线程让步 -- yield让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态

    并行与并发

    Stack overflow上的文章作者关于并发与并行的解释:

    并发是两个任务可以在重叠的时间段内启动,运行和完成。并行是任务在同一时间运行,例如,在多核处理器上。
    并发是独立执行过程的组合,而并行是同时执行(可能相关的)计算。
    并发是一次处理很多事情,并行是同时做很多事情。
    应用程序可以是并发的,但不是并行的,这意味着它可以同时处理多个任务,但是没有两个任务在同一时刻执行。
    应用程序可以是并行的,但不是并发的,这意味着它同时处理多核CPU中的任务的多个子任务。
    一个应用程序可以即不是并行的,也不是并发的,这意味着它一次一个地处理所有任务。
    应用程序可以即是并行的也是并发的,这意味着它同时在多核CPU中同时处理多个任务。

    网上看到的关于并发与并行的区别,觉得描述的挺生动

    个人理解:

    并行是指两个或者多个事件在同一时刻发生;而并发是指两个或多个事件在同一时间间隔发生。
    并发:交替做不同事情的能力,即不同的代码块交替执行
    并行:同时做不同事情的能力,不同的代码块同时执行

    为什么要用多线程

    1. 为了更好的利用cpu的资源,如果只有一个线程,则第二个任务必须等到第一个任务结束后才能进行,如果使用多线程则在主线程执行任务的同时可以执行其他任务,而不需要等待;
    2. 进程之间不能共享数据,线程可以;
    3. 系统创建进程需要为该进程重新分配系统资源,创建线程代价比较小;

    sleep 和wait 的区别

    区别 sleep wait
    所属类 是Thread类的方法 是Object类的方法
    时间 必须指定时间 wait可以指定时间也可以不指定时间
    释放锁 释放CPU执行权不释放锁 释放CPU执行权也释放锁
    使用地方 任意地方 只能在同步代码方法或同步代码块中使用
  • 相关阅读:
    docker log
    byte转String防止乱码
    SQL索引
    Redis 总结精讲
    如何保证消息队列是高可用的
    消息中间件(一)MQ详解及四大MQ比较
    @Bean和@Componet区别
    理解Spring的AOP和Ioc/DI就这么简单
    SpringBoot 基础
    《Linux 鸟哥私房菜》 第6章 Linux的文件权限与目录配置
  • 原文地址:https://www.cnblogs.com/herberts/p/10872987.html
Copyright © 2020-2023  润新知