导图社区 Java高并发核心编程
Java高并发核心编程、通过Thread 类创建线程对象,将Runnable 实例作为 实际参数 传递给Thread 类构造器,由Thread构造器将该Runnable 实例赋值给自己的target 执行目标属性。
编辑于2023-01-10 21:07:25 广东Java高并发核心编程
Java高并发核心编程
Java高并发核心编程
线程的基本原理
进程和线程的区别
线程是 "进程代码段" 的一次顺序执行流程,一个进程由一个或多个现线程组成,一个进程至少有一个线程
线程是CPU调度的最小单位,进程是操作系统分配资源的最小单位。线程的划分尺度小于进程,使得多线程程序的并发性高
线程是出于高并发的调度诉求从进程内部演进而来的。 线程的出现即充分发挥了CPU的计算性能,又弥补了进程调度过于笨重的问题
进程之间是相互独立的,但进程内部的各个线程之间并不完全独立。各个线程之间共享进程的方法区内存、堆内存、系统资源(文件句柄、 系统信号等)
切换速度不同:线程上下文切换比进程上下文切换要快的多。所以,有的时候, 线程也称为轻量级进程
子主题6
组成线程的三部分
线程描述信息
线程ID。 线程的唯一标识,同一个进程内不同线程ID不会重叠
线程名称。 主要是方便用户识别,用户可以指定线程的名字,如没指定,系统会自动分配
线程优先级。表示线程调度的优先级,优先级越高,获得执行的机会越大
线程状态。表示当前线程的执行状态, 为新建、就绪、运行、堵塞、结束等状态中的一种
其他。 例如是否为守护线程等,后续再说
程序计数器
记录线程下一个指令的代码段内存地址
栈内存
栈帧(方法帧)的操作是后进先出的模式,这也是标准的栈操作模式,因此存放方法帧的内存也被叫做栈内存、
Thread类
线程ID
private long tid
保存线程的ID
线程名称
private String name
保存线程的实例名字
线程优先级
private int priority
保存一个 Thread 线程实例的优先级
是否为守护线程
private boolean daemon = false
保存 Thread 线程实例的守护状态,参数为 true 为守护线程
守护线程是在进程运行时提供某种后台服务的线程, 比如垃圾回收(GC)线程
线程的状态
private int threadStatus
以整数形式保存当前线程的状态
public Thread.State.getState()
返回当前线程状态
状态
NEW
新建
RUNNABLE
就绪、运行
BLOCKED
堵塞
WAITING
等待
TIMED_WAITING
计时等待
TERMINATED
结束
线程的启动和运行
public void start()
用来启动一个线程,当调用 start() 方法后, jvm 才会开启一个新的线程来执行用户定义的线程代码逻辑
public void run()
作为线程代码逻辑的入口,run 方法 不是由用户调用,当调用 start() 后 启动一个线程, 线程获得cpu执行时间,便进入 run() 方法体中执行具体的用户线程代码
取得当前线程
public static Thread currentThread()
用于获取当前线程的实例对象
等等。。。
创建线程的4种方式
继承Thread类创建线程目标类
package org.example;
public class CreateDemo { public static final int MAX_TUAN = 5; public static final String getCurThreadName(){ return Thread.currentThread().getName(); }
static int threadNo = 1; static class DemoThread extends Thread{ public DemoThread(){ super("DemoThread-" + threadNo++); } public void run() { for (int i = 1; i <MAX_TUAN; i++){ System.out.println(getName() + ", 轮次:" + i); } System.out.println(getName() + " 运行结束. "); } } public static void main(String[] args){ Thread thread = null; for (int i = 0; i < 2; i++) { thread = new DemoThread(); thread.start(); } System.out.println(getCurThreadName() + " 运行结束. "); }
需要 继承 Thread 类,创建一个新的线程类。
同时重写run() 方法, 将需要并发的业务代码编写在 run() 方法中
实现Runnable接口创建线程目标类
package org.example;
public class CreateDemo2 { public static final int MAX_TURN = 5;
static int threadNo = 1; static class RunTarget implements Runnable{ @Override public void run() { for (int i = 0; i < MAX_TURN; i++) { System.out.println(ThreadUtil.getCurThreadName() + ", 轮次:" + i); } System.out.println("运行结束"); } } public static void main(String[] args) { Thread thread = null; for (int i = 0; i < 2; i++) { Runnable target = new RunTarget(); thread = new Thread(target, "RunnableThread" + threadNo++); thread.start(); } }
实现接口的方式
定义一个新类实现Runnable 接口
实现 Runnable接口中的run() 抽象方法, 将线程代码逻辑存放在该run() 实现版本中
通过Thread 类创建线程对象,将Runnable 实例作为 实际参数 传递给Thread 类构造器,由Thread构造器将该Runnable 实例赋值给自己的target 执行目标属性
调用 Thread 实例的start() 方法启动线程
线程启动之后, 线程的run() 方法将被JVM 执行, 该run() 方法 将调用 target 属性的 run() 方法, 从而完成Runnable实现类中业务逻辑代码的并发执行
创建 Runnable 接口 的两种方式
通过匿名内部类创建Runnable 线程目标类
package org.example;
public class CreateDemo2 { public static final int MAX_TURN = 5;
static int threadNo = 1; public static void main(String[] args) { Thread thread = null; for (int i = 0; i < 2; i++) { thread = new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < MAX_TURN; i++) { System.out.println(ThreadUtil.getCurThreadName() + ", 轮次:" + i); } System.out.println(ThreadUtil.getCurThreadName()+"运行结束"); } }, "RunnableThread" + threadNo++); thread.start(); } System.out.println(ThreadUtil.getCurThreadName()+"运行结束"); }
使用 Lambda 表达式优雅的创建Runable 线程目标类
package org.example;
public class CreateDemo2 { public static final int MAX_TURN = 5;
static int threadNo = 1; public static void main(String[] args) { Thread thread = null; for (int i = 0; i < 2; i++) { thread = new Thread(()->{ for (int j = 0; j < MAX_TURN; j++) { System.out.println(ThreadUtil.getCurThreadName() + ", 轮次:" + j); } System.out.println(ThreadUtil.getCurThreadName()+"运行结束"); }, "RunnableThread" + threadNo++); thread.start(); } System.out.println(ThreadUtil.getCurThreadName()+"运行结束"); }
@Functionallnterface声明了一个函数式接口
java函数式接口定义是 “函数式接口”是有且仅有一个抽象方法的接口
通过Runnable接口方式创建线程目标类的优缺点
优点
可以避免由于java 单继承带来的局限性。如果异步逻辑所在类已经继承一个基类,就没有办法再继承Thread 类
逻辑和数据更的分离,通过实现Runnable 接口 的方法创建多线程更加适合同一个资源被多段业务逻辑并行处理的场景
缺点
如果访问当前线程的属性(甚至控制当前线程), 不能直接访问Thread 的实例方法,必须通过Thread.currentThread() 获取当前线程实例,才能控制和访问当前线程
所创建的类并不是线程类, 而是线程的 target 执行目标类,需要将实例作为参数传入线程构造器,才能创建真正的线程
通过实现Runnable接口的方式创建线程
逻辑和数据更好的分离实例
package org.example;
import java.util.concurrent.atomic.AtomicInteger;
import static java.lang.Thread.sleep;
public class SalesDemo {
public static final int MAX_AMOUNT = 5; static class StoreGoods extends Thread { StoreGoods(String name) { super(name); } private int goodsAmount = MAX_AMOUNT; @Override public void run() { for (int i = 0; i <= MAX_AMOUNT; i++) { if (this.goodsAmount > 0) { System.out.println(ThreadUtil.getCurThreadName() + "卖出一件, 还剩:" + (--goodsAmount)); try { sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } } System.out.println(ThreadUtil.getCurThreadName() + "运行结束"); } } static class MallGoods implements Runnable{ private AtomicInteger goodsAmout = new AtomicInteger(MAX_AMOUNT); @Override public void run() { for (int i = 0; i < MAX_AMOUNT; i++) { if (this.goodsAmout.get() > 0){ System.out.println(ThreadUtil.getCurThreadName() + "卖出一件, 还剩:" + (goodsAmout.decrementAndGet())); try { sleep(10); } catch (InterruptedException e) { throw new RuntimeException(e); } } System.out.println(ThreadUtil.getCurThreadName() + "运行结束"); } } } public static void main(String[] args) throws InterruptedException { System.out.println("商店版本销售"); for (int i = 0; i <= 2; i++) { Thread thread = null; thread = new StoreGoods("店员-"+ i); thread.start(); } Thread.sleep(10); System.out.println("商场版本销售员"); MallGoods mallGoods = new MallGoods(); for (int i = 0; i <= 2; i++) { Thread thread = null; thread = new Thread(mallGoods, "商场销售员-"+i); thread.start(); } System.out.println(ThreadUtil.getCurThreadName() + "运行结束。"); }
逻辑和数据更好的分离 原理
通过继承Thread类实现多线程能更好的做到 多个线程并发地的完成各自的开发任务
通过实现Runnable 接口 实现多线程能更好地做到多个线程并发地完成同一个任务
通过实现Runnable 接口实现多线程时,如果数据资源存在多线程共享的情况,那么数据共享资源需要使用原子类型(而不是普通数据类型),或者需要进行线程的同步控制,以保证对共享数据操作时不会出现线程安全问题
异步执行任务在大多数情况下是通过线程池去提交的,而很少通过创建一个新的线程去提交,更多的做法是,通过实现Runnable 接口创建异步执行任务,而不是继承Thread 去创建异步执行任务
使用Collable和FutureTask创建线程
通过线程池创建线程
util
ThreadUtil
package org.example;
public class ThreadUtil {
public static String getCurThreadName(){ return Thread.currentThread().getName(); }
分支主题3
子主题1
子主题2
子主题1
子主题1
子主题2
子主题3
子主题5
子主题6
概要
子主题3
分支主题4
子主题2
子主题1
概要
子主题3
子主题4
分支主题5
子主题1
子主题2
子主题3
子主题4
分支主题6
子主题1
子主题2
子主题3
子主题4
概要
分支主题7
子主题1
子主题2
子主题3
子主题4
分支主题8