欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

并发编程-线程的认识、基本操作

程序员文章站 2022-05-04 18:50:13
...

多线程

线程的定义

线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中, 是进程中的实际运作单位

Java线程从创建到销毁,一共经历6个状态
NEW: 初始状态,线程被构建,但是还没有调用start方法
RUNNABLED: 运行状态,JAVA线程把操作系统中的就绪和运行两种状态统一称为“运行中”
BLOCKED: 阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了CPU使用权,阻塞也分为几种情况
WAITING: 等待状态
TIME_WAITING: 超时等待状态,超时以后自动返回
TERMINATED: 终止状态,表示当前线程执行完毕
并发编程-线程的认识、基本操作

多线程的本质是:合理的利用多核心CPU资源来实现线程的并行处理,来实现同一个进程内的多个任务的并行执行,同时基于线程本身的异步执行特性,提升任务处理的效率

多线程优势

1.在多核CPU中,利用多线程可以实现真正意义上的并行执行
2.在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也被阻塞。通过对不同任务创建不同的线程去处理,可以提升程序处理的实时性
3.线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快

多线程应用场景

因此多线程一般应用于以下及类似场景
1.使用多线程实现文件下载
2.后台任务:如定时向大量(100W以上)的用户发送邮件
3.异步处理:记录日志
4.多步骤的任务处理,可根据步骤特征选用不同个数和特征的线程来 协作处理,多任务的分割,由一个主线程分割给多个线程完成

线程的基本操作

创建线程

线程的创建有三种方式

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口

简单总结三者之间的区别:如果需要获得返回值,推荐使用Callable。由于Java中只能实现单继承(接口除外),因此某个线程类如果需要继承别的类的时候实现Runnable接口,反之直接继承Thread类即可。

Thread.join()

Thread.join()方法本质上是wait/notifyall,他可以用来保证线程执行结果的可见性。我们可以看一下join()的源码

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

这里写一段示例代码,在这段代码中,在t2启动之前,主线程先执行t1.join(),导致主线程执行到这一行代码时进入阻塞一直到t1线程执行完成,因此对于t2来讲,t1的执行结果是可见的,所以会正确返回结果i=4。

public class ThreadJoinDemo {
    private static int x=0;
    private static int i=0;
    public static void main(String[] args) throws InterruptedException {
        Thread t1=new Thread(()->{
            //阻塞操作
            i=1;
            x=2;
        });
        Thread t2=new Thread(()->{
            i=x+2;
        });
        //两个线程的执行顺序,
        t1.start();
        t1.join(); //t1线程的执行结果对于t2可见(t1线程一定要比t2线程有限执行) --- 阻塞
        t2.start();
        Thread.sleep(1000);
        System.out.println("result:"+i);
    }

Thread.sleep()

使线程暂停执行一段时间,直到等待的时间结束才恢复执行或在这段时间内被中断

sleep()首先挂起线程并修改运行状态,利用提供的参数来设置一个定时器,时间结束后定时器触发,内核收到中断后修改线程运行状态。
特别强调sleep(0),sleep(0)虽然只睡0秒,但可以利用它修改线程运行状态,使它重新进行CPU资源抢占。

wait和notify

一个线程修改了一个对象的值,而另个线程感 知到了变化,然后进行响应的操作

首先,wait和notify是基于对互斥资源的争夺。也就是说wait和notify方法一定是互斥存在的,因此synchronized是一种很好的解决方案。两个方法分别处于不同的线程之中,它们依靠synchronized进行线程间的通信。最出名的例子就是生产者消费者模型了。

public class WaitNotifyDemo {

    public static void main(String[] args) {
        Queue<String> queue=new LinkedList<>();
        int size=10000;		//数据量足够大容易观察到线程运行的过程
        Producer producer=new Producer(queue,size);
        Consumer consumer=new Consumer(queue,size);
        Thread t1=new Thread(producer);
        Thread t2=new Thread(consumer);
        t1.start();
        t2.start();
    }
}

中断线程

众所周知,Thread.stop()是一个已经被废弃的方法,因为调用stop()方法会使得线程变得不安全。

停止一个线程会导致其解锁其上被锁定的所有监视器(监视器以在栈顶产生ThreadDeath异常的方式被解锁)

这句话的意思就是,假设一个线程指向一个对象并正在修改它,调用stop()方法会使得该对象的状态变得不确定的,当线程在受损的对象上进行操作时,会导致任意行为。这种行为可能微妙且难以检测,也可能会比较明显。不像其他未受检的(unchecked)异常, ThreadDeath 悄无声息的杀死及其他线程。因此,用户得不到程序可能会崩溃的警告。崩溃会在真正破坏发生后的任意时刻显现,甚至在数小时或数天之后。

因此我们采用Thread.interrupt()和Thread.interrupted()方法来中断线程。调用interrupt()进行友好的中断,可以对处于阻塞状态的线程进行中断,例如while()中写入Thread.current().interrupted()作为是否中断的标志,或者线程处于sleep()阻塞。

    public static void main(String[] args) throws InterruptedException {
        Thread thread=new Thread(()->{
            //Thread.currentThread().isInterrupted() 默认是false
            //正常的任务处理..
            try {
                Thread.sleep(100000);
            } catch (InterruptedException e) {
                //抛出异常来相应客户端的中断请求
                e.printStackTrace();
            }
        });
        Thread thread2=new Thread(()->{
            while(!Thread.currentThread().isInterrupted()){
                i++;
            }
        });
        thread.start();
        thread2.start();
        Thread.sleep(5000);
        //interrupt 这个属性有false-true
        thread.interrupt(); //中断(友好)
        thread2.interrupt();
        System.out.println("i:"+i);
    }
}

用interrupt()中断sleep()时会抛出InterruptedException: sleep interrupted异常
i为负数是因为i超过int的范围。
并发编程-线程的认识、基本操作
interrupt这个属性是在JVM中进行维护。Thread.interrupted()对设置中断标识的线程复位,并且返回当前的中断状态。

相关标签: Java学习