Day28.多线程、实现方式、线程对象的生命周期、Thread类常用方法

目录

多线程概述

实现线程的两种方式

第一种方式:编写一个类,直接继承java.lang.Thread,重写run方法。

 第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法。

线程对象的生命周期

Thread类常用方法

 获取当前线程对象

 sleep(睡眠)方法

提前结束线程

Java启动main方法,有几个线程?(扩展)


多线程概述

  • 什么是进程?什么是线程?

进程:电脑中时会有很多单独运行的程序,每个程序有一个独立的进程,而进程之间是相互独立存在的。

线程:线程是一个进程中的执行场景/执行单元同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。一个进程可以启动多个线程

  • 多线程

那什么是多线程?提到多线程这里要说两个概念,就是串行和并行,搞清楚这个,我们才能更好地理解多线程。

所谓串行,其实是相对于单条线程来执行多个任务来说的,我们就拿下载文件来举个例子:当我们下载多个文件时,在串行中它是按照一定的顺序去进行下载的,也就是说,必须等下载完A之后才能开始下载B,它们在时间上是不可能发生重叠的。

并行:下载多个文件,开启多条线程,多个文件同时进行下载,这里是严格意义上的,在同一时刻发生的,并行在时间上是重叠的。(单核cpu不支持)

多进程是指操作系统能同时运行多个任务(程序)。

并发:同一个微小的时间段(时间片)内,多个时间正在执行

多线程是指在同一程序中有多个顺序流在执行。多线程并发可以提高执行效率。

实现线程的两种方式

  • 第一种方式:编写一个类,直接继承java.lang.Thread重写run方法

//定义线程类
public class MyThread extends Thread{
    public void run(){
    }
}
    //创建线程对象
    MyThread t = new MyThread();
    //启动线程
    t.start();
实现线程的第一种方式:
    编写一个类,直接继承java.lang.thread,重写run方法
    怎么创建线程对象?   new 就行了
    怎么启动线程呢?    调用线程对象的start()方法
注意:
    亘古不变的原理:方法体当中的代码永远都是自上而下的顺序依次逐行执行的
public class ThreadTest02 {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主线中运行。
        //新建一个分支线程对象
        MyThread myThread = new MyThread();
        //启动线程
        //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后瞬间就结束了。
        //这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
        //启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)
        //run方法在分支栈的栈底部,main方法在主栈的栈底部,run和main是平级的。

        myThread.start();
        // 这里的代码还是运行在主线程中。
        for (int i = 0; i < 10000; i++) {
            System.out.println("主线程--->"+ i);
        }
    }
}
class MyThread extends Thread{
    @Override
    public void run() {
        //编写程序,这段程序运行在分支线程(分支栈)。
        for(int i = 0;i<10000;i++){
            System.out.println("分支线程--->" + i);
        }
    }
}

  •  第二种方式:编写一个类,实现java.lang.Runnable接口,实现run方法

  • 这种方式使用较多。一个类实现了接口,还可以去继承其他的类,更灵活。
//定义一个可运行的类
public class MyRunnable implements Runnable {
    public void run(){
    }
}
    //创建线程对象
    Thread t = new Thread(new MyRunnable());
    //启动线程
    t.start();
public class ThreadTest03 {
    public static void main(String[] args) {
        /*创建一个可运行的对象
        MyRunnable r = new MyRunnable();
        //将可运行的对象封装成一个线程对象
        Thread t = new Thread(r);*/
        Thread t = new Thread(new MyRunnable());  //合并代码
        //启动线程
        t.start();
        for(int i = 0;i<10000;i++){
            System.out.println("主线线程--->" + i);
        }
    }
}
//这并不是一个线程类,是一个可运行的类,它还不是一个线程
class MyRunnable implements Runnable{
    @Override
    public void run() {
        for(int i = 0;i<10000;i++){
            System.out.println("分支线程--->" + i);
        }
    }
}
  • 采用匿名内部类方式
        //创建线程对象,采用匿名内部类方式
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                //for()....
            }
        });
        //启动线程
        t.start();

线程对象的生命周期

传统线程模型中把线程的生命周期描述为五种状态:新建(New)就绪(Runnable)运行(Running)阻塞(Blocked)死亡(Dead)

 线程调度(了解)

  1. 抢占式调度模型
    哪个线程的优先级比较高,
    抢到的CPU时间片的概率就高一些 。java采用的就是抢占式调度模型。
  2. 均分式调度模型
    平均分配CPU时间片,每个占有的CPU时间片长度一样,平均分配,一切平等。有一些编程语言,线程调度采用的这种方式。
  3. 优先级最低为1,最高为10
  4. 个新线程的默认优先级与创建此线程的优先级相同,main方法默认优先级为5
 intgetPriority()
          获取线程的优先级。
 voidsetPriority(int newPriority)
          设置线程的优先级。
static voidyield()  礼让,抢到的时间片交给其他线程
          暂停当前正在执行的线程对象,并执行其他线程。(运行状态---> 就绪状态)
 voidjoin()()
          合并线程 当前线程进入阻塞,直到其他线程执行结束。
通常推荐设置Thread类的三个优先级常量
static intMAX_PRIORITY 最高10
          线程可以具有的最高优先级。
static intMIN_PRIORITY 最低1
          线程可以具有的最低优先级。
static intNORM_PRIORITY 默认5
          分配给线程的默认优先级。

java.lang.Thread类内部定义了一个枚举类用来描述线程的六种状态:
枚举中将就绪(New) 与 运行(Running) 状态定义为 Runnable,将阻塞(Blocked) 拆分为三种状态。

    public enum State {
        NEW,
        RUNNABLE,
        BLOCKED,
        WAITING,
        TIMED_WAITING,
        TERMINATED;
    }

Thread类常用方法

构造方法(Constructor)

Thread()
          分配新的 Thread 对象。

Thread(String name)
          分配新的 Thread 对象,并命名。

Thread(Runnable target)
          分配新的 Thread 对象。
Thread(Runnable target, String name)
          分配新的 Thread 对象,并命名。
 voidsetName(String name)
          改变线程名称,使之与参数 name 相同。
 StringgetName()
          返回该线程的名称。
static ThreadcurrentThread()    //this
          返回对当前正在执行的线程对象的引用。
static voidsleep(long millis)  //不释放锁
          在指定的毫秒数内让当前正在执行的线程休眠(进入阻塞状态),此操作受到系统计时器和调度程序精度和准确性的影响。
 voidinterrupt()
          中断线程睡眠。(依靠了java的异常处理机制)
          中断线程,实际上是给线程打上一个中断的标记,并不会真正使线程停止执行。
 voidsetDaemon(boolean on) //GC 垃圾回收器
          将该线程标记为守护线程或用户线程。
 voidsetPriority(int newPriority)
          更改线程的优先级。
static voidyield()  礼让,抢到的时间片交给其他线程
          暂停当前正在执行的线程对象,并执行其他线程。(运行状态----> 就绪状态)
 booleanisAlive()
          测试线程是否处于活动状态。
 voidinterrupt()
          中断线程。实际上是给线程打上一个中断的标记,并不会真正使线程停止执行。
static booleaninterrupted()
          检查线程的中断状态,调用此方法会清除中断状态(标记)。
 booleanisInterrupted()
          检查线程中断状态,不会清除中断状态(标记)
 voidjoin() 
         加入线程,等待加入的线程终止后再继续执行当前线程。
voidjoin(long millis)
          等待该线程终止的时间最长为 millis 毫秒。

部分Object方法

 voidwait(long timeout)
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者超过指定的时间量前,导致当前线程等待。
 voidwait(long timeout, int nanos)
          在其他线程调用此对象的 notify() 方法或 notifyAll() 方法,或者其他某个线程中断当前线程,或者已超过某个实际时间量前,导致当前线程等待。
 voidnotify()
          唤醒在此对象监视器上等待的单个线程。
 voidnotifyAll()
          唤醒在此对象监视器上等待的所有线程。

 获取当前线程对象

1、怎么获取当前线程对象?
    Thread t = Thread.currentThread();  静态方法
2、修改、获取线程对象的名字
    thread.setName("线程名字")
    thread.gatName(")
3、线程的默认名称
    Thread-0; Thread-1; Thread-2;
public class ThreadTest05 {
    public static void main(String[] args) {
        //这个代码出现在main方法当中,所当前线程就是主线程
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());    //main

        //创建线程对象
        MyThread2 t = new MyThread2();
        //启动线程
        t.start();
        //设置线程名字
        t.setName("t1");
        //获取线程的名字
        System.out.println(t.getName());

        MyThread2 t2 = new MyThread2();
        t2.start();
        t2.setName("t2");
        System.out.println(t2.getName());
    }
}
class MyThread2 extends Thread{
    public void run(){
        for (int i = 0; i < 100; i++) {
            //currentThread就是当前线程对象 当t1线程执行run方法,当前线程就是t1
            Thread currentThread = Thread.currentThread();
            System.out.println(currentThread.getName()+"-->"+i);
        }
    }
}

 sleep(睡眠)方法

关于线程的sleep方法
    sleep(long millis)
    1、静态方法:Thredd.sleep(1000);
    2、参数是毫秒
    3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其他线程使用
    4、Thredd.sleep()方法,可以做到这种效果:
        间隔特定的时间,去执行一段特定的代码,每隔多久执行一次
public class ThreadTest006 {
    public static void main(String[] args) {
        //让当前线程进入休眠,时间五秒 (当前线程是主线程)
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("hello World");

        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"--->"+i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}
  • 中断睡眠方法 interrupt() 
/*
sleep睡眠太久了,如果希望中途停止,该如何做?
    注意:不是中断线程的执行,是终止线程的睡眠。
 */
public class ThreadTest08 {
    public static void main(String[] args) {
        Thread t = new Thread(new MyRunnble2());
        t.setName("t");
        t.start();

        //希望五秒之后,t线程醒来(5秒后主线程手里活儿干完了)
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //中断t线程的睡眠,(这种中断睡眠的方式依靠了java的异常处理机制)
        t.interrupt();
    }
}
class MyRunnble2 implements  Runnable{

    //重点:run()当中的异常不能throws,只能try catch
    //因为run()方法在父类中没有抛出更多异常,子类不能比父类更多的异常。
    @Override
    public void run() {
        System.out.println(Thread.currentThread().getName() + "--->begin" );
        //睡眠一年
        try {
            Thread.sleep(1000*60*60*24*365);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName() + "--->end");
    }
}

提前结束线程

/*
怎么合理的终止一个线程的执行。这种方式是很常用的
 */
public class ThreadTest10 {
    public static void main(String[] args) {
        MyRunnable4 r = new MyRunnable4();  //
        Thread t = new Thread(r);
        t.setName("t");
        t.start();
        //模拟五秒
        try {
            Thread.sleep(1000*5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //想终止t的执行,将标记改为false即可
        r.run = false;
    }
}
class MyRunnable4 implements Runnable{
    //标记
    boolean run = true;
    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            if(run) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }else {
                //return就结束了,结束之前可以进行保存 save...
                return; //终止当前线程
            }
        }
    }
}

Java启动main方法,有几个线程?(扩展)

    public static void main(String[] args) {

        Map<Thread, StackTraceElement[]> maps = Thread.getAllStackTraces();
        for (Thread thread : maps.keySet()) {
            System.out.println(thread.getId() + "--" + thread.getName() + "---" + thread.getPriority());
        }
    }

 Reference是java中的引用类,它用来给普通对像进行包装,从而在JVM在GC时,按照引用类型的不同,在回收时采用不同的逻辑。

Finalizer是垃圾回收线程

线程所属说明
Attach ListenerJVMAttach Listener线程是负责接收到外部的命令,而对该命令进行执行的并且吧结果返回给发送者。通常我们会用一些命令去要求jvm给我们一些反馈信息,如:java -version、jmap、jstack等等。如果该线程在jvm启动的时候没有初始化,那么,则会在用户第一次执行jvm命令时,得到启动。
Signal DispatcherJVM前面我们提到第一个Attach Listener线程的职责是接收外部jvm命令,当命令接收成功后,会交给signal dispather线程去进行分发到各个不同的模块处理命令,并且返回处理结果。signal dispather线程也是在第一次接收外部jvm命令时,进行初始化工作。
CompilerThread0JVM用来调用JITing,实时编译装卸class。通常,jvm会启动多个线程来处理这部分工作,线程名称后面的数字也会累加,例如:CompilerThread1
ConcurrentMark-SweepGCThreadJVM并发标记清除垃圾回收器(就是通常所说的CMS GC)线程,该线程主要针对于老年代垃圾回收。ps:启用该垃圾回收器,需要在jvm启动参数中加上:-XX:+UseConcMarkSweepGC
DestroyJavaVMJVM执行main()的线程在main执行完后调用JNI中的jni_DestroyJavaVM()方法唤起DestroyJavaVM线程。   JVM在Jboss服务器启动之后,就会唤起DestroyJavaVM线程,处于等待状态,等待其它线程(java线程和native线程)退出时通知它卸载JVM。线程退出时,都会判断自己当前是否是整个JVM中最后一个非deamon线程,如果是,则通知DestroyJavaVM线程卸载JVM。ps:扩展一下:1.如果线程退出时判断自己不为最后一个非deamon线程,那么调用thread->exit(false),并在其中抛出thread_end事件,jvm不退出。2.如果线程退出时判断自己为最后一个非deamon线程,那么调用before_exit()方法,抛出两个事件: 事件1:thread_end线程结束事件、事件2:VM的death事件。然后调用thread->exit(true)方法,接下来把线程从active list卸下,删除线程等等一系列工作执行完成后,则通知正在等待的DestroyJavaVM线程执行卸载JVM操作。
 

Container

Background  Processor

JBOSS它是一个守护线程,在jboss服务器在启动的时候就初始化了,主要工作是定期去检查有没有Session过期.过期则清除.参考:http://liudeh-009.iteye.com/blog/1584876
ConfigClientNotifierConfigServerConfigServer服务端当有配置变更时,就会将最新的配置推送到ConfigServer客户端的一个数据列队中,ConfigClientNotifier线程用于定期检查该数据列队中是否有数据,如果有数据,则将数据分发到订阅该数据的组件去做业务逻辑,比如:tair和hsf的数据都订阅了ConfigServer数据源,当ConfigClientNotifier线程发现数据有更新时,就触发做数据分发特定特定信号标识将数据分发到相应的订阅者。
ConfigClientWorker-DefaultConfigServer包括主动向服务器端发送数据(主要是订阅和发布的数据)和接收服务器推送过来的数据(主要是订阅数据的值)。
Dispatcher-Thread-3Log4jLog4j具有异步打印日志的功能,需要异步打印日志的Appender都需要注册到AsyncAppender对象里面去,由AsyncAppender进行监听,决定何时触发日志打印操作。AsyncAppender如果监听到它管辖范围内的Appender有打印日志的操作,则给这个Appender生成一个相应的event,并将该event保存在一个buffuer区域内。  Dispatcher-Thread-3线程负责判断这个event缓存区是否已经满了,如果已经满了,则将缓存区内的所有event分发到Appender容器里面去,那些注册上来的Appender收到自己的event后,则开始处理自己的日志打印工作。Dispatcher-Thread-3线程是一个守护线程。
Finalizer线程JVM这个线程也是在main线程之后创建的,其优先级为10,主要用于在垃圾收集前,调用对象的finalize()方法;关于Finalizer线程的几点:1)只有当开始一轮垃圾收集时,才会开始调用finalize()方法;因此并不是所有对象的finalize()方法都会被执行;2)该线程也是daemon线程,因此如果虚拟机中没有其他非daemon线程,不管该线程有没有执行完finalize()方法,JVM也会退出;3) JVM在垃圾收集时会将失去引用的对象包装成Finalizer对象(Reference的实现),并放入ReferenceQueue,由Finalizer线程来处理;最后将该Finalizer对象的引用置为null,由垃圾收集器来回收;4) JVM为什么要单独用一个线程来执行finalize()方法呢?如果JVM的垃圾收集线程自己来做,很有可能由于在finalize()方法中误操作导致GC线程停止或不可控,这对GC线程来说是一种灾难;
Gang worker#0JVMJVM用于做新生代垃圾回收(monir gc)的一个线程。#号后面是线程编号,例如:Gang worker#1

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值