并发编程和JVM调优

2021-01-09  本文已影响0人  极速魔法

并发编程三要素

守护线程和非守护线程

非守护线程运行结束,jvm退出,与守护线程是否存在无关。
守护线程通常用在作为垃圾收集器或缓存管理器的应用程序中,执行辅助任务

Callable 有返回值

package com.lagou.concurrent.demo;

import java.util.concurrent.*;

public class Main2 { 
    public static void main(String[] args) throws ExecutionException, InterruptedException {

    ThreadPoolExecutor executor = new ThreadPoolExecutor( 5, 5, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(10) ) { 
        @Override protected void afterExecute(Runnable r, Throwable t) { 
        super.afterExecute(r, t); 
        } 
    };

    Future<String> future = executor.submit(new MyCallable());

    String s = future.get(); System.out.println(s);

    executor.shutdown();

    }
}

synchronized关键字

给某个对象加锁
实例方法锁加在对象 myClass上;静态方法锁加在 MyClass.class

锁🔐的本质

锁是一个对象

  1. 这个对象内部得有一个标志位(state变量),记录自己有没有被某个线程用。

  2. 如果这个对象被某个线程占用,记录这个线程的thread ID。

  3. 这个对象维护一个thread id list,记录其他所有阻塞的、等待获取拿这个锁的线程。在当前线程释 放锁之后从这个thread id list里面取一个线程唤醒。

轻量级阻塞和重量级阻塞

能够被中断的阻塞称为轻量级阻塞,对应的线程状态是WAITING或者TIMED_WAITING;
而像 synchronized 这种不能被中断的阻塞称为重量级阻塞

Interrupted异常

只有那些声明了会抛出InterruptedException的函数才会抛出异常,
也就是下面这些常用的函数 sleep(),wait(),join()

thread.interrupted()的精确含义是唤醒轻量级阻塞,而不是字面意思“中断一个线程”。如果线程此时恰好处于WAITING 或者TIMED_WAITING状态,就会抛出一个InterruptedException,并且线程被唤醒。

thread.isInterrupted()与Thread.interrupted()的区别

并发和并行

并发: 一个cpu核心执行多个任务
并行:不同核心上同时运行多个任务

happen-before

如果A happen-before B,意味着A的执行结果必须对B可见,也就是保证跨线程的内存可见性

CopyOnWrite

CopyOnWrite指在“写”的时候,不是直接“写”源数据,而是把数据拷贝一份进行修改,再通过悲观锁或 者乐观锁的方式写回。
为了在“读”的时候不加锁。

同步工具

Semaphore

信号量,提供了资源数量的并发访问控制

// 5份共享资源。第二个参数表示是否是公平
Semaphore myResources = new Semaphore(5, true);
//工作线程每获取一份资源,就在该对象上记下来
myResources.acquire();

// 工作线程每归还一份资源,就在该对象上记下来
myResources.release();

CountDownLatch

主线程要等待5个 Worker 线程执行完才能退出

CountDownLatch latch = new CountDownLatch(5);

// 子线程中任务完成
latch.countDown();

// 主线程等待 
latch.await();

自旋与阻塞

阻塞:放弃CPU,进入阻塞状态,等待后续被唤醒,再重新被操作系统调度。 自旋:多核cpu,不放弃CPU,空转,不断重试,也就是所谓的“自旋”。

Lock与Condition

“可重入锁”是指当一个线程调用 object.lock()获取到锁,进入临界区后,再次调用object.lock(),仍然可 以获取到该锁。

ForkJoin

RecursiveAction 没有返回值
RecursiveTask<Long>,有返回值。

求1到n个数的和
继承RecursiveTask,重写compute()方法

public class ForkJoinPoolDemo02 {

    static class SumTask extends RecursiveTask<Long> { 
        private static final int THRESHOLD = 10; 
        private long start; 
        private long end;

        public SumTask(long n) { this(1, n); }

        public SumTask(long start, long end) { 
            this.start = start; 
            this.end = end; 
        }

        @Override
        protected Long compute() {
            long sum = 0; 
            // 如果计算的范围在threshold之内,则直接进行计算 
            if ((end - start) <= THRESHOLD) { 
                for (long l = start; l <= end; l++) {
                    sum += l;
                } 
            } else { 
                // 否则找出起始和结束的中间值,分割任务
                long mid = (start + end) >>> 1;

                SumTask left = new SumTask(start, mid);
                SumTask right = new SumTask(mid + 1, end);
                left.fork(); 
                right.fork(); 
                // 收集子任务计算结果 
                sum = left.join() + right.join();
            } 
            return sum;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException { 
        SumTask sum = new SumTask(100); 
        ForkJoinPool pool = new ForkJoinPool();

        ForkJoinTask<Long> future = pool.submit(sum);

        Long aLong = future.get(); 
        System.out.println(aLong); 
        pool.shutdown();
    }
}

JVM

JVM与操作系统

JVM 与操作系统之间的关系:JVM 上承开发语言,下接操作系统,它的中间接口就是字节码

JVM 内存

JVM 内存共分为虚拟机栈、堆、方法区、程序计数器、本地方法栈五个部分

线程私有的: ①程序计数器 ②虚拟机栈 ③本地方法栈
线程共享的: ①堆 ②方法区 直接内存(非运行时数据区的一部分)

虚拟机栈

Java虚拟机栈和线程同时创 建,用于存储栈帧
每个方法在执行时都会创建一个栈帧(Stack Frame), 用于存储局部变量表、操作数栈、动态 链接、方法出口等信息。

唯一目的就是存放对象实例,几乎所有的对象实例以及数组都要在这里分配内存。在内存中不连续
设置堆空间大小

-Xmx20m -Xms5m
当下Java应用最大可用内存为20M, 最小内存为5M

永久代和元空间

  1. 存储位置不同:永久代在物理上是堆的一部分,和新生代、老年代的地址是连续的,而元空间属于本地内存。

  2. 存储内容不同:在原来的永久代划分中,永久代用来存放类的元数据信息、静态变量以及常量池等。 现在类的元信 息存储在元空间中,静态变量和常量池等并入堆中,相当于原来的永久代中的数据,被元空间和堆内存给瓜分了。

常量池和运行时常量池

字节码文件中,内部包含了常量池
方法区中,内部包含了运行时常量池
常量池:存放编译期间生成的各种字面量与符号引用
运行时常量池:常量池表在运行时的表现形式

JVM加载机制

类加载器ClassLoader角色

  1. class file 存在于本地硬盘上,可以理解为设计师画在纸上的模板,而最终这个模板在执行的时候是要加载到 JVM当中来根据这个文件实例化出n个一模一样的实例。
  2. class file 加载到JVM中,被称为DNA元数据模板。
  3. 在 .class文件 --> JVM --> 最终成为元数据模板,此过程就要一个运输工具(类装载器Class Loader),扮演一 个快递员的角色。Class对象存放在方法区

类使用的阶段

类从被加载到虚拟机内存中开始,到卸载出内存,
它的整个生命周期包括:
加载(Loading)、验证 (Verification)、
准备(Preparation)、解析(Resolution)、初始化(Initiallization)、使用(Using)和卸载 (Unloading)这7个阶段

初始化

<clinit> 由Javac编译器的 自动生成物
执行类变量的赋值动作和静态语句块(static{}块) 中的 语句

垃圾回收

垃圾回收算法

判断对象生死算法

收集死亡对象方 法

有四种,如标记-清除算法、标记-复制算法、标记-整理算法。

垃圾收集器

垃圾收集器是算法的落地实现


image
上一篇 下一篇

猜你喜欢

热点阅读