前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Java并发编程基础

Java并发编程基础

作者头像
103style
发布2022-12-19 13:25:29
1810
发布2022-12-19 13:25:29
举报
文章被收录于专栏:Android开发经验分享

转载请以链接形式标明出处: 本文出自:103style的博客

Java并发编程的艺术笔记


目录

  • 线程的简介
  • 启动和终止线程
  • 线程间通信
  • 小结

线程的简介

  • 什么是线程 线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。
  • 为什么要使用多线程 目前的处理器核心越来越多,使用多线程能有更快的响应时间,并能有更好的编程模型。
  • 线程优先级 现代操作系统基本采用时分的形式调度运行的线程,操作系统分出每一个时间片会根据线程的优先级来分配,优先级越高的最先获取执行资源。 在Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。 线程优先级的设置:
    • 频繁阻塞(休眠或者I/O操作)的线程需要设置 较高优先级
    • 偏重计算(需要较多CPU时间或者偏运算)的线程则设置 较低的优先级,确保处理器不会被独占。

    在不同的 JVM 以及 操作系统 上,线程规划会存在差异,有些操作系统甚至会忽略对线程优先级的设定。 线程优先级不能作为程序正确性的依赖,因为操作系统可以完全不用理会 Java 线程对于优先级的设定。

  • 线程的状态
    • NEW 初始状态
    • RUNNABLE 运行状态
    • BLOCKED 阻塞状态
    • WAITING 等待状态
    • TIME_WAITING 超时等待状态
    • TERMINATED 终止状态

下图是状态变化的介绍:

Java线程状态变迁.png
Java线程状态变迁.png
  • Daemon线程 Daemon 线程是一种支持型线程,因为它主要被用作程序中后台调度以及支持性工作。 这意味着,当一个Java虚拟机中不存在非Daemon线程的时候,Java虚拟机将会退出Daemon线程不一定会执行完)。 可以通过调用Thread.setDaemon(true)将线程设置为Daemon线程。需在启动之前设置。

启动和终止线程

线程随着 thread.start() 开始启动 到 run() 方法执行完毕 结束。

我们可以通过 Thread.interrupted() 方法中断线程。

中断可以理解为线程的一个标识位属性,它表示一个运行中的线程是否被其他线程进行了中断操作。 线程通过检查自身是否被中断来进行响应,线程通过方法isInterrupted()来进行判断是否被中断,也可以调用静态方法Thread.interrupted()对当前线程的中断标识位进行复位。如果该线程已经处于终结状态,即使该线程被中断过,在调用该线程对象的isInterrupted()时依旧会返回false。 许多声明抛出InterruptedException的方法(例如Thread.sleep(long millis)方法)这些方法在抛出InterruptedException之前,Java虚拟机会先将该线程的中断标识位清除,然后抛出InterruptedException,此时调用isInterrupted()方法将会返回false

下面看一个例子:

代码语言:javascript
复制
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        while (true) {
            try {
                TimeUnit.SECONDS.sleep(10);
                System.out.println("time = " + System.currentTimeMillis() / 1000 + ", i = " + i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }, "t1");
    Thread t2 = new Thread(() -> {
        while (true) {
            i++;
        }
    }, "t2");

    //设置为 daemon 线程 并启动
    t1.setDaemon(true);
    t2.setDaemon(true);
    t1.start();
    t2.start();

    //让t1 t2 运行3s
    TimeUnit.SECONDS.sleep(3);

    //中断线程
    t1.interrupt();
    t2.interrupt();
    //获取中断状态
    System.out.println("time = " + System.currentTimeMillis() / 1000 + ", t1.isInterrupted() = " + t1.isInterrupted());
    System.out.println("time = " + System.currentTimeMillis() / 1000 + ", t2.isInterrupted() = " + t2.isInterrupted());
    //防止 t1 t2 立即退出
    TimeUnit.SECONDS.sleep(15);
}

输出结果:

代码语言:javascript
复制
time = 1560134045, t1.isInterrupted() = false
time = 1560134045, t2.isInterrupted() = true
java.lang.InterruptedException: sleep interrupted
	at java.lang.Thread.sleep(Native Method)
	at java.lang.Thread.sleep(Thread.java:340)
	at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
	at com.tcl.executors.Test.lambda$main$0(Test.java:16)
	at java.lang.Thread.run(Thread.java:745)
time = 1560134055, i = -576615207

根据输出结果,我们知道在线程sleep的时候,调用 isInterrupted() 会导致 sleep interrupted 异常,并且中断标记也被清除了。

已经被废弃的 suspend()(暂停)、resume()(恢复) 和 stop()(停止)。 废弃原因是,在调用方法之后,线程不会保证占用的资源被正常释放。 示例:

代码语言:javascript
复制
public static void main(String[] args) throws InterruptedException {
    Thread t = new Thread(() -> {
        while (true) {
            System.out.println("time = " + System.currentTimeMillis() / 1000);
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
    t.setDaemon(true);
    t.start();
    TimeUnit.SECONDS.sleep(3);

    t.suspend();
    System.out.println("suspend time = " + System.currentTimeMillis() / 1000);
    TimeUnit.SECONDS.sleep(3);

    t.resume();
    System.out.println("resume time = " + System.currentTimeMillis() / 1000);
    TimeUnit.SECONDS.sleep(3);

    t.stop();
    System.out.println("stop time = " + System.currentTimeMillis() / 1000);
    TimeUnit.SECONDS.sleep(3);
}

输出结果:

代码语言:javascript
复制
time = 1560134529
time = 1560134530
time = 1560134531
suspend time = 1560134532
resume time = 1560134535
time = 1560134535
time = 1560134536
time = 1560134537
stop time = 1560134538

线程间通信

volatile和synchronized关键字

volatile修饰的变量,程序访问时都需要在共享内存中去读取,对它的改变也必须更新共享内存,保证了线程对变量访问的可见性。

synchronized:对于 同步块 的实现使用了monitorentermonitorexit指令,而 同步方法 则是依靠方法修饰符上的ACC_SYNCHRONIZED来完成的。无论采用哪种方式,其本质是对一个对象的监视器monitor进行获取,而这个获取过程是排他的,也就是同一时刻只能有一个线程获取到由synchronized所保护对象的监视器。


等待/通知机制——wait和notify

指一个线程A调用了对象Owait()方法进入等待状态,而另一个线程B调用了对象Onotify()或者notifyAll()方法,线程A收到通知后从对象Owait()方法返回,进而执行后续操作。 等待:wait()wait(long)wait(long, int) 通知:notify()notifyAll() 示例:

代码语言:javascript
复制
private static Object object = new Object();

public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(1);
            synchronized (object) {
                System.out.println("t1 start object.wait(), time = " + System.currentTimeMillis() / 1000);
                object.wait();
                System.out.println("t1 after object.wait(), time = " + System.currentTimeMillis() / 1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    Thread t2 = new Thread(() -> {
        try {
            TimeUnit.SECONDS.sleep(5);
            synchronized (object) {
                System.out.println("t2 start object.notify(), time = " + System.currentTimeMillis() / 1000);
                object.notify();
                System.out.println("t2 after object.notify(), time = " + System.currentTimeMillis() / 1000);
            }

            synchronized (object) {
                System.out.println("t2  hold lock again, time = " + System.currentTimeMillis() / 1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    });
    t1.start();
    t2.start();
}

输出结果:

代码语言:javascript
复制
t1 start object.wait(), time = 1560138112
t2 start object.notify(), time = 1560138116
t2 after object.notify(), time = 1560138116
t2  hold lock again, time = 1560138116
t1 after object.wait(), time = 1560138116

1.使用wait()notify()notifyAll()时需要先对调用对象加锁,否则会报java.lang.IllegalMonitorStateException异常。 2.调用wait()方法后,线程状态由RUNNING变为WAITING,并将当前线程放置到对象的等待队列。 3.notify()notifyAll()方法调用后,等待线程依旧不会从wait()返回,需要调用notify()notifAll()的线程释放锁之后,等待线程才有机会从wait()返回。 4.notify()方法将等待队列中的一个等待线程从等待队列中移到同步队列中,而notifyAll()方法则是将等待队列中所有的线程全部移到同步队列,被移动的线程状态由WAITING变为BLOCKED。 5.从wait()方法返回的前提是获得了调用对象的锁。


等待/通知的经典范式

包括 等待方(消费者)和 通知方(生产者)。 等待方遵循以下原则:

  • 获取对象的锁。
  • 如果条件不满足,那么调用对象的wait方法,被通知后任要检查条件。
  • 条件不满足则执行对应的逻辑。

对应代码如下:

代码语言:javascript
复制
synchronized (对象) {
    while (条件不满足) {
        对象.wait();
    }
    对应的处理逻辑
}

通知方遵循以下原则:

  • 获取对象的锁。
  • 改变条件。
  • 通知所有在等待在对象上的线程。
代码语言:javascript
复制
synchronized (对象) {
    改变条件
    对象.notifyAll();
}

管道输入/输出流

PipedOutputStreamPipedInputStreamPipedReaderPipedWriter。 示例代码:

代码语言:javascript
复制
private static PipedWriter writer;
private static PipedReader reader;

public static void main(String[] args) throws InterruptedException, IOException {
    writer = new PipedWriter();
    reader = new PipedReader();
    //绑定输入输出
    writer.connect(reader);
    Thread t = new Thread(() -> {
        int res;
        try {
            while ((res = reader.read()) != -1) {
                System.out.print((char) res);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    });
    t.start();

    int res;
    while ((res = System.in.read()) != -1) {
        System.out.println(res);
        writer.write(res);
        //按回车结束
        if (res == 10) {
            break;
        }
    }
    writer.close();
}

输出:

代码语言:javascript
复制
Hi!
72
105
33
10
Hi!

Thread.join()

thread.join() 即当前线程需要在 thread 线程执行完之后才能继续执行,Java Thread.join()详解,这里已经做了详细介绍了,就不再赘述了。


ThreadLocal

ThreadLocal,即线程变量,是一个以ThreadLocal对象为 任意对象 的存储结构。 这个结构被附带在线程上,也就是说一个线程可以根据一个ThreadLocal对象查询到绑定在这个线程上的一个值。 可以通过 set(T t) 设置, get() 获取。 示例如下:

代码语言:javascript
复制
private static ThreadLocal<String> threadLocal = new ThreadLocal<>();

public static void main(String[] args) throws InterruptedException {
    String time = String.valueOf(System.currentTimeMillis() / 1000);
    System.out.println("time = " + time);
    threadLocal.set(time);
    Thread t = new Thread(() -> {
        String time1 = String.valueOf(System.currentTimeMillis());
        System.out.println("time1 = " + time1);
        threadLocal.set(time1);
    });
    t.start();
    TimeUnit.SECONDS.sleep(5);
    System.out.println("threadLocal.get() = " + threadLocal.get());
}

输出结果:

代码语言:javascript
复制
time = 1560146178
time1 = 1560146178263
threadLocal.get() = 1560146178

可以看到线程t中对threadLocal设置的值,并不影响main线程中的值。

set(T value)方法的源代码: 可以看到即把 当前ThreadLocal对象key传入的参数value 保存在 ThreadLocalMap中。

代码语言:javascript
复制
public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

小结

本文我们介绍了:

  • 什么是线程,为什么使用多线程,线程的优先级、状态变化 以及 Deamon线程。
  • 线程启动start() 和 中断线程interrupt(),以及过期的suspend()resume()stop()的作用。
  • 通过 volatilesynchronized来保证变量在多线程中的可见性,实现线程间通信。
  • 用 线程的 等待/通知 机制 来 实现线程间通信,使用的注意事项 以及 等待方通知方 需要遵循的原则。
  • 通过管道输入输出流 PipedOutputStreamPipedInputStreamPipedWirterPipedReader的介绍。
  • Thread.join() 的使用介绍。
  • ThreadLocal的使用介绍。
本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-06-10,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • Java并发编程的艺术笔记
  • 目录
  • 线程的简介
  • 启动和终止线程
  • 线程间通信
    • volatile和synchronized关键字
      • 等待/通知机制——wait和notify
        • 等待/通知的经典范式
          • 管道输入/输出流
            • Thread.join()
              • ThreadLocal
              • 小结
              相关产品与服务
              容器服务
              腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
              领券
              问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档