JAVA线程创建的基本方式

线程创建的基础方式

线程创建主要分为两大类,一类是实现Runnable、Callable或继承Thread类后直接创建,另一种是通过线程池创建

继承Thread类创建线程

Thread类实际上是实现了Runnable类并实现了run()方法,我们在继承Thread类后,也需要重写run()方法,在run()方法中实现我们的业务逻辑,并调用子类的start()方法,开启一个线程并执行业务;

// 继承Thread类,重写run()方法
public class ThreadDemo extends Thread {
    @Override
    public void run() {
        while (true) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + ": " + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        
        // 1.实例化子类
        ThreadDemo threadDemo = new ThreadDemo();
        
        // 2.启动线程
        threadDemo.start();
        
    }
}

//我们可以看到运行结果,每1s打印一次
Thread[Thread-0,5,main]: 1597114115408
Thread[Thread-0,5,main]: 1597114116408
Thread[Thread-0,5,main]: 1597114117409
Thread[Thread-0,5,main]: 1597114118409

这里有一个问题:我们为什么不直接调用run方法,而是调用了start()方法呢?

当我们new Thread()时,就会新建一个线程,但是并没有启动,若此时直接调用run()方法,便会被当成当前线程的一个普通方法执行,并没有新开启一个线程,而start()方法则会启动一个新的线程并进入就绪态,一旦得到分配的时间片后便执行run()方法中的业务代码了。

实现Runnable创建线程

在继承Thread类创建线程的方法中,我们提到过Thread类实际上也是实现了Runnable接口,然后在创建并启动线程后执行了run()方法。

我们来看下Runnable接口的代码

public interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

发现Runnable接口只定义了一个run()方法,因此我们在实现Runnable接口后,还需要进一步的操作才能创建一个线程,请看代码

//实现Runnable接口
public class RunnableDemo implements Runnable {
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread() + ": " + System.currentTimeMillis());
        }
    }

    public static void main(String[] args) {
        
        // 1.实例化实现Runnable接口的类
        RunnableDemo runnableDemo = new RunnableDemo();
        
        // 2.新建一个线程,并将实例化的runnableDemo作为参数传入thread
        Thread thread = new Thread(runnableDemo);
        
        // 3.调用thread类的start()方法启动线程,执行RunnableDemo类中实现的run()方法
        thread.start();
    }
}

//执行结果
Thread[Thread-0,5,main]: 1597115827464
Thread[Thread-0,5,main]: 1597115828465
Thread[Thread-0,5,main]: 1597115829465
Thread[Thread-0,5,main]: 1597115830465

从上面代码我们可以看出,把在new Thread()时把实现了Runnable接口的RunnableDemo实例作为参数传入了Thread的构造方法,再调用Thread的start()方法,进而执行了RunnableDemo中重写的run()方法,完成了一次线程从创建、启动、执行的过程

了解Thread类的构造方法

在上文中,我们了解到,实现了Runnable接口的类的示例是可以作为参数传入Thread类的构造方法的,接下来看一下Thread类的public构造方法

// 1
public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), 0);
}

// 2
public Thread(Runnable target) {
    init(null, target, "Thread-" + nextThreadNum(), 0);
}

// 3
public Thread(ThreadGroup group, Runnable target) {
    init(group, target, "Thread-" + nextThreadNum(), 0);
}

// 4
public Thread(String name) {
    init(null, null, name, 0);
}

// 5
public Thread(ThreadGroup group, String name) {
    init(group, null, name, 0);
}

// 6
public Thread(Runnable target, String name) {
    init(null, target, name, 0);
}

// 7
public Thread(ThreadGroup group, Runnable target, String name) {
    init(group, target, name, 0);
}

// 8
public Thread(ThreadGroup group, Runnable target, String name,
              long stackSize) {
    init(group, target, name, stackSize);
}

从所有的构造方法我们可以看到,最终都是调用了init()方法

private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
    //调用了重载的init()方法
    init(g, target, name, stackSize, null, true);
}

//重载的init()方法
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();
        if (g == null) {
            /* Determine if it's an applet or not */

            /* If there is a security manager, ask the security manager
               what to do. */
            if (security != null) {
                g = security.getThreadGroup();
            }

            /* If the security doesn't have a strong opinion of the matter
               use the parent thread group. */
            if (g == null) {
                g = parent.getThreadGroup();
            }
        }

        /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
        g.checkAccess();

        /*
         * Do we have the required permissions?
         */
        if (security != null) {
            if (isCCLOverridden(getClass())) {
                security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
            }
        }

        g.addUnstarted();

        this.group = g;
        this.daemon = parent.isDaemon();
        this.priority = parent.getPriority();
        if (security == null || isCCLOverridden(parent.getClass()))
            this.contextClassLoader = parent.getContextClassLoader();
        else
            this.contextClassLoader = parent.contextClassLoader;
        this.inheritedAccessControlContext =
                acc != null ? acc : AccessController.getContext();
        this.target = target;
        setPriority(priority);
        if (inheritThreadLocals && parent.inheritableThreadLocals != null)
            this.inheritableThreadLocals =
                ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
}

现在让我们来依次看一下几常用参数的意义

  • ThreadGroup g

    线程组,指定创建后的线程属于哪个线程组,init()方法中可以看到,如果不指定g,则默认归属于父线程的线程组

    if (g == null) {
        /* Determine if it's an applet or not */
        /* If there is a security manager, ask the security manager
        what to do. */
        if (security != null) {
        	g = security.getThreadGroup();
        }
    
        /* If the security doesn't have a strong opinion of the matter
        use the parent thread group. */
        if (g == null) {
        	g = parent.getThreadGroup();
        }
    }
    
  • Target target

    实现了Runnable接口或继承了Thread的类的实例对象,在Thread类中的run()方法可以看到,若target为null,则没有任何执行结果

    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    
  • String name,线程名称,以一个不传该参数的重载方法来看,默认是Thread-线程号

    public Thread(Runnable target) {
        init(null, target, "Thread-" + nextThreadNum(), 0);
    }
    
  • long stackSize

    /*
    * The requested stack size for this thread, or 0 if the creator did
    * not specify a stack size.  It is up to the VM to do whatever it
    * likes with this number; some VMs will ignore it.
    */
    private long stackSize;
    

    新开线程是需要栈空间的,这里的stackSize则是指定新开的栈空间大小,而这个参数默认是0的,注意最后一句:It is up to the VM to do whatever it likes with this number; some VMs will ignore it。也就是说这个主要是依据jvm的设定,即便给定值了,在过高或过低甚至有的jvm直接就会忽略掉这个参数,所以不建议动这个参数的值。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值