Java并发线程基础知识

线程状态及转换

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rNNkcWAm-1653614687260)(C:\Users\zhangjie\AppData\Roaming\Typora\typora-user-images\1652775892400.png)]

新建(New)

创建后尚未启动

运行(Runnable)

可能正在运行,也可能正在等待 CPU 时间片。

包含了操作系统线程状态中的 Running 和 Ready。

阻塞(Blocked)

等待获取一个排它锁,如果其线程释放了锁就会结束此状态。

无限期等待(Waiting)

等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。

进入方法:

  • 没有设置 Timeout 参数的 Object.wait() 方法。
  • 没有设置 Timeout 参数的 Thread.join() 方法。

对应的退出方法:

  • Object.notify() / Object.notifyAll()。
  • 等待调用的线程执行完毕。
有限期等待(Timed Waiting)

无需等待其它线程显式地唤醒,在一定时间之后会被系统自动唤醒。

阻塞和等待的区别在于,阻塞是被动的,它是在等待获取一个排它锁。而等待是主动的,通过调用 Thread.sleep() 和 Object.wait() 等方法进入。

进入方法:

  • Thread.sleep() 方法。

  • 设置了 Timeout 参数的 Object.wait() 方法。

  • 设置了 Timeout 参数的 Thread.join() 方法。

对应的退出方法:

  • 时间结束。

  • 时间结束 / Object.notify() / Object.notifyAll()。

  • 时间结束 / 等待调用的线程执行完毕。

死亡(Terminated)

可以是线程结束任务之后自己结束,或者产生了异常而结束 。

Thread的state枚举类源码
/**
     * A thread state.  A thread can be in one of the following states:
     * <ul>
     * <li>{@link #NEW}<br>
     *     A thread that has not yet started is in this state.
     *     </li>
     * <li>{@link #RUNNABLE}<br>
     *     A thread executing in the Java virtual machine is in this state.
     *     </li>
     * <li>{@link #BLOCKED}<br>
     *     A thread that is blocked waiting for a monitor lock
     *     is in this state.
     *     </li>
     * <li>{@link #WAITING}<br>
     *     A thread that is waiting indefinitely for another thread to
     *     perform a particular action is in this state.
     *     </li>
     * <li>{@link #TIMED_WAITING}<br>
     *     A thread that is waiting for another thread to perform an action
     *     for up to a specified waiting time is in this state.
     *     </li>
     * <li>{@link #TERMINATED}<br>
     *     A thread that has exited is in this state.
     *     </li>
     * </ul>
     *
     * <p>
     * A thread can be in only one state at a given point in time.
     * These states are virtual machine states which do not reflect
     * any operating system thread states.
     *
     * @since   1.5
     * @see #getState
     */
    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  A thread in the runnable
         * state is executing in the Java virtual machine but it may
         * be waiting for other resources from the operating system
         * such as processor.
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         * A thread in the blocked state is waiting for a monitor lock
         * to enter a synchronized block/method or
         * reenter a synchronized block/method after calling
         * {@link Object#wait() Object.wait}.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         * A thread is in the waiting state due to calling one of the
         * following methods:
         * <ul>
         *   <li>{@link Object#wait() Object.wait} with no timeout</li>
         *   <li>{@link #join() Thread.join} with no timeout</li>
         *   <li>{@link LockSupport#park() LockSupport.park}</li>
         * </ul>
         *
         * <p>A thread in the waiting state is waiting for another thread to
         * perform a particular action.
         *
         * For example, a thread that has called <tt>Object.wait()</tt>
         * on an object is waiting for another thread to call
         * <tt>Object.notify()</tt> or <tt>Object.notifyAll()</tt> on
         * that object. A thread that has called <tt>Thread.join()</tt>
         * is waiting for a specified thread to terminate.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         * A thread is in the timed waiting state due to calling one of
         * the following methods with a specified positive waiting time:
         * <ul>
         *   <li>{@link #sleep Thread.sleep}</li>
         *   <li>{@link Object#wait(long) Object.wait} with timeout</li>
         *   <li>{@link #join(long) Thread.join} with timeout</li>
         *   <li>{@link LockSupport#parkNanos LockSupport.parkNanos}</li>
         *   <li>{@link LockSupport#parkUntil LockSupport.parkUntil}</li>
         * </ul>
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

线程创建方式

一般来说有三种使用线程的方法:

  • 继承 Thread 类。

  • 实现 Runnable 接口。

  • 实现 Callable 接口。

但是我们需要注意,实现 Runnable 和 Callable 接口的类只能当做一个可以在线程中运行的任务,而不是真正意义上的线程。

线程的启动是要调用 start 方法,而 start 方法始终是从 Thread 的 run 方法开始执行的,这也是 Runnable 和 Callable 最后都得交给 Thread 启动的原因。

所以我的理解是:Thread是线程类,另外两个是任务类

附上一段源码:

/**
 * Causes this thread to begin execution; the Java Virtual Machine
 * calls the <code>run</code> method of this thread.
 * <p>
 * The result is that two threads are running concurrently: the
 * current thread (which returns from the call to the
 * <code>start</code> method) and the other thread (which executes its
 * <code>run</code> method).
 * <p>
 * It is never legal to start a thread more than once.
 * In particular, a thread may not be restarted once it has completed
 * execution.
 *
 * @exception  IllegalThreadStateException  if the thread was already
 *               started.
 * @see        #run()
 * @see        #stop()
 */
public synchronized void start() {
    ~~~~~~~~~
}
继承 Thread 类

比较粗暴,直接继承 Thread 类,然后重写 run 方法。

当调用 start() 方法启动一个线程时,虚拟机会将该线程放入就绪队列中等待被调度,当一个线程被调度时会执行该线程的 run() 方法。

public class MyThread extends Thread {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyThread t = new MyThread();
    t.start();
}

为啥说这种方式比较粗暴,我们看看Thread类的run方法源码。

private Runnable target;

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

从源码可以看出来,Thread 更希望我们传入一个 Runnable 类型的对象,然后来调用这个

Runnable 对象的 run 方法,达到一种 线程与任务 分离的效果。而不是希望我们通过继承来粗暴的重写。

实现 Runnable 接口

实现 run() 方法,然后将 Runnable 子类传递给 Thread 类。

public class MyRunnable implements Runnable {
    public void run() {
        // ...
    }
}
public static void main(String[] args) {
    MyRunnable instance = new MyRunnable();
    Thread t = new Thread(instance);
    t.start();
}

这里需要实现 Runnable#run() 的原因有两个:

  • 因为 Thread#run() 里面调用了 target.run()。
  • Runnable是接口,也强制必须重写他的方法。

这里大家肯定觉得明明第二个理由就已经是必要的条件了,为啥还要第一个理由。

我们知道 Thread 的 run 方法是线程的起点。这个时候如果我们需要分开线程和任务,并且我们 Thread#run() 里面必须要调用一个固定的方法来执行任务类,那该怎么做呢?

那就是将 run() 方法向上抽取, 做成抽象方法 ,让每个任务类都强制去实现这个方法。

也就是说我们是因为 Thread#run() 需要那个任务类有固定的方法,所以我们抽出了 Runnable#run()。

实现 Callable 接口

与 Runnable 相比,Callable 可以有返回值,返回值通过 FutureTask 进行封装。

public class MyCallable implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
    MyCallable mc = new MyCallable();
    FutureTask<Integer> ft = new FutureTask<>(mc);
    Thread t = new Thread(ft);
    t.start();
    System.out.println(ft.get());
}

一些线程机制

Executor

Executor 管理多个异步任务的执行,而无需程序员显式地管理线程的生命周期。 也就是我们常说的线程池。

线程池的好处
  • 降低资源消耗
  • 提高响应速度
  • 提高线程的可管理性
线程池核心类ThreadPoolExecutor主要参数
  • corePoolSize:核心线程数量
  • maximumPoolSize:线程最大线程数
  • keepAliveTime:线程没有任务执行时最多保持多久时间终止(当线程中的线程数量大于corePoolSize的时候,如果这时没有新的任务提交核心线程外的线程不会立即销毁,而是等待,直到超过keepAliveTime)
  • unit:keepAliveTime的时间单位。
  • workQueue:阻塞队列,存储等待执行的任务,很重要,会对线程池运行过程产生重大影响。
  • threadFactory:线程工厂,用来创建线程,有一个默认的工场来创建线程,这样新创建出来的线程有相同的优先级,是非守护线程、设置好了名称)。
  • rejectHandler:当拒绝处理任务时(阻塞队列满)的策略。
/**
     * Creates a new {@code ThreadPoolExecutor} with the given initial
     * parameters and default thread factory and rejected execution handler.
     * It may be more convenient to use one of the {@link Executors} factory
     * methods instead of this general purpose constructor.
     *
     * @param corePoolSize the number of threads to keep in the pool, even
     *        if they are idle, unless {@code allowCoreThreadTimeOut} is set
     * @param maximumPoolSize the maximum number of threads to allow in the
     *        pool
     * @param keepAliveTime when the number of threads is greater than
     *        the core, this is the maximum time that excess idle threads
     *        will wait for new tasks before terminating.
     * @param unit the time unit for the {@code keepAliveTime} argument
     * @param workQueue the queue to use for holding tasks before they are
     *        executed.  This queue will hold only the {@code Runnable}
     *        tasks submitted by the {@code execute} method.
     * @throws IllegalArgumentException if one of the following holds:<br>
     *         {@code corePoolSize < 0}<br>
     *         {@code keepAliveTime < 0}<br>
     *         {@code maximumPoolSize <= 0}<br>
     *         {@code maximumPoolSize < corePoolSize}
     * @throws NullPointerException if {@code workQueue} is null
     */
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue) {
        this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
             Executors.defaultThreadFactory(), defaultHandler);
    }
主要的四种创建方法
  • Executors.newCachedThreadPool: 一个任务创建一个线程;

  • Executors.newFixedThreadPool: 所有任务只能使用固定大小的线程;

  • Executors.newSingleThreadExecutor: 相当于大小为 1 的 FixedThreadPool。

  • Executors.newScheduledThreadPoll: 定长线程池,核心线程数由用户传入,支持定时和周期任务执行

public static void main(String[] args) {
    ExecutorService executorService = Executors.newCachedThreadPool();
    for (int i = 0; i < 5; i++) {
        executorService.execute(new MyRunnable());
    }
    executorService.shutdown();
}

Daemon

守护线程是程序运行时在后台提供服务的线程,属于程序中不可或缺的部分。

当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

main() 属于非守护线程。

使用 setDaemon() 方法将一个线程设置为守护线程。

public static void main(String[] args) {
    Thread thread = new Thread(new MyRunnable());
    thread.setDaemon(true);
}
sleep()

Thread.sleep(millisec) 方法会休眠当前正在执行的线程,millisec 单位为毫秒。

sleep() 可能会抛出 InterruptedException,因此必须使用try-catch进行处理。线程中抛出的其它异常也同样需要使用try-catch进行处理。

public void run() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

线程互斥同步

在Java中,主要是使用 JVM 实现的 synchronized,和 JDK 实现的 ReentrantLock。

synchronized

1. 同步类

作用于整个类,如果说多个线程调用同一个类的不同对象上的这种同步语句,也会进行同步。

public void func() {
    synchronized (类名.class) {
        // ...
    }
}

2. 同步方法

public synchronized void func () {
    // ...
}

3. 同步代码块

它只作用于同一个对象,如果调用两个对象上的同步代码块,就不会进行同步。

public void func() {
    synchronized (this) {
        // ...
    }
}

4. 同步静态方法

会作用于整个类。

public synchronized static void fun() {
    // ...
}
ReentrantLock

ReentrantLock 是 java.util.concurrent(J.U.C)包中的锁。

比 synchronized 更灵活,只需要在需要的代码块上使用 lock() 和 unlock() 包裹即可。

public class LockExample {

    private Lock lock = new ReentrantLock();

    public void func() {
        lock.lock();
        try {
            for (int i = 0; i < 10; i++) {
                System.out.print(i + " ");
            }
        } finally {
            lock.unlock(); // 确保释放锁,从而避免发生死锁。
        }
    }
}

synchronized 和 ReentrantLock 比较

lock 和 synchronized 区别

线程之间的协作

当有多个线程可以需要一起工作去解决某个问题时,那他们肯定就有执行的先后之分,那么这时候就需要对线程进行协调 。

join()

在线程中调用另一个线程的 join() 方法,会将当前线程挂起,直到目标线程结束。

public class JoinExample {
    private class A extends Thread {
        @Override
        public void run() {
            System.out.println("A");
        }
    }
    private class B extends Thread {
        private A a;
        B(A a) {
            this.a = a;
        }
        @Override
        public void run() {
            try {
                a.join();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("B");
        }
    }
    public void test() {
        A a = new A();
        B b = new B(a);
        b.start();
        a.start();
    }
}

以上代码,虽然 b 线程先启动,但是因为在 b 线程中调用了 a 线程的 join() 方法,b 线程会等待 a 线程结束才继续执行,因此最后能够保证 a 线程的输出先于 b 线程的输出。

wait() notify() notifyAll()

调用 wait() 使得线程等待某个条件满足,线程在等待时会被挂起,当其他线程的运行使得这个条件满足时,其它线程会调用 notify() 或者 notifyAll() 来唤醒挂起的线程。

值得一提的事,它们都属于 Object 的方法,而不是 Thread 的方法。

只能用在同步方法或者同步控制块中使用,否则会在运行时抛出 IllegalMonitorStateExeception。

使用 wait() 挂起期间,线程会释放锁。这是因为,如果没有释放锁,那么其它线程就无法进入对象的同步方法或者同步控制块中,那么就无法执行 notify() 或者 notifyAll() 来唤醒挂起的线程,造成死锁。

wait() 和 sleep() 的区别

  • wait() 是 Object 的方法,而 sleep() 是 Thread 的静态方法;
  • wait() 会释放锁,sleep() 不会。
await() signal() signalAll()

java.util.concurrent 类库中提供了 Condition 类来实现线程之间的协调,可以在 Condition 上调用 await() 方法使线程等待,其它线程调用 signal() 或 signalAll() 方法唤醒等待的线程。相比于 wait() 这种等待方式,await() 可以指定等待的条件,因此更加灵活。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值