背景:
进程和线程的区别:
进程的内存大小为: 堆内存 + 线程数量 * 栈内存, 即线程数量 =( 最大地址空间[MaxProcessMemory] - JVM 堆内存 - 系统保留内存[ReservedOsMemory] )/ ThreadStackSize(XSS), 从中可以看出, 线程的数量随栈内存的增多而减少.
线程是程序执行的一个路径, 每一个线程都有自己的局部变量表, 程序计数器 (指向正在执行的指令指针) 以及各自的生命周期. 当启动了一个 Java 虚拟机 (JVM) 时, 从操作系统开始就会创建一个新的进程(JVM 进程),JVM 进程将会派生或者创建很多线程.
一个线程的创建肯定是由另一个线程完成的;
被创建线程的父线程是创建它的线程;
线程会带来额外的开销, 如 CPU 调度时间, 并发控制开销等; 每个线程在自己的工作内存交互, 加载和存储主内存控制不当会造成数据不一致.
一. 线程创建方式:
构造 Thread 类: 实现线程的执行单元 run 有两种方式, 分别是下面
继承 Thread, 重写 run 方法: Thread 实现了 Runnable 接口, 使用 start 开启线程, start 开启后线程会加入调度器, 然后调用 run 方法, start 会调用 start0 本地方法跟 OS 进行交互运行; 下面是 start 源码解析
- /**
- * Causes this thread to begin execution; the Java Virtual Machine
- * calls the <code>run</code> method of this thread.
- * 开启线程, JVM 会调用 run 方法[start 使用了模板方法]
- * <p>
- * It is never legal to start a thread more than once.
- * 不能两次启动线程, 否则报 IllegalThreadStateException 异常
- * In particular, a thread may not be restarted once it has completed
- * execution.
- * 一个线程生命周期结束, 也就是到了 TERMINATED 状态, 再次调用 start 方法是不允许的,
- * 也就是 TERMINATED 状态没法回到 RUNNABLE/RUNNING 状态.
- *
- * @exception IllegalThreadStateException if the thread was already
- * started.
- * @see #run()
- * @see #stop()
- */
- public synchronized void start() {// 线程安全的
- /**
- * This method is not invoked for the main method thread or "system"
- * group threads created/set up by the VM. Any new functionality added
- * to this method in the future may have to also be added to the VM.
- * 这个方法不会被主线程调用或通过虚拟机系统线程组创建起来. 未来任何添加到该方法里的新功能可能需要加入到虚拟机中
- *
- * A zero status value corresponds to state "NEW".
- * 线程被构造后的 new 状态, threadStatus 的属性值是 0
- */
- if (threadStatus != 0)
- throw new IllegalThreadStateException();
- /* Notify the group that this thread is about to be started
- * so that it can be added to the group's list of threads
- * and the group's unstarted count can be decremented.
- * 通知线程组新线程将要启动, 以便它可以添加到线程组列表并且线程组没有开始计数 */
- group.add(this);// 加入线程组
- boolean started = false;
- try {
- start0();// 调用本地方法
- started = true;
- } finally {
- try {
- if (!started) {// 启动失败
- group.threadStartFailed(this);// 线程启动失败, 从组中移除该线程
- }
- } catch (Throwable ignore) {
- /* do nothing. If start0 threw a Throwable then
- it will be passed up the call stack */
- }
- }
- }
- void add(Thread t) {
- synchronized (this) {
- if (destroyed) {// 线程组状态校验
- throw new IllegalThreadStateException();
- }
- if (threads == null) {
- threads = new Thread[4];// 初始化长度为 4 的线程组
- } else if (nthreads == threads.length) {
- threads = Arrays.copyOf(threads, nthreads * 2);// 数组满了就扩容 2 倍
- }
- threads[nthreads] = t;// 当前线程添加到线程组中
- // This is done last so it doesn't matter in case the
- // thread is killed
- nthreads++;// 线程数 + 1
- // The thread is now a fully fledged member of the group, even
- // though it may, or may not, have been started yet. It will prevent
- // the group from being destroyed so the unstarted Threads count is
- // decremented.
- nUnstartedThreads--;// 未启动线程数 - 1
- }
- }
- private native void start0();// 本地方法调用重写的 run 方法
- void threadStartFailed(Thread t) {
- synchronized(this) {
- remove(t);// 移除当前线程
- nUnstartedThreads++;// 没有启动的线程数量 + 1
- }
- }
- //======================= 测试 ============================
- Thread t = new Thread(){
- @Override
- public void run(){
- try {
- TimeUnit.SECONDS.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- t.start();
- t.start();// 不能两次启动, 第二次启动是不允许的, 报 IllegalThreadStateException, 此时该线程是处于运行状态
- //======================= 测试 ==============================
- Thread t = new Thread(){
- @Override
- public void run(){
- try {
- TimeUnit.SECONDS.sleep(10);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- };
- t.start();
- TimeUnit.SECONDS.sleep(10);// 设置休眠时间, 上面的线程的生命周期已经终止, 下面再次启动报 IllegalThreadStateException
- t.start();
实现 Runnable 接口, 重写 run 方法并且将 Runnable 实例用作构造 Thread 的参数 [单继承有局限性, 推荐使用接口] : 将线程的控制(start) 和业务逻辑 (run) 的运行彻底分离开来, 使用的是策略模式; Thread 的 run 方法是不能共享的, 但 Runnbale 的 run 方法可以共享, 使用同一个 Runnable 的实例构造不同的 Thread 实例; 把实现类对象 (实现 Runnable 接口的类的实例化) 放入代理类对象 (Thread 构造方法) 中, 使用的是代理模式; 下面是静态代理的代码解释:
- public class StaticProxy {
- public static void main(String[] args) {
- new Weeding(new Me()).happyMarry();
- // new Thread(对象).start(); 类似
- }
- }
- interface Marry {
- void happyMarry();
- }
- // 真实角色
- class Me implements Marry {
- @Override
- public void happyMarry() {
- System.out.println("me will marry!");
- }
- }
- // 代理对象
- class Weeding implements Marry{
- // 真实角色
- private Marry marry;
- public Weeding(Marry marry){
- this.marry=marry;
- }
- @Override
- public void happyMarry() {
- System.out.println("start");
- marry.happyMarry();
- System.out.println("end");
- }
- }
实现 Callable 接口, 重写 call 方法, Future 获取返回值: Callable 能接受一个泛型, 然后在 call 方法中返回一个指定类型的值;
- public interface Callable<V> {
- V call() throws Exception;
- }
- // 线程池队列开启线程, 不会产生脏读数据
- // 使用步骤:
- //1. 创建目标对象 new
- //2. 创建执行服务线程池
- //3. 提交执行 submit
- //4. 获取结构 get
- //5. 关闭服务 shutdownNow
- public class MyThread implements Callable {
- private static int count = 20;
- public static void main(String[] args) throws ExecutionException, InterruptedException {
- MyThread m1 = new MyThread();
- MyThread m2 = new MyThread();
- MyThread m3 = new MyThread();
- MyThread m4 = new MyThread();
- ScheduledExecutorService service = new ScheduledThreadPoolExecutor(2,
- new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build());
- Future submit = service.submit(m1);
- Future submit1 = service.submit(m2);
- Future submit2 = service.submit(m3);
- Future submit3 = service.submit(m4);
- System.out.println(submit.get());
- System.out.println(submit1.get());
- System.out.println(submit2.get());
- System.out.println(submit3.get());
- service.shutdown();
- }
- @Override
- public Object call() throws Exception {
- count--;
- return count;
- }
- }
匿名内部类;
- new Thread(){// 相当于继承 Thread 的方式
- public void run(){
- System.out.println("thread1 start ...");
- }
- }.start();
- new Thread(new Runnable() {// 相当于实现 Runnable 接口的方式
- @Override
- public void run() {
- System.out.println("thread2 start ....");
- }
- }).start();
定时器(Timer);
- Timer timer = new Timer();// 创建时间器
- timer.schedule(new TimerTask() {// 使用 schedule, 参数为定时器任务并重写 run 方法
- @Override
- public void run() {
- System.out.println("timer task is run");
- }
- }, 0, 1000);
线程池(内部使用队列, 所以加入线程池的线程是顺序执行): 使用 execute 和重写 Runnbale 的 run 方法;
- ScheduledExecutorService service = new ScheduledThreadPoolExecutor(2,
- new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build());
- service.execute(new Runnable() {
- @Override
- public void run() {
- System.out.println("run test");
- }
- });
lambda 表达式;
- new Thread(()-> {
- for(int i = 1 ; i<10 ; i++){
- System.out.println("It is a lambda function!");
- }
- }).start();
Spring 方式(@Async 注解);
- @Test
- public void test() {
- run();
- }
- @Async
- public void run(){
- System.out.println("Async Test");
- }
二. 线程生命周期
new 新生状态: 当用 new 创建一个 Thread 对象时, 此时它并不处于执行状态, 因为没有调用 star 启动该线程, 那么线程的状态为 new 状态, 也就是说, 它只是 Thread 对象的状态, 在没有 start 之前, 该线程是不存在的;
runnable 就绪状态: 线程对象进入 runnable 就绪状态必须调用 start 方法, 那么此时才是真正地在 JVM 进程中创建了一个线程; 就绪状态不会直接进入阻塞状态和死亡状态, 即使是在线程的执行逻辑中调用 wait,sleep 或其他 block 的 IO 操作等, 也必须先获得 CPU 的调度执行权才可以, 严格来说, 就绪状态的线程只能意外终止或进入运行状态;
running 运行状态: 一旦 CPU 通过轮询或其他方式从任务可执行队列中选中了线程, 此时它才能真正地执行自己的逻辑代码; 一个正在 running 状态的线程事实上也是一个 runnable 的, 但是反过来则不成立;
sleep: 使当前线程进入指定毫秒级的休眠, 暂停执行, 但不会放弃 monitor 锁的所有权, 即不会释放锁资源; 使用 TimeUnit 来替代 Thread.sleep, 省去了时间单位的换算步骤;
yield: 属于一种启发式的方法, 其会提醒调度器我愿意放弃当前的 CPU 资源, 如果 CPU 的资源不紧张, 则会忽略这种提醒; yield 只是一个提示(hint),CPU 调度器并不会担保每次都能满足 yield 提示;
sleep 和 yield 的区别:
sleep 会导致当前线程暂停指定的时间, 没有 CPU 时间片的消耗;
yield 只是对 CPU 调度器的一个提示, 如果 CPU 调度器没有忽略这个提示, 它会导致线程上下文的切换;
sleep 会使线程短暂 block, 会在给定的时间内释放 CPU 资源;
yield 会使 running 状态的线程进入 runnable 状态(如果 CPU 调度器没有忽略这个提示的话);
sleep 几乎百分之百地完成了给定时间的休眠, 但 yield 的提示并不能一定担保;
一个线程 sleep 另一个线程 interrupt 会捕获到中断信号, 而 yield 则不会;
join:join 某个线程 A, 会使当前线程 B 进入等待, 直到线程 A 结束生命周期; 可以使用 join 来达到线程顺序执行的效果;
wait: 表示线程一直等待, 直到其他线程通知, 与 sleep 不同的是它会释放锁; 调用 wait 会加入 wait set 中;
notify: 唤醒一个处于等待状态的线程;
notifyAll: 唤醒同一个对象上所有调用 wait 方法的线程, 优先级高的线程优先调度;
synchronized: 同步, 内置锁, 互斥锁, 锁定共享资源, JVM 指令是 monitor enter 和 monitor exit;synchronized 的指令严格遵守 java happens-before 规则, 一个 monitor exit 指令之前必定要有一个 monitor enter;
锁信息存在对象头中:
Mark Word
线程 id
Epoch
对象的分代年龄信息
是否是偏向锁
锁标志位
Class Metadata Address
来源: https://www.cnblogs.com/huangrenhui/p/12703353.html