Java并发编程多线程知识

331 阅读5分钟

这是我参与8月更文挑战的第10天,活动详情查看: 8月更文挑战

概述

在我们的运行的系统中,运行的比较多的线程,例如当运行Jvm的时候,操作系统会创建一个Jvm进程来运行我们编写的Java应用程序,但是现代的操作系统调度的最小单元是线程,所以我们在运行的Jvm进程中可以创建多个线程,这些线程拥有各自的程序计数器,堆栈和局部变量等属性,能够访问共享的内存变量,因为多个线程可以访问共享内存变量,所以在多线程编程的时候,我们更多的是考虑线程的安全性,通过一系列的手段来保证共享内存数据的准确性。处理器通过高速的切换时间片来运行不同的线程,这给我们感觉就是多个线程在同时运行。 在Java应用中,Java的入口为Main()方法,很多人认为在代码中我们并没有写多线程的代码逻辑,那么一个Jvm只运行了一个线程,这种理解是错误的,下面通过实际的例子来说明

//获取Java线程管理MxBean
ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
//获取线程相关信息
ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
for (ThreadInfo threadInfo : threadInfos) {
    System.out.println(threadInfo.getThreadId() + "." + threadInfo.getThreadName());
}

运行结果:

6.Monitor Ctrl-Break		  //Idea特有的线程:监控Ctrl+Brean键
5.Attach Listener             //Jvm进程间通信监听
4.Signal Dispatcher           //分发处理发送给JVM信号的线程
3.Finalizer                   //调用对象Finalizer方法的线程
2.Reference Handler			  //清除Reference
1.main                        //main线程,程序入口

从上面打印的结果可以看出我们启动一个main方法,其实是有6个线程在运行,平时编程我们只关注main里面的代码逻辑,其他线程的逻辑我们不关系,所以一般情况下不会去接触到其他线程的相关逻辑。

相关知识

创建线程

在创建一个线程的时候,可以使用下面的3种方式来创建一个线程

  • 继承Thread类
  • 实现Runable接口
  • 实现Callable接口

线程状态

  • NEW:初始状态,线程刚刚创建,还没有调用start方法
Thread thread = new Thread();
  • RUNNABLE:运行状态,调用了run方法,线程可以执行,在这个时候又分为两种情况:RUNNING-线程运行中,READY-等待操作系统调度执行线程
thread.start();
  • BLOCKED:阻塞状态,线程运行没有抢到锁,线程阻塞于锁
  • WAITING:等待状态,当前线程需要等其他线程做成一些特定操作(通知或中断)后才继续运行
  • TIMED_WAITING:超时等待状态,与WAITING一样都是等待状态,但是不同的是:该状态等到一定的时间后可以自行返回
  • TERMINATED :终止状态,表示当前线程已经执行完毕

线程状态切换图.png 通过上面Java线程状态变迁图,我们可以很清楚的了解到每个状态之间通过哪些方法来切换到不同的状态,在进入阻塞状态的时候,是线程阻塞在synchronized关键字修饰的方法或代码块时的状态,跟synchronized对应的还有Lock接口对应的锁,对应锁进入的状态,线程却是等待状态,因为Lock接口对于阻塞的实现均使用了LockSupport类中的相关方法。

Daemon线程

Daemon线程属于守护线程,当Java虚拟机退出的时候,该线程模式不会阻塞后续的操作,会随着主线程运行完成后自动退出。设置一个线程为守护线程只需要调用Thread.setDaemon(true)将线程设置为守护线程,设置时需要在调用start之前设置。

线程优先级

在有些时候,我们创建的线程需要优先处理时,由于操作系统调度会将线程分配到若干时间片,当线程执行时间片用完后就会切换到其他线程的调度,我们期望下次分配的时候能够被操作系统优先调用处理,这个时间我们就可以设置线程的优先级,通过Thread.setPriority(int)来设置线程的优先级,优先级参数范围为0-10,值越大优先级越高。

线程中断

线程中断为线程的一个标识位属性,表示运行中的线程是否被其他线程进行了中断操作。线程通过检查自身是否被中断来进行相应,同过方法isInterrupted来判断是否被中断,也可以使用Thread.interrupted来对当前线程的中断标识位进行复位。

终止线程

当我们运行一个线程的时候,达到一定的条件值时,需要安全的终止线程,可以使用线程中断的技术,也可以使用线程共享变量来终止线程

public class ThreadDemo {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runner());
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        t1.interrupt();

        Runner r2 = new Runner();
        Thread t2 = new Thread(r2);
        t2.start();
        TimeUnit.SECONDS.sleep(1);
        r2.cancel();
    }

    public static class Runner implements Runnable {
        private volatile boolean on = true;

        private long i;

        @Override
        public void run() {
            while (on && !Thread.currentThread().isInterrupted()) {
                i++;
            }
            System.out.println("i=" + i);
        }
        public void cancel() {
            on = false;
        }
    }
}

上面的代码使用了两种方式来终止当前正在运行的线程,这种通过标识位或者中断操作的方式可以能够使线程在终止的时候能够去清理资源,而不是一下就像线程停止,这样的话可以使线程中断的更加安全以及优雅。

在编程中,我们针对线程一般单个使用的时候比较少,基本上很多时候都是结合线程池来使用。