首页 > 编程语言 > 详细

【Java多线程】1. 线程

时间:2021-02-04 15:30:07      阅读:25      评论:0      收藏:0      [点我收藏+]

线程与进程

<1> 概念

每个运行中的应用程序就是一个进程

线程又叫做轻量级进程,是进程内部的一个单一顺序的执行流,是进程中的实际运作单位。在多线程OS中,一个进程可能有多个线程,每条线程并行执行不同的任务。

进程是资源分配的基本单位;线程是独立运行、调度、分派的基本单位。

<2> 区别总结

  1. 资源:进程是资源分配的基本单位,线程不拥有资源,只可以访问所属进程的资源。
  2. 运行:在多线程OS中,进程不是一个可执行的实体,进程中的线程才是实际运作单位。
  3. 切换:线程切换(同进程中)比进程切换要快得多,因为进程切换开销大。

在同一进程中,线程的切换不会引起进程切换,从一个进程中的线程切换到另一个进程中的线程时,会引起进程切换。

  1. 通信:进程间需要借助IPC的一些手段;线程间可以通过直接读写同一进程中的数据直接进行通信。
  2. 开销:进程的创建、撤销、切换,所付出的开销远远大于线程。

因为创建和撤销进程时,系统要为之分配或回收资源,如内存空间、I/O 设备等;切换时,涉及当前执行进程 CPU 环境的保存及新调度进程 CPU 环境的设置。
而线程切换时只需保存和设置少量寄存器内容,开销很小。

线程状态

技术分享图片

<1> 初始态(New)

创建后尚未启动。

<2> 运行态(Runnable)

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

Java中的线程将操作系统中的就绪和运行两种状态笼统称为运行态。

  1. 就绪 Ready:已经获得执行所需的所有资源,正在等待 CPU 时间片。
    所有就绪的线程存放在就绪队列中。
  2. 运行中 Running:获得CPU执行权,正在运行。由于一个CPU同一时刻只能执行一条线程,因此每个CPU每个时刻只有一条运行中的线程。

<3> 阻塞态(Blocked)

在Java中,指线程请求锁失败时进入的状态。由一个阻塞队列存放所有阻塞态的线程。

阻塞态等待获取一个排它锁,不断请求资源,一旦请求成功,就会进入就绪队列。

<4> 等待态(Waiting)

线程等待其它线程显式地唤醒,否则不会被分配 CPU 时间片。由一个等待队列存放所有等待态的线程。

进入方法 退出方法
调用了 无Timeout参数的 object.wait() 方法 其它线程调用object.notify() / object.notifyAll()
调用了 无Timeout参数的 thread.join() 方法 被调用的线程执行完毕
LockSupport.park() 方法 LockSupport.unpark(Thread)

<5> 超时等待态(Timed Waiting)

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

进入方法 退出方法
Thread.sleep() 方法 时间结束
设置了 Timeout 参数的 object.wait() 方法 时间结束 / object.notify() / object.notifyAll()
设置了 Timeout 参数的 thread.join() 方法 时间结束 / 被调用的线程执行完毕
LockSupport.parkNanos() 方法 LockSupport.unpark(Thread)
LockSupport.parkUntil() 方法 LockSupport.unpark(Thread)

调用 Thread.sleep() 方法使线程进入限期等待状态时,常常用“使一个线程睡眠”进行描述。
调用 Object.wait() 方法使线程进入限期等待或者无限期等待时,常常用“挂起一个线程”进行描述。
睡眠和挂起是用来描述行为,而阻塞和等待用来描述状态。

阻塞和等待的区别

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

<6> 终止态(Terminated)

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

线程使用

1. 创建

有三种使用线程的方法:

<1> 实现 Runnable 接口

  1. 任务类 A 实现 Runnable 接口,且需要实现 run() 方法。
  2. 主线程中用类 A 的一个实例 a 来构造一个 Thread 实例 thread。
  3. 通过 thread.start() 来启动线程。
例子
public class A implements Runnable {
    public void run() {
        // ...
    }
}

public static void main(String[] args) {
    A a = new A();
    Thread thread = new Thread(a);
    thread.start();
}

<2> 实现 Callable 接口

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

  1. 任务类 B 实现 Callable 接口,且需要实现 call() 方法,且有返回值,类型为 T。
  2. 主线程中用类 B 的一个实例 b 来构造一个 FutureTask 实例 ft。
  3. 用 ft 来构造一个 Thread 实例 thread。
  4. 通过 thread.start() 来启动线程。
  5. 通过 ft.get() 来获得返回值。
例子
public class B implements Callable<Integer> {
    public Integer call() {
        return 123;
    }
}

public static void main(String[] args) 
    throws ExecutionException, InterruptedException {
    B b = new B();
    FutureTask<Integer> ft = new FutureTask<>(b);
    Thread thread = new Thread(ft);
    thread.start();
    Integer res = ft.get());
}

<3> 继承 Thread 类

  1. 类 C 继承 Thread 类,且需要实现 run() 方法,因为 Thread 类也实现了 Runnable 接口。
  2. 类 C 的一个实例为 c。
  3. 通过 c.start() 来启动线程。
例子
public class C extends Thread {
    public void run() {
        // ...
    }
}

public static void main(String[] args) {
    C c = new C();
    c.start();
}

<4> 从线程池中获取

应用程序可以使用Executor框架来创建线程池。

<5> 实现接口 VS 继承 Thread

一般实现接口更好,因为:

  1. Java 不支持多重继承,因此继承了 Thread 类就无法继承其它类,但是可以实现多个接口;
  2. 在类只要求可执行就行的情况下,继承整个 Thread 类开销过大。

2. 运行:start()

运行一个线程,只需调用Thread实例的start()方法即可。当调用start()方法之后,Java虚拟机会创建一个线程,然后在该线程中运行run()方法中定义的任务,真正实现多线程。

调用start()和run()的区别
这里必须强调的是应该调用start()方法,而不是run()方法。直接调用run()方法的结果是,虚拟机不会新建线程运行任务,而是在当前线程执行任务,无法实现多线程并发。

线程相关机制

1. Thread类的静态方法

Thread.sleep()

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

可能会抛出 InterruptedException,而且因为异常不能跨线程传播回 来,因此必须在本地线程进行处理。线程中抛出的其它异常也同样需要在本地进行处理。即,要放在try-catch(InterruptedException e)中。

注意

  1. Thread.sleep会让出CPU,但如果持有锁时不释放锁!
  2. Thread.sleep(0)是让当前线程放弃剩余时间片,CPU重新选择优先度最高的线程(可能还是自己)。在hotspot中相当于Thread.yield()

wait() 和 sleep() 的区别

  1. wait() 是 Object实例方法,而 sleep() 是 Thread静态方法;
  2. wait() 会释放锁,sleep() 不会。
例子
public void run() {
    try {
        Thread.sleep(3000);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
}

Thread.yield()

调用静态方法 Thread.yield() ,声明了当前线程已经完成了生命周期中最重要的部分,可以切换给其它线程来执行。

该方法只是对线程调度器的一个建议,而且也只是建议具有相同优先级的其它线程可以运行。

在hotspot中,Thread.yield() 相当于 Thread.sleep(0)

2. thread的实例方法

thread.setDaemon()

守护线程是程序运行时在后台提供服务的线程,不属于程序中必要的部分。当所有非守护线程结束时,程序也就终止,同时会杀死所有守护线程。

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

main() 属于非守护线程。

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

thread.join

在A线程中调用B线程的 join() 方法,会将A线程挂起,让B线程执行,直到线程B执行完毕。要放在try-catch(InterruptedException e)中。

join的内部使用了this.wait,也就是说,如果threadA持有threadB的锁,在threadA中调用threadB.join时,会释放该锁。

但如果threadA持有的其它对象的锁,则threadB.join无法释放,如果threadB也要竞争该锁,可能会产生死锁。

// 锁的是obj,join只释放threadB。因此join不释放锁,会死锁。
synchronized(obj){
    threadB.join(); 
}

// 锁的是threadB,join可以释放锁
synchronized(threadB){
    threadB.join();
}
无锁的例子

public class JoinExample {
    public void work1(Thread threadB){
        try {
            threadB.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for(int i = 0; i<5; i++){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("A");
        }
    }

    public void work2(){
        for(int i = 0; i<5; i++){
            System.out.println("B");
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        JoinExample example = new JoinExample();
        Thread threadB = new Thread(() -> example.work2());
        Thread threadA = new Thread(() -> example.work1(threadB));
        threadA.start();
        threadB.start();
    }
}


/*
B
B
B
B
B
A
A
A
A
A
*/
死锁的例子
// A start -> A锁住o -> B阻塞 -> A让B join -> (A不释放o,却等待B;B被阻塞,却需要先结束)死锁
public class JoinExample {
    public void work1(Object o, Thread threadB){
        synchronized (o){
            try{
                // 确保join前B已经start
                Thread.sleep(2000);
                threadB.join();

                for(int i = 0; i<5; i++){
                    Thread.sleep(1000);
                    System.out.println("A");
                }
            }
            catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void work2(Object o){
        synchronized (o){
            for(int i = 0; i<5; i++){
                System.out.println("B");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        JoinExample example = new JoinExample();
        Object o = new Object();
        Thread threadB = new Thread(() -> example.work2(o));
        Thread threadA = new Thread(() -> example.work1(o, threadB));
        // A先start,确保A已经锁住o后,B再start
        threadA.start();
        Thread.sleep(1000);
        threadB.start();
    }
}

【Java多线程】1. 线程

原文:https://www.cnblogs.com/BWSHOOTER/p/14372397.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!