java线程相关内容大总结

java线程

JDK 1.2 之前,Java 线程是基于绿色线程(Green Threads) 实现的,这是一种用户级线程(用户线程),也就是说 JVM 自己模拟了多线程的运行,而不依赖于操作系统。 由于绿色线程和原生线程比起来在使用时有一些限制(比如绿色线程不能直接使用操作系统提供的功能如异步 I/O、只能在一个内核线程上运行无法利用多核),在 JDK 1.2 及以后 ,Java 线程改为基于原生线程(Native Threads)实现,也就是说 JVM 直接使用操作系统原生的内核级线程(内核线程)来实现 Java 线程,由操作系统内核进行线程的调度和管理。

线程和进程之间的关系

Java 运行时数据区域(JDK1.8 之后)
在这里插入图片描述
一个进程中可以有多个线程,多个线程共享进程的堆和方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器、虚拟机栈和本地方法栈

进程拥有的:堆和方法区

堆和方法区是所有线程共享的资源。
其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (几乎所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

线程拥有的:程序计数器、虚拟机栈 和 本地方法栈

程序计数器

程序计数器作用:线程切换后能恢复到正确的执行位置

  1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
  2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。
  3. 需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

虚拟机栈和本地方法栈

虚拟机栈: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务
每个 Java 方法在执行之前会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。

本地方法栈: 本地方法栈则为虚拟机使用到的 Native 方法服务

在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

java线程六种状态:

Java 线程在运行的生命周期中的指定时刻只可能处于下面 6 种不同状态的其中一个状态:

NEW: 初始状态,线程被创建出来但没有被调用 start() 。
RUNNABLE: 运行状态,线程被调用了 start()等待运行的状态。
BLOCKED:阻塞状态,需要等待锁释放。
WAITING:等待状态,表示该线程需要等待其他线程做出一些特定动作(通知或中断)。TIME_WAITING:超时等待状态,可以在指定的时间后自行返回而不是像 WAITING 那样一直等待。TERMINATED:终止状态,表示该线程已经运行完毕。
在这里插入图片描述

线程常用的构造方法

  • 实现Runable接口
  • 实现Callable接口
  • 继承Thread类

实现 Runnable和 Callable 接口的类只能当做一个可以在线程中运行的任务,不是真正意义上的线程,因此最后还需要通过 Thread 来调用。可以说任务是通过线程驱动从而执行的

实现Runable接口

public class RunnableThread implements Runnable {
    @Override
    public void run() {
        System.out.println("I am an instance of the RunnableThread");
    }
}

public class Main {

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

实现Callable接口

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

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

继承Thread类

继承Thread类,并重写了其中的 run()方法。

public class ExtendsThread extends Thread {

    @Override
    public void run( ) {
        System.out.println("用Thread类实现线程");
    }
}

public class Main {

    public static void main( String[] args) throws ExecutionException, InterruptedException {
        ExtendsThread mt = new ExtendsThread();
        mt.start( );
    }
}

可以直接调用 Thread 类的 run 方法吗?

new 一个 Thread,线程进入了新建状态。
调用 start()方法,会启动一个线程并使线程进入了就绪状态,当分配到时间片后就可以开始运行了。 start() 会执行线程的相应准备工作,然后自动执行 run() 方法的内容,这是真正的多线程工作。

但是,直接执行 run() 方法,会把 run() 方法当成一个 main 线程下的普通方法去执行,并不会在某个线程中执行它,所以这并不是多线程工作。

总结:调用 start() 方法方可启动线程并使线程进入就绪状态,直接执行 run() 方法的话不会以多线程的方式执行。

线程中断

中断是线程的一个标志位,对于正常运行的程序没有影响。
例如对于执行一般逻辑的线程,如果调用它的interrupt()方法,那么对这个线程没有任何影响。
比如线程a正在执行:

while(条件) 
	x ++;

如果其它线程调用a.interrupt();那么并不会影响a对象上运行的线程,如果在其它线程里测试a的中断状态可以发现它已经改变,但并不会停止这个线程的运行。

对wait,join,sleep方法的影响

如果sleep、wait等可以让线程进入阻塞的方法使线程休眠了,而处于休眠中的线程被中断,那么线程是可以感受到中断信号的,并且会抛出一个InterruptedException异常。在抛出InterruptException之前,Java虚拟机会将该线程的中断标记位清除。然后抛出InterruptException,此时调用isInterrunpted ()方法将会返回false。

对于wait中等待notify/notifyAll唤醒的线程

这时如果它的中断状态被改变,那么它就会抛出异常。这个InterruptedException异常不是线程抛出的,而是wait方法,也就是对象的wait方法内部会不断检查在此对象上休息的线程的状态,如果发现哪个线程的状态被置为已中断,则会抛出InterruptedException,意思就是这个线程不能再等待了,其意义就等同于唤醒它了。
和正常唤醒的唯一的区别:被notify/All唤醒的线程会继续执行wait下面的语句,而在wait中被中断的线程则将控制权交给了catch语句.一些正常的逻辑要被放到catch中来运行。

对于sleep和join中的线程

如果你调用了Thread.sleep(一年);现在你后悔了,想让它早些醒过来,调用interrupt()方法就是唯一手段,只有改变它的中断状态,让它从sleep中将控制权转到处理异常的catch语句中,然后再由catch中的处理转换到正常的逻辑。同样,对于join中的线程你也可以这样处理。

线程之间的通信

Volatile:

当两个线程A,B共同使用一个普通共享变量的时候,线程A对变量进行了修改,另外一个线程B是不能保证一定能看到最新值的。这就导致了线程之间的可见性问题,并发程序基于此运行是会出错的。

为了解决这个问题,Java提供了Volatile关键字,被该关键字修饰的变量不会存在B线程读不到最新值的情况。

Synchronized:

这是Java提供的另外一个关键字,它可以修饰方法或者同步块,被修饰之后能够确保同一时间只有一个线程可以处于方法或者同步块中,所以在方法和同步块中去访问共享变量,可以保证可见性和排他性。

wait/notify:

Java还提供了"等待/通知"的机制来进行线程间的协作运行。

Java的等待通知机制:

将上面AB换成线程理解就行。java在的等待和通知方法(如下5种)被定义在了Object类上。一个线程B调用了对象O的wait()方法进入等待状态WAITING或者TIMED_WAITING,而另外一个线程A调用了对象O的的notify或者notifyAll方法,线程B从wait方法返回,然后执行后续操作。

  • notify:通知一个在对象上等待的线程。
  • notifyAlI:通知所有等待在该对象上的线程。
  • wait():线程调用该方法进入等待(WAITING)状态,返回需要等待另外的线程通知或者被中断,另外注意线程调用wait方法后会释放对象的锁(能调用wait方法的前提也是获取到了对象的锁)
  • wait(long):线程调用之后会进入超时等待(TIMED_WAITING)状态,多一种返回方式,就是如果没有通知,也会在等待n毫秒后返回。
  • wait(long,int):超时时间更细,到纳秒

Thread.join():

AB两线程,A调用B.join(),表示A需要等待B完全执行完成,才会从B.join()处返回继续执行。当然也支持join(long)和join(long,int)两种超时返回。调用join后,A线程会处于等待(WAITING)或超时等待(TIMED_WAITING)状态

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值