Java 基础篇之 Thread

栏目: IT技术 · 发布时间: 4年前

内容简介:下面我们来学习一下如何创建线程。注意是调用 start 方法,而不是直接调用 run 方法。

Java 基础篇之 Thread

  • New:线程刚刚创建,还未加入线程调度
  • Runnable:就绪态,调用 start() 后,线程加入线程调度。此时,只要获取到 CPU 时间片,就会进入运行态
  • Running:运行态,线程获取到 CPU 时间片后,就会被 CPU 运行。可以通过 yield() 主动让出时间片,会使得线程返回就绪态
  • Blocked:阻塞态,此时线程需要等待释放信号才能进入就绪态,如等待用户输入、等待锁被解除
  • Dead:线程结束或抛出未捕获异常

下面我们来学习一下如何创建线程。

1. 创建线程的两种方法

extends Thread
implements Runnable

1.1 继承 Thread 类

public class MyThread extends thread {
    @Override
    public void run() {
        // ...
    }
}
MyThread thread = new MyThread();
thread.start();

注意是调用 start 方法,而不是直接调用 run 方法。

调用 start 方法会进入 runnable 就绪态。当分配到 CPU 时间片时,run() 方法就会执行,从而进入 Running 态。

1.2 实现 Runnable 接口

public class MyThread implements Runnable {
    @Override
    public void run() {
        System.out.println("hello...");
    }
}
Thread thread = new Thread(new MyThread());
thread.start();

Lambda 写法:

Thread thread = new Thread(() -> {
    System.out.println("你好");
});
thread.start();

2. 线程内部概念

2.1 常用 API

// 获取当前线程
Thread thread = Thread.currentThread();
// 获取线程 ID
long id = thread.getId();
// 获取线程 Name 
String name = thread.getName();
// 获取线程优先级
int priority = thread.getPriority();
// 判定线程是否为守护线程
boolean isDaemon = thread.isDaemon();
// 判定线程是否被中断;被中断会进入阻塞 Blocked 状态
boolean isInterrupted = thread.isInterrupted();
// 判定线程是否存活
boolean isAlive = thread.isAlive();

线程优先级

Thread 中定义了三个优先级常量:

public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;

所以线程一共有 10 个优先级,最低为 1,最高为 10,默认为 5

2.2 守护线程

private boolean    daemon = false;

public final boolean isDaemon() {
        return daemon;
}

public final void setDaemon(boolean on) {
    checkAccess();
    if (isAlive()) {    
        throw new IllegalThreadStateException();
    }
    daemon = on;
}

守护线程的含义是:该线程是一个后台线程,依赖于其他线程,当其他线程都结束之后,该线程也会自动结束。当正在运行的都是守护线程的时候,Java 虚拟机会关闭。

example:

public static void main(String[] args) {
    Thread front = new Thread(() -> {
       System.out.println("前台线程开始运行...");
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("前台线程运行结束");
    });

    Thread back = new Thread(() -> {
        while (true) {
            System.out.println("后台线程开始运行,当前台线程 Dead,我就自杀!");

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

    front.start();
    back.setDaemon(true);
    back.start();
}

result:

前台线程开始运行...
后台线程开始运行,当前台线程 Dead,我就自杀!
后台线程开始运行,当前台线程 Dead,我就自杀!
后台线程开始运行,当前台线程 Dead,我就自杀!
前台线程运行结束

2.3 线程状态转换、线程交互

在上面的状态图里,我们已经看到了部分线程状态转换、线程交互的方法。这里详细总结一下这部分的知识。

2.3.1 sleep

Thread 提供了 sleep 静态方法,可以使线程进入阻塞态指定时间,单位是毫秒。超时后,线程自动进入就绪态,等待再次分配 CPU 时间片进入运行态。

源码:

public static native void sleep(long millis) throws InterruptedException;

Example:

Thread.sleep(1000); // 睡眠 1s

这个方法非常常见,是 Thread 中使用最多的 API 之一。

2.3.2 yield

Thread 提供了 yield 静态方法,可以使当前线程主动让出时间片,进入就绪态,等待再次分配时间片。

public static native void yield();

2.3.3 join

等待该线程死亡,即在 B 线程中调用了 A.join(),那么 B 线程将在此处等待 A 线程死亡才会继续往下执行。

public final void join() throws InterruptedException {
    join(0);
}
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()) {
                wait(0);
        }
    } else {
           while (isAlive()) {
                long delay = millis - now;
            if (delay <= 0) {
                    break;
            }
            wait(delay);
            now = System.currentTimeMillis() - base;
        }
    }
}

注意这里调用了 Object 类的 wait 方法。

Example:

public static void main(String[] args) {
        Thread front = new Thread(() -> {
           System.out.println("开始下载文件...");
           for (int i = 0; i < 100; i++) {
               System.out.println("下载进度" + i + "%");
               try {
                   Thread.sleep(50);
               } catch (InterruptedException e) {
                   e.printStackTrace();
               }
           }
            System.out.println("下载结束,是否打开文件?");
        });

        Thread back = new Thread(() -> {
            System.out.println("等待文件下载完毕...");
            try {
                front.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("是,文件打开如下...");
        });

        front.start();
        back.start();
    }

Result:

开始下载文件...
等待文件下载完毕...
下载进度0%
下载进度1%
// ...
下载进度99%
下载结束,是否打开文件?
是,文件打开如下...

2.3.4 Object 的 wait 和 notify

在 Thread 的 join 方法中使用了 wait 方法,wait 和 notify 是 Object 提供的线程之间协调工作的方法。

public final void wait() throws InterruptedException {
     wait(0);
}

// timeout 表示最长等待多长时间,如果 = 0,则表示一直等待,直到 notify 被触发!
public final native void wait(long timeout) throws InterruptedException;

public final native void notify();

public final native void notifyAll();

使用某个对象的 wait 方法,使得当前线程进入阻塞态,直到该对象的 notify 方法被调用,该线程进入就绪态。如果该对象的 wait 方法被多个线程调用,那么 notify 方法会随机释放一个线程。如果想要释放所有线程,使用 notifyAll 方法。

注意,使用 wait 和 notify 方法时,必须对该对象加锁。

Example:

public static void main(String[] args) {

        Integer obj = 1;
        Thread front = new Thread(() -> {
            System.out.println("线程1开始下载文件...");
            for (int i = 1; i <= 10; i++) {
                System.out.println("下载进度" + (i*10) + "%");
                try {
                    Thread.sleep(50);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

            System.out.println("下载结束,通知线程2打开文件");
            synchronized (obj) {
                obj.notify();
            }
        });

        Thread back = new Thread(() -> {
            System.out.println("线程2启动,等待线程1通知文件下载完成...");
            synchronized (obj) {
                try {
                    obj.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("接受到线程1通知,打开文件");
        });

        front.start();
        back.start();
    }

Result:

线程1开始下载文件...
线程2启动,等待线程1通知文件下载完成...
下载进度10%
下载进度20%
下载进度30%
下载进度40%
下载进度50%
下载进度60%
下载进度70%
下载进度80%
下载进度90%
下载进度100%
下载结束,通知线程2打开文件
接受到线程1通知,打开文件

3. 多线程并发问题

在多线程环境下,经常会出现并发问题,主要体现在抢占临界资源问题上。

比如同时调用某个对象。

解决办法通常是使用支持并发的对象(即对象内的资源被加锁了),比如 ConcurrentHashMap,或者直接对调用该对象的代码块加锁。

最简单的加锁方式就是 synchronized,如:

synchronized (user) {
    user.setName("hello");
}

4. 线程池

创建和销毁线程通常会消耗一定的资源,如果在线程非常多的情况下,这种资源消耗就不可小视了。这时候我们引入了线程池的概念——通俗理解,管理线程的容器。

这里用一个简单的例子演示一下线程池的使用:

  • 创建 ExecutorService 和提交 Runnable
ExecutorService pool = Executors.newFixedThreadPool(3);

        for (int i = 0; i < 10; i++) {
            Runnable runnable = () -> {
                Thread currentThread = Thread.currentThread();
                System.out.println(currentThread.getName() + " 执行任务");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }

                System.out.println(currentThread.getName() + " 完毕");
            };
            pool.execute(runnable);
        }

        pool.shutdown();

以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

Numerical Recipes 3rd Edition

Numerical Recipes 3rd Edition

William H. Press、Saul A. Teukolsky、William T. Vetterling、Brian P. Flannery / Cambridge University Press / 2007-9-6 / GBP 64.99

Do you want easy access to the latest methods in scientific computing? This greatly expanded third edition of Numerical Recipes has it, with wider coverage than ever before, many new, expanded and upd......一起来看看 《Numerical Recipes 3rd Edition》 这本书的介绍吧!

在线进制转换器
在线进制转换器

各进制数互转换器

html转js在线工具
html转js在线工具

html转js在线工具