二、线程创建与运行

目录

线程创建

继承Thread类

实现Runnable

两种方式的分析

线程池方式

线程运行

start()方法

join()方法

getState()方法

sleep()方法

yield()方法

interrupt()方法

两阶段终止模式

LockSupport.park()方法

setDaemo()方法


线程创建

在java中创建线程的方式有以下几种:

继承Thread类

在java中通过继承Thread类,或者直接new一个Thread类来创建一个线程

class MyThread extends Thread {
    public void run(){
       // 复写run方法
    }
}
MyThread t = new MyThread();
t.start(); //启动线程

实现Runnable

通过实现一个Runnalbe接口,将此接口传递给Thread类,来创建一个线程

class MyRunnable implements Runnable {
    public void run(){
       // 实现run方法
    }
}
Thread t = new Thread(new MyRunnable());
t.start(); //启动线程
 

两种方式的分析

这里需要分析一下,为什么上述两种请求都可以实现呢。我们看一下Thread的run方法

    /**
     * If this thread was constructed using a separate
     * {@code Runnable} run object, then that
     * {@code Runnable} object's {@code run} method is called;
     * otherwise, this method does nothing and returns.
     * <p>
     * Subclasses of {@code Thread} should override this method.
     *
     * @see     #start()
     * @see     #stop()
     * @see     #Thread(ThreadGroup, Runnable, String)
     */
    @Override
    public void run() {
        if (target != null) {
            target.run();
        }
    }

线程真正执行的逻辑就是run方法的逻辑。

在方式1中,我们直接复写run方法,那么当线程运行的时候自然会直接执行我们复写的run方法,这个很好理解

在方式2中,我们通过Thread的构造方法传递一个Runnable进来,而Thread的构造方法如下:
 

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

可以看到Tread的构造函数中除了命名了线程名,还赋值了target。这个target就是run()方法中调用的target对象,也就是我们传递的Runnable对象。

线程池方式

其实本质上线程池的方式也是利用上述两种方式来创建具体的对象。具体实现如下:

ExecutorService executor = Executors.newFixedThreadPool(10);
executor.execute(new MyRunnable());
executor.submit(new MyCallable());

继续跟踪execute或者submit。我们那TreadPoolExecutor来看源码:

//向线程池中添加线程
private boolean addWorker(Runnable firstTask, boolean core) {
    //
    w = new Worker(firstTask);//其中firstTask就是runnable。
    //
}



//worker就是当前线程池中的某个线程包装对象
Worker(Runnable firstTask) {
     setState(-1); // inhibit interrupts until runWorker
     this.firstTask = firstTask;
     this.thread = getThreadFactory().newThread(this); 
    //这里可以看到线程池创建线程的方式就是newThread(this)。
    //而this就是当前Worker本身。而work对象实现了Runnable接口
}

线程运行

start()方法

启动一个新的线程,底层代码是调用native的start0()。 调用此方法的时候,当前线程将进入到running状态。(ready和run都是running状态)。

join()方法

当前执行线程等到目标线程执行结束。例如有两个线程A和B。 在A线程中执行B.join()。则当前A线程将会阻塞,直到B线程执行结束。

public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) { //判断target线程是否存活,
                wait(0);//等待
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay); //超时等待
                now = System.currentTimeMillis() - base;
            }
        }
    }

上述代码是B.join()的源码。我们可以看到,首先执行isAlive(),判断当前线程是否存活。isAlive()是一个本地方法。其源码注释为“

Tests if this thread is alive. A thread is alive if it has been started and has not yet died.
Returns:
true if this thread is alive; false otherwise.

如果此线程存活,则进行wait或者timewait。而wait本身也是一个native方法,具体注释为:

Causes the current thread to wait until it is awakened, 
typically by being notified or interrupted, or until a certain amount of real time has elapsed.
In all respects, this method behaves as if wait(timeoutMillis, 0) had been called. See the specification of the wait(long, int) method for details.
Params:
timeoutMillis – the maximum time to wait, in milliseconds

会造成当前线程一直等待,直到被唤醒。

getState()方法

获取当前的线程状态。java线程状态的流转如下图所示:

1、new状态:当Thread刚刚被new出来的时候,当前线程的状态为new

2、runnable状态:当Thread调用start()方法的时候,当前线程为runnable状态。但是此时线程不一定被cpu调度。里面包含了两个子状态:running和ready。当被cpu调度的时候,现成从ready变为running,当线程执行yield()方法时,状态从running变成ready

3、block状态:当线程执行synchronized代码块的时候,由于为获取到锁,将会被block到monitor的阻塞队列中

4、waiting状态:当线程执行sleep、wait、park将会进入此状态,只有当对应的对象notify之后,才能恢复到ready状态

5、time-waiting状态:超时等待

sleep()方法

该用于暂停当前正在执行的线程,让出CPU给其他线程。这个方法接收一个参数,表示暂停的时间,单位是毫秒。例如,Thread.sleep(1000)会暂停当前线程1秒。

这个方法会抛出InterruptedException异常,如果其他线程在这个线程睡眠的时候中断了这个线程,那么这个线程会立即退出睡眠状态,然后抛出这个异常。  

需要注意的是,sleep方法并不会释放对象锁,如果当前线程持有某个对象的锁,那么即使这个线程调用了sleep方法,其他线程也无法获取这个对象的锁。  另外,sleep方法的精度并不是非常准确,实际的暂停时间可能会比指定的时间稍微长一些

yield()方法

当前线程让步,让出cpu的占用。但是,与sleep()方法不同的是,yield()方法并不会导致线程进入阻塞状态,而是使线程回到可运行状态,所以如果没有其他线程或者所有的线程都处于阻塞状态,那么有可能会立即重新运行yield()方法的线程。  yield()方法的调用不会导致线程的状态发生转变,也就是说,线程在调用yield()方法前后的状态都是Runnable的。  需要注意的是,yield()方法的调用并不能确保当前线程一定会立即停止执行,这取决于线程调度器的具体实现和状态。另外,和sleep()方法一样,yield()方法也不会释放已经持有的锁

interrupt()方法

向指定线程发送打断信号。例如执行A.interrupt(),意味着向A线程发送一个打断信号,如果A线程并未对该信号做任何处理,则A线程不会受到影响。只有在A线程执行逻辑中判断当前是否收到了打断信号,thread.isInterrupt(),然后进行处理。

两阶段终止模式

1、禁止使用Thread.stop()。 此方法会直接kill掉线程,导致线程的锁得不到释放。

2、使用flag标记。

3、使用打断标记。

LockSupport.park()方法

LockSupport类是Java并发编程中的一个工具类,它提供了一种基于许可的阻塞和解除阻塞的方式。

LockSupport.park()方法可以阻塞当前线程,主要通过消耗许可来判断是否应该阻塞

LockSupport.unpark(Thread)方法用来发放许可。

LockSupport的park()和unpark()方法的调用顺序并不固定,可以先调用park()再调用unpark(),也可以先调用unpark()再调用park()。

每个线程最多只有一个许可,如果线程已经持有许可,再次调用unpark()方法不会累积许可。相反,如果线程没有持有许可,调用park()方法会使线程阻塞,直到线程获得许可。  这种机制使得unpark()方法可以在park()方法之前调用,也就是说,如果一个线程在调用park()方法之前已经持有了许可,那么调用park()方法时,它会立即返回,而不会阻塞。

这就是所谓的"先行发放许可",可以避免因为线程执行顺序的不确定性导致的死锁。

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("Thread is running...");
                LockSupport.park(); //阻塞当前线程
                System.out.println("Thread is resumed...");
            }
        });

        thread.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("Main thread is running...");
        LockSupport.unpark(thread); //唤醒指定线程
    }
}

上述代码,如果main线程先执行,那么将会为子线程发放许可,那么子线程在执行park的时候,将会直接返回。

setDaemo()方法

设置当前的线程为守护线程。

在java程序中,如果当前进程还存在任何一个线程存活,那么当前进程就不会结束。

那么这里存在一个问题:当jvm启动用户程序的时候,背后会启动gc线程,那么在用户线程执行结束的时候,这个gc线程并没有手动kill掉。按照上述说法,只要gc线程存活,那么当前java进程就不会结束,但实际上,当用户线程结束之后,java进程已经结束了。

原因是:gc线程为守护线程,守护线程会自动结束,而结束的条件是:当所普通线程结束之后,守护线程会自动结束。因此,当用户线程结束的时候,gc线程会自动结束。那么意味着java进程的所有线程都结束了,所以java进程会结束

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值