并发编程——基础拾遗

并发编程基础拾遗

在看并发的书或者看并发相关的博客时,会发现一些知识点会遗漏或者之前没有看到,这里去总结一下。

创建线程相关

main线程

我们通常会通过写main方法去创建多个线程,main线程是非守护线程,代表方法的入口。这个时候如果用jconsole去看的话,会发现这时也会启用很多后台线程:比如GC线程、计算引用线程、JMX线程。

线程生命周期
  • 新建状态
  • 可运行状态
  • 运行状态
  • 阻塞状态
  • 死亡状态

这些个状态之间的转换每次都记得不是很牢固。

当新建一个线程之后,这个线程处于新建状态,这时调用t.start()方法,线程就会进入可运行状态,这个状态下线程还没有真正的执行。当os分配给当前线程时间片的时候,线程会进入运行状态。当线程处于运行状态时,时间片用完或者调用Thread.yield()方法,线程则变回为可运行状态,同样若再获得os的时间片,线程将会再次进入运行状态。当正在运行状态的线程遇到Thread.sleep或者其他线程join的时候,会进入阻塞状态,直到sleep结束或者join线程结束之后线程进入可运行状态等待时间片的分配。(这里不能直接由阻塞状态直接变为运行状态)。当线程处于运行状态的时候,对象调用了o.wait方法,那么线程就会进入等待队列,等待被其他线程唤醒进入锁池,还有如果线程未持有对象的锁而被阻塞的时候也会进入到锁池,直到拿到对象锁标志之后,才能到可运行状态,再次等待时间片的分配。当线程的run方法结束,或者线程是一个守护线程是main线程结束,或者异常退出之后,线程进入到死亡状态。

start()方法和run方法的一个设计

start方法和run()方法是Thread类中的两个方法,我们启动一个线程的时候要调用start方法。

public synchronized void start() {

    if (threadStatus != 0)
        throw new IllegalThreadStateException();
    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 */
        }
    }
}

在start方法里是调用了一个start0的native方法,这个方法其实是调用的run方法。

@Override
public void run() {
    if (target != null) {
        target.run();
    }
}

这样的设计是体现的设计模式中的一个简单模板方法,在调用start方法的同时其实是调用你重写的run方法,其他的start0和一些线程状态的判断都在start方法中作为不变的部分,而变的run方法则供使用者去实现run方法。

Runnable接口相关
runnable接口的作用

Runnable接口创建线程,这种创建线程的方式可以将run方法的逻辑从new thread的控制中抽离出来,这里如果耦合在new Thread中,没有体现业务可执行逻辑和线程控制分离开,体现了面向对象的思想。其实这种方式体现的是一种策略模式,Runnable接口就像策略模式中的策略接口,实现Runnable接口的run方法的类都是一个线程运行的策略,同时Runnable设计成了函数式接口,也更加灵活。

Thread API相关

new Thread构造函数

Thread构造函数有多个参数的重载,这里去记录下相关的构造函数,并且每个构造函数中参数的含义。

new Thread()

创建线程对象Thread,默认有一个线程名,以Thread-开头,从0开始数。

new Thread(Runnable target)

如果在构造Thread的时候没有传递Runnable或者没有复写Thread的run方法,该Thread将不会调用任何东西,如果传递了Runnable接口的实例,或者复写了Thread的run方法,则会执行该方法的逻辑单元(逻辑代码)。

new Thread(ThreadGroup g)

在创建线程的时候,如果构造函数中未传入threadGroup,则thread会默认获取父线程的threadGroup作为该线程的threadGroup。此时子线程和父线程将会在同一个ThreadGropu中。这时候可以通过threadGroup提供的api去获取一些信息。

比如threadGroup.getActiveCount 获取活跃线程数。

比如threadGroup.enumerate(new Thread[]) 是将所有活跃线程枚举到一个数组中。

new Thread(long stackSize)

stackSize是平常用的比较少的,这里要清楚jvm的一个结构:(这里简单用图去展示了jmm和虚拟机栈中栈帧结构)。jmm的结构:

关于变量的存储,这里可以看一下下面代码中的注释:

public class NewThreadWithStackSize {

    /**
     * 注意这里不是局部变量,所以这里的int i是放入方法区的
     */
    private int i = 0;

    /**
     * bytes这个变量存放在方法区中,里面存放的是对象在堆内存中的地址
     */
    private byte[] bytes = new byte[1024];

    private static int count = 0;

    public static void main(String[] args) {

        // main函数也是一个线程
        // create a thread by jvm named 'main' and 创建一个虚拟机栈

        // 局部变量 放入局部变量表中
        int j = 0;
        // arr在局部变量表中,对象还是放入堆内存中
        int[] arr = new int[1024];
    }
}

栈帧的结构:

这里可以做一个栈溢出的的例子,来为一个线程分配固定栈空间的大小,看看大概操作栈的次数(这里只是一个大概的数值)

public class NewThreadWithStackSize2 {

    private static int count = 0;

    public static void main(String[] args) {

        /**
         * 指定stackSize的new Thread构造函数
         */
        Thread thread = new Thread(null, new Runnable() {
            @Override
            public void run() {
                try {
                    add(1);
                } catch (Error r) {
                    r.printStackTrace();
                    System.out.println(count);
                }
            }

            private void add(int i) {
                count ++;
                add(i+1);
            }
        }, "test-stackSize", 1<<23); // 1 右移23位 大概是8mb 给当前线程thread分配了8m的stackSize 进行递归栈溢出

        thread.start();
    }
}

可以看到count的数值是:

我们可以知道:构造Thread的时候传入stacksize表示该线程占用的stack大小,如果没有指定stacksize的大小,默认是0,0代表着会忽略该参数,该参数会被JNI函数去调用。需要注意:该参数有一些平台有效,有些平台则无效。可以通过jvm参数 -Xss10m 来设置stacksize的大小.

github

以上代码都可以在github中找到:
https://github.com/zhanglijun1217/juc/tree/master/src/lesson/wwj/juc

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值