This is the multi-page printable view of this section. Click here to print.

Return to the regular view of this page.

Thread Objects

内容来源:

1 - 多线程入门

并发编程入口

为什么要实现多线程?

线程与进程

进程使得操作系统可以并发的执行任务 但是在一段时间内单个进程只能执行一个任务 进程内的子任务只能逐个按顺序执行, 效率还有提升空间

因此提出线程概念, 使得一个线程可以执行一个子任务使得进程内部也可实现并发, 提高效率

多线程的优势

  • 进程间的通信比较复杂,而线程间的通信比较简单,通常情况下,我们需要使用共享资源,这些资源在线程间的通信很容易。

  • 进程是重量级的,而线程是轻量级的,多线程方式的系统开销更小。

Java中创建线程的三种方式

继承 Thread

重写 run 方法

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println(getName() + ":打了" + i + "个小兵");
        }
    }
}

主函数启动线程

//创建MyThread对象
MyThread t1=new MyThread();
MyThread t2=new MyThread();
MyThread t3=new MyThread();
//设置线程的名字
t1.setName("鲁班");
t2.setName("刘备");
t3.setName("亚瑟");
//启动线程
t1.start();
t2.start();
t3.start();

实现 Runnable 接口

创建任务类实现 Runnable 接口

public class MyRunnable implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            try {//sleep会发生异常要显式处理
                Thread.sleep(20);//暂停20毫秒
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "打了:" + i + "个小兵");
        }
    }
}

主函数启动线程

//创建MyRunnable类
MyRunnable mr = new MyRunnable();
//创建Thread类的有参构造,并设置线程名
Thread t1 = new Thread(mr, "张飞");
Thread t2 = new Thread(mr, "貂蝉");
Thread t3 = new Thread(mr, "吕布");
//启动线程
t1.start();
t2.start();
t3.start();

实现 Callable<> 接口

相较于 Runnable 接口 callable<> 可以获取线程的执行结果, 使用 FutureTask 类存储 了解 [[获取线程执行结果]]

重写 call<> 方法

public class CallerTask implements Callable<String> {
   public String call() throws Exception {
       return "Hello,i am running!";
   }
}

主函数启动线程

        //创建异步任务
        FutureTask<String> task=new FutureTask<String>(new CallerTask());
        //启动线程
        new Thread(task).start();
        try {
            //等待执行完成,并获取返回结果
            String result=task.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

2 - 线程

线程,是进程的子任务,是 CPU 调度和分派的基本单位,实现了进程内部的并发。

进程 说简单点就是我们在电脑上启动的一个个应用。它是操作系统分配资源的最小单位。

线程 是进程的子任务,是 CPU 调度和分派的基本单位,实现了进程内部的并发。

线程与进程

线程,是进程的子任务,是进程中的独立执行单元。多个线程可以共享同一个进程的资源,如内存;每个线程都有自己独立的栈和寄存器。

image.png

并发环境下, 多线程存在下述问题

  1. 线程之间如何确保执行顺序,实现线程之间的协调与合作? 线程通信
  2. 线程之间如何确保对临界资源修改不会冲突? 线程同步?

线程通信

Inter-thread communication in Java is a mechanism in which a thread is paused running in its critical section and another thread is allowed to enter (or lock) in the same critical section to be executed.

线程间通信 是一种机制,使得线程能够在执行时进行协作,通常用于解决某些线程需要等待其他线程完成任务的情况。它主要涉及线程之间的等待和通知机制,而不是同一临界区内的互斥访问。

线程同步

Synchronization is crucial for ensuring that multiple threads operate safely on shared resources. Without *Synchronization, data inconsistency or corruption can occur when multiple threads try to access and modify shared variables simultaneously. In Java, it is a mechanism that ensures that only one thread can access a resource at any given time

线程同步的重点是 保证线程安全,特别是在多个线程访问共享资源时。同步机制通过确保某个共享资源在同一时刻只能被一个线程访问来防止数据不一致或冲突。Java 中的 synchronized 关键字和 ReentrantLock 都是同步机制的实现,确保线程在访问共享资源时不会发生并发冲突。

解决上述问题的方式有两种

  1. 共享内存
  2. 消息传递

image.png

Java 使用共享内存的并发模型实现线程同步与通信, 这个模型称之为 JMM JMM 决定了一个线程对共享变量的写入何时对另外一个线程可见。

引用: Geekfork

3 - 线程生命周期

操作系统中的线程状态转换

Java的线程分为两类, 用户线程和守护线程

线程调度就是线程不同状态间的转换

操作系统中,线程被视为轻量级的进程,所以线程状态其实和进程状态是一致的

image.png

Java线程有如下状态

// Thread.State 源码
public enum State {
    NEW,
    RUNNABLE,
    BLOCKED,
    WAITING,
    TIMED_WAITING,
    TERMINATED;
}

NEW

处于 NEW 状态的线程是刚创建 Thread 而尚未启动。这里的尚未启动指的是还没调用 Thread 实例的start()方法。

private void testStateNew() {
    Thread thread = new Thread(() -> {});
    System.out.println(thread.getState()); // 输出 NEW
}

RUNNABLE

线程启动之后的状态

call thread.start() will Causes this thread to begin execution; the Java Virtual Machine calls the run method of this thread.

The result is that two threads are running concurrently: the current thread (which returns from the call to the start method) and the other thread (which executes its run method).

当前线程正在运行中。处于 RUNNABLE 状态的线程在 Java 虚拟机中运行

It is never legal to start a thread more than once. In particular, a thread may not be restarted once it has completed execution.

when recall a RUNNABLE thread, an IllegalThreadStateException will throwed if the thread was already started.

Java 线程的RUNNABLE状态其实包括了操作系统线程的readyrunning两个状态。

BLOCK

阻塞状态。处于 BLOCKED 状态的线程正等待锁(锁会在后面细讲)的释放以进入同步区。

WAITING

等待状态。处于等待状态的线程变成 RUNNABLE 状态需要其他线程唤醒。

  • Object.wait():使当前线程处于等待状态直到另一个线程唤醒它
  • Thread.join():等待线程执行完毕,底层调用的是 Object 的 wait 方法,等待期间可以通过Object.notify()/notifyAll()/Locksupport.unpark()唤醒
  • LockSupport.park():除非获得调用许可,否则禁用当前线程进行线程调度

TIMED_WAITING

超时等待状态。线程等待一个具体的时间,时间到后会被自动唤醒。

调用如下方法会使线程进入超时等待状态:

  • Thread.sleep(long millis):使当前线程睡眠指定时间

  • Object.wait(long timeout):线程休眠指定时间,等待期间可以通过Object.notify()/notifyAll()/Locksupport.unpark()唤醒

  • Thread.join(long millis):等待当前线程最多执行 millis 毫秒,如果 millis 为 0,则会一直执行

  • LockSupport.parkNanos(long nanos): 除非获得调用许可,否则禁用当前线程进行线程调度指定时间

  • LockSupport.parkUntil(long deadline):同上,也是禁止线程进行调度指定时间;

TERMINATED

终止状态

image.png

调度方法

image.png

等待与通知
  1. Object.wait() 调用wait()方法前线程必须持有对象的锁。

线程调用wait()方法时,会释放当前的锁,直到有其他线程调用notify()/notifyAll()方法唤醒等待锁的线程。

需要注意的是,其他线程调用notify()方法只会唤醒单个等待锁的线程,如有有多个线程都在等待这个锁的话不一定会唤醒到之前调用wait()方法的线程。

同样,调用notifyAll()方法唤醒所有等待锁的线程之后,也不一定会马上把时间片分给刚才放弃锁的那个线程,具体要看系统的调度。

  1. Object.wait(long)/Object.wait(long, int)

wait(long)方法使线程进入 TIMED_WAITING 状态。这里的wait(long)方法与无参方法 wait()相同的地方是,都可以通过其他线程调用notify()notifyAll()方法来唤醒。

不同的地方是,有参方法wait(long)就算其他线程不来唤醒它,经过指定时间 long 之后它会自动唤醒,拥有去争夺锁的资格。

  1. Thread.join()

调用join()方法,会一直等待这个线程执行完毕(转换为 TERMINATED 状态)。

休眠
  1. Thread.sleep(long)

使当前线程睡眠指定时间。需要注意这里的“睡眠”只是暂时使线程停止执行,并不会释放锁。时间到后,线程会重新进入 RUNNABLE 状态。

让出执行权

yield():Thread 类中的静态方法,当一个线程调用 yield 方法时,实际是在暗示线程调度器,当前线程请求让出自己的 CPU,但是线程调度器可能会“装看不见”忽略这个暗示。

中断

中断(Interrupt)一个线程意味着在该线程完成任务之前停止其正在进行的一切,有效地中止其当前的操作。线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序。

那么,我们究竟应该如何停止线程呢?

  • 1、任务中一般都会有循环结构,只要用一个标记控制住循环,就可以结束任务。
  • 2、如果线程处于了冻结状态,无法读取标记,此时可以使用interrupt()方法将线程从冻结状态强制恢复到运行状态中来,让线程具备CPU的执行资格。

Thread.interrupt(long)

作用是中断阻塞的线程。将会设置线程中断为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。 线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。 该方法只是改变中断状态,不会中断一个正在运行的线程。

因为该方法并不执行中断, 只是指示线程中断因此用户需要监视线程的执行结果来判断下一步操作 比如抛出interruptedException的方法, 监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常, 从而指示阻塞线程退出阻塞

引用: 博客园