转载

Java 基础篇之 Thread

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();
原文  http://www.eknown.cn/index.php/java/48.html
正文到此结束
Loading...