线程的状态

  • NEW 新建但尚未启动
  • RUNNABLE 就绪状态,表示线程可以运行。这个状态中的线程可能已经在运行,也可能在等待资源
  • BLOCKED 阻塞状态
  • WAITING 等待状态
  • TIMED-WAITING 计时等待
  • TERMINATED 线程已经结束

线程的工作模式是,首先先要创建线程并指定线程需要执行的业务方法,然后再调用线程的 start() 方法,此时线程就从 NEW(新建)状态变成了 RUNNABLE(就绪)状态,此时线程会判断要执行的方法中有没有 synchronized 同步代码块,如果有并且其他线程也在使用此锁,那么线程就会变为 BLOCKED(阻塞等待)状态,当其他线程使用完此锁之后,线程会继续执行剩余的方法。

线程状态

BLOCKED 和 WAITING 的区别

虽然 BLOCKED 和 WAITING 都有等待的含义,但二者有着本质的区别,首先它们状态形成的调用方法不同,其次 BLOCKED 可以理解为当前线程还处于活跃状态,只是在阻塞等待其他线程使用完某个锁资源;而 WAITING 则是因为自身调用了 Object.wait() 或着是 Thread.join() 又或者是 LockSupport.park() 而进入等待状态,比如当线程因为调用了 Object.wait() 而进入 WAITING 状态之后,则需要等待另一个线程执行 Object.notify() 或 Object.notifyAll() 才能被唤醒。

start() 和 run() 的区别

  • start() 方法属于 Thread 自身的方法,并且使用了 synchronized 来保证线程安全
  • run() 方法为 Runnable 的抽象方法,必须由调用类重写此方法,重写的 run() 方法其实就是此线程要执行的业务方法
  • 从执行的效果来说,start() 方法可以开启多线程,让线程从 NEW 状态转换成 RUNNABLE 状态,而 run() 方法只是一个普通的方法。
  • start() 方法不能被多次调用,否则会抛出 java.lang.IllegalStateException;而 run() 方法可以进行多次调用,因为它只是一个普通的方法而已。

线程优先级

// 线程可以拥有的最小优先级
public final static int MIN_PRIORITY = 1;

// 线程默认优先级
public final static int NORM_PRIORITY = 5;

// 线程可以拥有的最大优先级
public final static int MAX_PRIORITY = 10

设置优先级

在程序中我们可以通过 Thread.setPriority() 来设置优先级,setPriority() 源码如下:

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    // 先验证优先级的合理性
    if (newPriority > MAX_PRIORITY || newPriority < MIN_PRIORITY) {
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) != null) {
        // 优先级如果超过线程组的最高优先级,则把优先级设置为线程组的最高优先级
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

线程常用方法

join()

在一个线程中调用 other.join() ,这时候当前线程会让出执行权给 other 线程,直到 other 线程执行完或者过了超时时间之后再继续执行当前线程,join() 源码如下:

public final synchronized void join(long millis)
throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    // 超时时间不能小于 0
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    // 等于 0 表示无限等待,直到线程执行完为之
    if (millis == 0) {
        // 判断子线程 (其他线程) 为活跃线程,则一直等待
        while (isAlive()) {
            wait(0);
        }
    } else {
        // 循环判断
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

public class ThreadExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 1; i < 6; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程睡眠:" + i + "秒。");
            }
        });
        thread.start(); // 开启线程
        // 主线程执行
        for (int i = 1; i < 4; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程睡眠:" + i + "秒。");
        }
    }
}

执行结果

public class ThreadExample {
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(() -> {
            for (int i = 1; i < 6; i++) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("子线程睡眠:" + i + "秒。");
            }
        });
        thread.start(); // 开启线程
        thread.join(2000); // 等待子线程先执行 2 秒钟
        // 主线程执行
        for (int i = 1; i < 4; i++) {
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("主线程睡眠:" + i + "秒。");
        }
    }
}

执行结果

yield()

这是个native方法,yield() 方法表示给线程调度器一个当前线程愿意出让 CPU 使用权的暗示,但是线程调度器可能会忽略这个暗示。

示例

public static void main(String[] args) throws InterruptedException {
    Runnable runnable = new Runnable() {
        @Override
        public void run() {
            for (int i = 0; i < 10; i++) {
                System.out.println("线程:" +
                        Thread.currentThread().getName() + " I:" + i);
                if (i == 5) {
                    Thread.yield();
                }
            }
        }
    };
    Thread t1 = new Thread(runnable, "T1");
    Thread t2 = new Thread(runnable, "T2");
    t1.start();
    t2.start();
}

每次执行结果不一样!

为什么说只有一种实现线程的方法

实现Runable接口

public class RunnableThread implements Runnable {

    @Override
    public void run() {
        System.out.println("用实现Runnable接口实现线程");
    }
}

继承Thread类

public class ExtendsThread extends Thread {
     
    @Override
    public void run() {
        System.out.println(“用Thread类实现线程");
    }
}

线程池创建

static class DefaultThreadFactory implements ThreadFactory {
 
    DefaultThreadFactory() {
        SecurityManager s = System.getSecurityManager();
        group = (s != null) ? s.getThreadGroup() :
            Thread.currentThread().getThreadGroup();
        namePrefix = "pool-" +
            poolNumber.getAndIncrement() +
            "-thread-";
    }
 

    public Thread newThread(Runnable r) {
        Thread t = new Thread(group, r,
                    namePrefix + threadNumber.getAndIncrement(),
0);

        if (t.isDaemon())
            t.setDaemon(false);
        if (t.getPriority() != Thread.NORM_PRIORITY)
            t.setPriority(Thread.NORM_PRIORITY);
        return t;
    }
}

对于线程池本质是通过线程工厂创建线程的,默认使用DefaultThreadFactory,它会给线程设置一些默认值,最终还是通过new Thread创建。

有返回值的Callable

class CallableTask implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        return new Random().nextInt();
    }
}

//创建线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//提交任务,并用 Future提交返回结果
Future<Integer> future = service.submit(new CallableTask());

其他方式

定时器

class TimerThread extends Thread {
//具体实现
}

本质只有一种方式

首先启动线程需要调用start方法,而start最终还是会调用run方法。

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

其中target实际上就是一个Runnable,即使用Runnable接口实现线程时传给Thread类的对象。
因此不同点仅仅在于实现线程运行内容的不同----一是实现runnable接口,而是重写run方法。

实现Runnable接口比继承Thread类好

1、Runnable只要一个run方法,这种情况下,实现了Runnable和Thread类的解耦,Thread负责线程启动和属性设置。
2、继承Thrad类方式,每次执行一次任务,都需要一个独立的线程,执行完就销毁。还想继续必须新建一个继承Thread类的类,而runnable可以直接传给线程池,使用固定线程来完成任务。
3、Java不支持多继承。

如何正确停止线程

用interrupt

public class StopThread implements Runnable {
 
    @Override
    public void run() {
        int count = 0;
        while (!Thread.currentThread().isInterrupted() && count < 1000) {
            System.out.println("count = " + count++);
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        Thread thread = new Thread(new StopThread());
        thread.start();
        Thread.sleep(5);
        thread.interrupt();
    }
}

线程还没打完1000个数就会中断,这是通过interrupt正确停止线程的情况。

Sleep

Runnable runnable = () -> {
    int num = 0;
    try {
        while (!Thread.currentThread().isInterrupted() && 
        num <= 1000) {
            System.out.println(num);
            num++;
            Thread.sleep(1000000);
        }
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
};

这是线程任务过程中有休眠需求。

处于休眠中的线程被中断,是可以感受到中断信号的,它会抛出InterruptedException异常,同时清除中断标识位设置为false。这样就不会担心长时间休眠的线程感受不到中断了。

处理中断信息的方式

void subTas() {
    try {
        Thread.sleep(1000);
    } catch (InterruptedException e) {
        // 在这里不处理该异常是非常不好的
    }
}

方法内部调用了sleep方法或者wait等能响应中断的方法,仅仅catch是不合适的,可以在方法中使用try/catch或者方法签名中抛出异常。


如上所示,catch中是空的,一旦有其他线程发送interrupt试图中断线程,此时会抛出异常,并清除中断信号。抛出的异常被catch捕捉,但也同时把信息隐藏了。

void subTask2() throws InterruptedException {
    Thread.sleep(1000);
}

让调用方去捕捉信息,并处理异常。

除此之外还可以使用再次中断

private void reInterrupt() {
    try {
        Thread.sleep(2000);
    } catch (InterruptedException e) {
        Thread.currentThread().interrupt();
        e.printStackTrace();
    }
}

在catch语句中将休眠期中断的线程清除掉的中断标志位还原回去。

volatile标记位的停止方法是错的

stop()、suspend()、resume()已经被@Deprecated标记了,这是错误停止线程的方法。

  • stop会立即停止线程,没有预留时间给线程处理、保存停止前的数据
  • suspend不会释放锁就进入休眠,在resume之前不会释放

volatile修饰标记位适用的场景

public class VolatileCanStop implements Runnable {
 
    private volatile boolean canceled = false;
 
    @Override
    public void run() {
        int num = 0;
        try {
            while (!canceled && num <= 1000000) {
                if (num % 10 == 0) {
                    System.out.println(num + "是10的倍数。");
                }
                num++;
                Thread.sleep(1);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
 
    public static void main(String[] args) throws InterruptedException {
        VolatileCanStop r = new VolatileCanStop();
        Thread thread = new Thread(r);
        thread.start();
        Thread.sleep(3000);
        r.canceled = true;
    }
}

这是正确利用volatile让线程3秒之后停止。

volatile修饰标记位不适用的场景

以生产者消费者模式的案例来演示。
生产者,会输出50的倍数
消费者,共用一个仓库,并生产随机数判断是否需要继续添加数字

public static void main(String[] args) throws InterruptedException {
        ArrayBlockingQueue storage = new ArrayBlockingQueue(8);

        Producer producer = new Producer(storage);
        Thread producerThread = new Thread(producer);
        producerThread.start();
        Thread.sleep(500);

        Consumer consumer = new Consumer(storage);
        while (consumer.needMoreNums()) {
            System.out.println(consumer.storage.take() + "被消费了");
            Thread.sleep(100);
        }
        System.out.println("消费者不需要更多数据了。");

        //一旦消费不需要更多数据了,我们应该让生产者也停下来,但是实际情况却停不下来
        producer.canceled = true;
        System.out.println(producer.canceled);
    }
}

建立生产者线程生成数字,并且主线程睡眠500ms让生产者有足够时间生产数字,并由于队列满了进入阻塞状态。
虽然消费者将cancled置成true,但是生产者阻塞中,无法进入下一次循环,无法判断cancled值。
此时用interrupt来请求中断,执行take方法时,由take内部唤醒生产者线程。

Last modification:April 7th, 2020 at 01:02 am