1. 线程基本知识

请带着如下问题阅读本文。
1.什么是线程
2.线程和进程的区别
3.多线程的优缺点
4.什么是多线程的上下文切换
5.wait() sleep() 区别
6.线程实现方式哪4种?
7.如何保证多线程下I++正确
8.如何在线程间共享数据
9.怎么唤醒一个阻塞的线程
10.线程如果出现了运行时异常该怎么办?
11.JAVA里线程调度算法是什么?
12.线程类的构造方法和静态块是被哪个线程调用的?
13.线程的五个状态是什么?怎么转换?
14.wait()和sleep()的区别?
15.start()方法和run()方法的区别?
16.Runnable接口和Callable接口的区别?
17.Java中如何获取到线程dump文件?

线程

线程是进程内的执行单元。


image.png

进程是系统进行资源分配的基本单位,有独立的内存地址空间

线程是CPU独立运行和独立调度的基本单位,没有单独地址空间,有独立的栈,局部变量,寄存器, 程序计数器等。

创建进程的开销大,包括创建虚拟地址空间等需要大量系统资源

创建线程开销小,基本上只有一个内核对象和一个堆栈。

一个进程无法直接访问另一个进程的资源;同一进程内的多个线程共享进程的资源。

进程切换开销大,线程切换开销小;进程间通信开销大,线程间通信开销小。

线程属于进程,不能独立执行。每个进程至少要有一个线程,成为主线程

线程基本操作

image.png

NEW: 初始线程状态,还没START()
RUNABLE:在跑的,和等着被CPU调度到要跑的都是这个状态。
BLOCKED: 在等待一个监视器,或者锁。或者是被唤醒(notify()) 想重进同步方法或同步块的状态。
WAITING: wait(),thread.join(),locksupport.park()

TIMED_WAITING:
Thread.sleep
Object.wait with timeout
Thread.join with timeout
LockSupport.parkNanos
LockSupport.parkUntil

TERMINATED:线程执行结束了

IO阻塞,线程是RUNABLE 还是 BLOCKED?

行阻塞式 I/O 操作时,Java 的线程状态究竟是什么?是 BLOCKED?还是 WAITING?

可能你已经猜到,既然放到 RUNNABLE 这一主题下讨论,其实状态还是 RUNNABLE。我们也可以通过一些测试来验证这一点:

@Test
public void testInBlockedIOState() throws InterruptedException {
    Scanner in = new Scanner(System.in);
    // 创建一个名为“输入输出”的线程t
    Thread t = new Thread(new Runnable() {
        @Override
        public void run() {
            try {
                // 命令行中的阻塞读
                String input = in.nextLine();
                System.out.println(input);
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
              IOUtils.closeQuietly(in);
            }
        }
    }, "输入输出"); // 线程的名字

    // 启动
    t.start();

    // 确保run已经得到执行
    Thread.sleep(100);

    // 状态为RUNNABLE
    assertThat(t.getState()).isEqualTo(Thread.State.RUNNABLE);
}

在最后的语句上加一断点,监控上也反映了这一点:


image.png

关于监控,可见上一篇中的介绍。

网络阻塞时同理,比如socket.accept,我们说这是一个“阻塞式(blocked)”式方法,但线程状态还是 RUNNABLE。

@Test
public void testBlockedSocketState() throws Exception {
    Thread serverThread = new Thread(new Runnable() {
        @Override
        public void run() {
            ServerSocket serverSocket = null;
            try {
                serverSocket = new ServerSocket(10086);
                while (true) {
                    // 阻塞的accept方法
                    Socket socket = serverSocket.accept();
                    // TODO
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    serverSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }, "socket线程"); // 线程的名字
    serverThread.start();

    // 确保run已经得到执行
    Thread.sleep(500);

    // 状态为RUNNABLE
    assertThat(serverThread.getState()).isEqualTo(Thread.State.RUNNABLE);

}

监控显示:

image.png

start() 启动线程(NEW->RUNABLE)

start()方法来启动一个线程,真正实现了多线程运行。这时无需等待run方法体代码执行完毕,可以直接继续执行下面的代码;

run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。 如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。

stop() 终止线程(因为会释放所有监视器monitor,所以会造成数据不一致)

image.png

interrupt() 中断线程

suspend(), resume() 挂起和继续执行

因为SUSPEND不会释放锁,如果RESUME发生在SUSPEND前,则会死锁。


image.png

join() 等待线程结束


image.png

yeild() 谦让(把CPU使用权让出去)

sleep() 休眠

方法是线程类(Thread)的静态方法,让调用线程进入睡眠状态,让出执行机会给其他线程,等到休眠时间结束后,线程进入就绪状态和其他线程一起竞争cpu的执行时间。

因为sleep() 是static静态的方法,他不能改变对象的机锁,当一个synchronized块中调用了sleep() 方法,线程虽然进入休眠,但是对象的机锁没有被释放,其他线程依然无法访问这个对象

wait()
wait()是Object类的方法,当一个线程执行到wait方法时,它就进入到一个和该对象相关的等待池,同时释放对象的机锁,使得其他线程能够访问,可以通过notify,notifyAll方法来唤醒等待的线程

守护线程

只有守护线程时,JAVA虚拟机就退出了

在后台默默完成一些任务,如垃圾回收。

线程同步的基本操作

synchronized (可以给指定对象,给实例方法(指定对象为当前实例),静态方法(指定对象为当前类)加锁)

Object.wait() notify();


image.png

线程安全的概念

指一个类或一个函数,在多线程环境中被调用时;能够正确处理各个线程的局部变量,使程序功能正确完成。


image.png

多线程

优点:
1.可以使每个线程做自己的任务,代码上语义更明确
2.利用多核CPU的优势
3.可以把占据时间长如IO阻塞的任务放到后台处理

缺点:
1.线程安全问题(脏数据,死锁)
2.性能问题(活锁,饥饿,上下文切换开销)
3.线程本身需要更多的内存

当前任务执行一个时间片后会切换到下一个任务。但是,在切换前会保存上一个任务的状态,以便下次切换回这个任务时,可以再次加载这个任务的状态

从任务保存到再加载的过程就是一次上下文切换

线程实现

1.继承THREAD类,重写RUN方法
2.实现RUNABLE 接口,实例对象作为THREAD的构造函数的传参。
3.实现CALLABLE接口,通过FUTURETASK来创建


image.png

4.通过线程池创建。

image.png

Callable接口和Runnable接口相似,区别就是Callable需要实现call方法,而Runnable需要实现run方法;并且,call方法还可以返回任何对象,无论是什么对象,JVM都会当作Object来处理。但是如果使用了泛型,我们就不用每次都对Object进行转换了。
1.Callable可以返回一个类型V,而Runnable不可以

2.Callable能够抛出checked exception,而Runnable不可以。

3.Callable和Runnable都可以应用于executors。而Thread类只支持Runnable.

一个线程如果出现了运行时异常会怎么样?

如果这个异常没有被捕获的话,这个线程就停止执行了。另外重要的一点是:如果这个线程持有某个某个对象的监视器,那么这个对象监视器会被立即释放

怎么唤醒一个阻塞的线程?

如果线程是因为调用了wait()、sleep()或者join()方法而导致的阻塞,可以中断线程,并且通过抛出InterruptedException来唤醒它;如果线程遇到了IO阻塞,无能为力,因为IO是操作系统实现的,Java代码并没有办法直接接触到操作系统。

Java中用到的线程调度算法是什么?

抢占式。一个线程用完CPU之后,操作系统会根据线程优先级、线程饥饿情况等数据算出一个总的优先级并分配下一个时间片给某个线程执行

线程类的构造方法、静态块是被new这个线程类所在的线程所调用的,而run方法里面的代码才是被线程自身所调用的。

假设Thread2中new了Thread1,main函数中new了Thread2,那么:

(1)Thread2的构造方法、静态块是main线程调用的,Thread2的run()方法是Thread2自己调用的

(2)Thread1的构造方法、静态块是Thread2调用的,Thread1的run()方法是Thread1自己调用的

Java中如何获取到线程dump文件

(1)获取到线程的pid,可以通过使用jps命令,在Linux环境下还可以使用ps -ef | grep java

(2)打印线程堆栈,可以通过使用jstack pid命令,在Linux环境下还可以使用kill -3 pid

你可能感兴趣的:(1. 线程基本知识)