听说你线程和对象八股很狂?say say look

493 阅读5分钟

线程是我们在日常工作中经常听到的词,如果你开发很多年只有CRUD,没有多线程操作过,那么你一定不是一个会炒菜的好javaer。

一、走进线程

刚入门的时候我们一直操作的都是单线程进行一些类似于OA系统的开发工作,有时候可能会用Thread.sleep来做点小动作,除此便没了,我们要清楚线程只是一个工作单元,我们的软件或者我们的系统是由很多这样的工作单元来组成,之前很多文章都是先说线程的状态和多线程,但很少说线程的结构,而是在JVM里说去了,今天我放这里说。

1.1 组成结构

首先线程是一个私有的东西,在JVM中呢我们的对象都是在堆这样的共享区,好了,接下来我们通过new Thread()创建线程,以下是创建过程,前面还有一步就是如果我们不给该线程命名,线程会自己通过Thread-加上一个线程创建自增的数字变量,下面代码是无参的方式初始化,有参最终也会调用该方法只是判断走的逻辑不一样;

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize, AccessControlContext acc,
                      boolean inheritThreadLocals) {
        if (name == null) {
            throw new NullPointerException("name cannot be null");
        }
        this.name = name;
        //该处表示调用本地方法获取当前线程
        Thread parent = currentThread();
        SecurityManager security = System.getSecurityManager();
        //咱们是无参构造,所以此处为null满足
        if (g == null) {
        //如果存在安全管理器会从该处获得线程group(下面解释)
            if (security != null) {
                g = security.getThreadGroup();
            }
            //如果依然为空,则会从当前线程获取线程group
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }
        //检查权限,通过上面那个安全管理器来check
        g.checkAccess();
        if (security != null) {
        //这段简单意思就是是否重写了Thread,如果getclass就是Thread
        //返回false,跳过安全检查,否则要进行安全检查
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }
        //增加线程组中未启动线程的计数
        g.addUnstarted();
        this.group = g;
        //判断parent是否是守护线程(注意parent就是当前线程)
        this.daemon = parent.isDaemon();
        //获取parent权重
        this.priority = parent.getPriority();
        //这段判断简单说就是如果变化了重新获取否则直接获取
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        //下面两个咱们无参初始化,所以都是给null
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
        //此处大白话就是获取当前线程的给咱们创建的线程
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        //无参表示0,不同平台效果不一样,字面意思:堆栈大小        
        this.stackSize = stackSize;
        //获取线程ID
        tid = nextThreadID();
    }

我也不知道线程组是什么,哈哈, a972880380654b389246a3179add2cca~tplv-k3u1fbpfcp-jj-mark_3024_0_0_0_q75.jpg

百度的结果是:

通过将线程分组到不同的线程组中,可以更方便地对一组线程进行操作和管理。线程组可以拥有子线程组,从而创建一个层次结构,使得线程组之间形成父子关系。

主要用途包括:线程组可以统一管理和监控一组相关的线程,方便对其进行集中控制。 可以通过线程组设置线程的优先级、守护状态、异常处理器等属性,从而批量设置一组线程的属性。 可以使用线程组的interrupt()方法来中断整个线程组中的所有线程。 可以通过线程组的activeCount()方法获取线程组中活动线程的数量。 总之,线程组提供了一种结构化和组织性的方式来管理线程,使得线程的操作和管理更加方便和灵活

以上就是我们new Thread干的事,不难,但我感觉好像大部分情况下都应该是继承父类的配置; 从上面初始化过程中我们发现我们的线程包含线程id权重线程组、以及一个状态变量 private volatile int threadStatus = 0;还有包括一个执行具体任务的Runnable,其他就是类加载器和线程上下文、队列的一些东西了,咱们不用管的太深了。

1.2 对象的组成结构

上面看完线程的结构我们得再看下对象的结构这样就能联系起来了对象这玩意其实网上一搜一大把介绍,咱们简单说下,对象是由对象头实例数据对齐填充(数据)三个部分组成。 其中

  1. 实例数据就是单纯的对象属性
  2. 对齐填充是因为对象大小必须是8字节的倍数,不够就用它填

我们主要说下对象头主要由markword、和指针以及数组才有的数组长度组成 指针就是当前对象所属于的类,和我们线程有直接关系的是markword,所以咱们主要说下这: 我们平时听说的锁就在这里面,此处还保存了hashcode值这是一个地址值、gc年龄和咱们的线程id, 扯了这么多,其实咱们的对象和线程就这点关系就这个线程id;

单线程就是我们一个线程去处理这个对象,然后返回结果;

多线程就是多个线程去处理这个对象,如果我们不加处理,每个线程都可以读到这个对象的数据进入线程内处理然后返回结果,导致结果可能不是我们想要的,于是线程id和锁以及volatile便出现了 由于涉及到对象的回收所以GC年龄也出来了;

1.2 最后

我们发现其实在这个过程中线程都是独立的,他们有自己的状态和栈,而且你去翻看线程的源码,里面的属性基本都是私有的,所以我们并不需要太关注线程,不管是多线程还是单线程(除非特殊场景),我们只需要关注对象本身,而当前jdk为我们做了很多,对象回收,锁升级,如果我们的能力不够超群但是有这些业务要求,我们只需要锁好我们的共享属性值,共享方法即可,剩下的交给jdk吧,如果等级起来了,可以设置优先级,设置队列,用更好的办法来替代锁以此达到优化的目的;

b8b2188a06c0438497e27b393f86ab97~tplv-k3u1fbpfcp-jj-mark_3024_0_0_0_q75.awebp

ok!完成

四、总结

我也曾是个快乐的童鞋,也有过崇高的理想,直到我面前堆了一座座山,脚下多了一道道坑,我。。。。。。!