java并发编程 | 线程详解

时间:2019-04-19
本文章向大家介绍java并发编程 | 线程详解,主要包括java并发编程 | 线程详解使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

进程与线程

进程:操作系统在运行一个程序的时候就会为其创建一个进程(比如一个java程序),进程是资源分配的最小单位,一个进程包含多个线程

线程:线程是cpu调度的最小单位,每个线程拥有各自的计数器,对战和局部变量等属性,并且能过访问共享的内存变量

线程的状态

java线程的生命周期总共包括6个阶段:

  1. 初始状态:线程被创建,但是还没有调用 start() 方法
  2. 运行状态:java中将就绪状态和运行状态统称为运行状态
  3. 阻塞状态:线程阻塞,线程等待进入 synchronized 修饰的代码块或方法
  4. 等待状态:线程进入等待状态,需要调用 notify() 或 notifyAll() 进行唤醒
  5. 超时等待状态:线程进入等待状态,在指定时间后自行返回
  6. 终止状态:线程执行完毕

在某一时刻,线程只能处于其中的一个状态

线程初始化后,调用 start() 方法变为运行状态,调用 wait() , join() 等方法,线程由运行状态变为等待状态,调用 notify() 或 notifyAll() 等方法,线程由等待状态变成运行状态,超时等待状态就是在等待状态基础上加了时间限制,超过规定时间,自动更改为运行状态,当需要执行同步方法时,如果没有获得锁,这时线程状态就变为阻塞状态,直到获取到锁,变为运行状态,当执行完线程的 run() 方法后,线程变为终止状态

创建线程

创建线程有三种方式

Thread
Runnable
Callable

继承 Thread 类

/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:13
 * @description: 继承Thread类
 */
public class ThreadTest extends Thread{

    @Override
    public void run() {
        IntStream.range(0,10).forEach(i->{
            System.out.println(this.getName()+":"+i);
        });
    }

    public static void main(String[] args) {
        Thread thread = new ThreadTest();
        thread.start();
    }
}

实现 Runnable 接口

/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:18
 * @description: 实现Runnable接口
 */
public class RunnableTest implements Runnable {

    @Override
    public void run() {
        IntStream.range(0,10).forEach(i->{
            System.out.println(Thread.currentThread().getName()+":"+i);
        });
    }

    public static void main(String[] args) {
        Runnable runnable = new RunnableTest();
        new Thread(runnable,"RunnableTest").start();
    }
}

 实现 Callable 接口

/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:23
 * @description: 实现Callable接口
 */
public class CallableTest implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        IntStream.range(0,10).forEach(i->{
            System.out.println(Thread.currentThread().getName()+":"+i);
        });
        return -1;
    }

    public static void main(String[] args) throws Exception {
        Callable callable = new CallableTest();
        FutureTask futureTask = new FutureTask(callable);
        new Thread(futureTask,"future").start();
        System.out.println("result:"+futureTask.get());
    }
}

 

线程的暂停,恢复,停止

不安全的线程暂停,恢复,停止操作

Thread 提供的过期方法可以实现对线程进行暂停 suspend() ,恢复 resume() ,停止 stop() 的操作

例:创建一个线程, run() 中循环输出当前时间,在 main() 方法中对新建线程进行暂停,恢复,停止的操作

/**
 * @author: chenmingyu
 * @date: 2019/4/8 15:51
 * @description: 线程的暂停,恢复,停止
 */
public class OperationThread implements Runnable{

    @Override
    public void run() {
        while (true){
            try {
                TimeUnit.SECONDS.sleep(1L);
                System.out.println(Thread.currentThread().getName()+"运行中:"+LocalTime.now());
            }catch (InterruptedException e){
                System.err.println(e.getMessage());
            }
        }
    }

    public static void main(String[] args) throws Exception{
        Runnable runnable = new OperationThread();
        Thread thread = new Thread(runnable,"operationThread");
        /**
         * 启动,输出当前时间
         */
        thread.start();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 线程暂停,不在输出当前时间
         */
        System.out.println("此处暂停:"+LocalTime.now());
        thread.suspend();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 线程恢复,继续输出当前时间
         */
        System.out.println("此处恢复:"+LocalTime.now());
        thread.resume();
        TimeUnit.SECONDS.sleep(3L);

        /**
         * 线程停止,不在输出当前时间
         */
        thread.stop();
        System.out.println("此处停止:"+LocalTime.now());
        TimeUnit.SECONDS.sleep(3L);
    }
}

 

输出

因为是过期方法,所以不推荐使用,使用 suspend() 方法后,线程不会释放已经占有的资源,就进入睡眠状态,容易引发死锁问题,而使用 stop() 方法终结一个线程是不会保证线程的资源正常释放的,可能会导致程序异常

安全的线程暂停,恢复,停止操作

线程安全的暂停,恢复操作可以使用等待/通知机制代替,安全的停止操作可以用线程是否被中断进行判断

安全的线程暂停,恢复(等待/通知机制)

相关方法:

方法名 描述
notify() 通知一个在对象上等待的线程,使其重wait()方法中返回,前提是该线程获得了对象的锁
notifyAll() 通知所有等待在该对象上的线程
wait() 调用该方法线程进入等待状态,只有等待另外线程的通知或被中断才会返回,调用该方法会释放对象的锁
wait(long) 超时等待一段时间(毫秒),如果超过时间就返回
wait(long,int) 对于超时时间耕细粒度的控制,可以达到纳秒

例:创建一个名为 waitThread 的线程,在 run() 方法,使用中使用 synchronized 进行加锁,以变量 flag 为条件进行 while 循环,在循环中调用 LOCK.wait() 方法,此时会释放对象锁,由 main() 方法获得锁,调用 LOCK.notify() 方法通知 LOCK 对象上等待的 waitThread 线程,将其置为阻塞状态,并将变量 flag 置为 true ,当 waitThread 线程再次获取对象锁之后继续执行余下代码

/**
 * @author: chenmingyu
 * @date: 2019/4/8 20:00
 * @description: wait/notify
 */
public class WaitNotifyTest {

    private static Object LOCK = new Object();
    private static Boolean FLAG = Boolean.TRUE;


    public static void main(String[] args) throws InterruptedException{
        Runnable r = new WaitThread();
        new Thread(r,"waitThread").start();
        TimeUnit.SECONDS.sleep(1L);
        synchronized (LOCK){
            System.out.println(Thread.currentThread().getName()+"唤醒waitThread线程:"+LocalTime.now());
            /**
             * 线程状态由等待状态变为阻塞状态
             */
            LOCK.notify();
            /**
             * 只有当前线程释放对象锁,waitThread获取到LOCK对象的锁之后才会从wait()方法中返回
             */
            TimeUnit.SECONDS.sleep(2L);
            FLAG = Boolean.FALSE;
        }
    }

    public static class WaitThread implements Runnable {
        @Override
        public void run() {
            /**
             * 加锁
             */
            synchronized (LOCK){
                while (FLAG){
                    try {
                        System.out.println(Thread.currentThread().getName()+"运行中:"+LocalTime.now());
                        /**
                         * 线程状态变为等待状态
                         */
                        LOCK.wait();
                        /**
                         * 再次获得对象锁之后,才会执行
                         */
                        System.out.println(Thread.currentThread().getName()+"被唤醒:"+LocalTime.now());
                    }catch (InterruptedException e){
                        System.err.println(e.getMessage());
                    }
                }
            }
            System.out.println(Thread.currentThread().getName()+"即将停止:"+LocalTime.now());
        }
    }
}

输出

可以看到在 mian 线程调用 LOCK.notify() 方法后,沉睡了2s才释放对象锁, waitThread线程在获得对象锁之后执行余下代码

安全的线程停止操作(中断标识)

线程的安全停止操作是利用线程的中断标识来实现,线程的中断属性表示一个运行汇总的线程是否被其他线程进行了中断操作,其他线程通过调用该线程的 interrupt() 方法对其进行中断操作,而该线程通过检查自身是否被中断来进行响应,当一个线程被中断可以使用 Thread.interrupted() 方法对当前线程的中断标识位进行复位

例:新建一个线程, run 方法中使用 Thread.currentThread().isInterrupted() 是否中断作为判断条件,在主线程中使用 thread.interrupt() 方法对子线程进行中断操作,用来达到终止线程的操作,这种方式会让子线程可以去清理资源或一些别的操作,而使用 stop() 方法则会会直接终止线程

 

/**
 * @author: chenmingyu
 * @date: 2019/4/8 20:47
 * @description: 中断
 */
public class InterruptTest {
    
    public static void main(String[] args) throws InterruptedException {
        Runnable r = new StopThread();
        Thread thread = new Thread(r,"stopThread");
        thread.start();
        TimeUnit.SECONDS.sleep(1L);
        System.out.println(Thread.currentThread().getName()+"对stopThread线程进行中断:"+LocalTime.now());
        thread.interrupt();
    }

    public static class StopThread implements Runnable {
        @Override
        public void run() {
            while (!Thread.currentThread().isInterrupted()){
                System.out.println(Thread.currentThread().getName()+"运行中:"+LocalTime.now());
            }
            System.out.println(Thread.currentThread().getName()+"停止:"+LocalTime.now());
        }
    }
}

 未完待续...

最后,宣传一下我的群

欢迎Java工程师朋友们加入Java进阶高级架构:809389099
群内提供免费的Java架构学习资料(里面有高可用、高并发、高性能及分布式、Jvm性能调优、Spring源码,
MyBatis,Netty,Redis,Kafka,Mysql,Zookeeper,Tomcat,Docker,Dubbo,Nginx等多个知识点的架构资料)
合理利用自己每一分每一秒的时间来学习提升自己,不要再用"没有时间“来掩饰自己思想上的懒惰!趁年轻,使劲拼,给未来的自己一个交代!