线程(1)更新中……

"安全感是自己意识到自己在变强大"

1、什么是线程 ?那进程 ??

  • 程序:(一段静态代码,静态对象)program 为了完成特定任务、用某种语言编写的一组指令的集合。
  • 进程:process 程序的一次执行过程,或者正在运行中的程序。是一个动态过程,有自身的产生、存在和消亡的过程——生命周期。进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域。堆和方法区与线程共享。
  • 线程:thread 程序内部的一条执行路径
  • 若一个进程同时并行执行多个线程,就是支持多线程的
  • 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小

进程和线程本质区别在于每个进程拥有自己的一整套变量,线程共享数据

2、如何创建一个线程以及使用

2.1 创建方式一:继承Thread(不常用)

1、创建一个Thread 的子类

2、重写Thread 类的 run() 方法 ——> 需要做的事

3、创建Thread 类的子类的对象

4、通过此对象调用 start() 方法

// 创建线程
public class MyThread extends Thread {
    // 1、创建Thread 的一个子类

    // 2、重写Thread 类的run()方法
    public void run() {
        // 要做什么
        for (int i = 0; i < 20 ; i++) {
            if (i % 2 ==0){
                System.out.println(i);
            }
        }
    }
    // 测试
    // 3、创建子类对象
	MyThread myThread = new MyThread();
    // 4、调用start()方法
    myThread.start();   
    /*一个主线程,一个MyThread线程*/

通过调试发现,我们执行到myThread.start() 时,它自动跳转执行了MyThread中的run() 方法,

  • 通过查看api发现,关于start()方法作用:

1、启动当前线程

2、调用当前线程的run() 方法

image

note:

如果我们直接通过当前对象调用run() 方法myThread.run();,此时只是在同一线程下的普通方法调用,实则就不是在开启新线程了。

不可以让已经start() 方法的线程再start()------> 报IllegalThreadStateException,那如何再使用线程呢?——> 新建线程对象再调用即可

总结:要想创建多个线程,就去new 多个对象

​ 启动线程只能通过start()

2.2 方式二 :实现Runnable接口

1、创建一个实现Runnable 接口的实现类;

2、实现类里实现Runnable接口的抽象方法run(); (implements)

3、创建实现类对象;

4、将此对象作为参数传入Thread类的构造器中,创建Thread类的对象

5、通过Thread的对象调用start();——> 调用的是Runnable类型的target的run().

note:

// start() 方法是调用当前线程的 run() 方法,此时我们通过创建 Thread 类的对象,而用这个对象是怎么执行实现类里的 run() 呢 ??

看源码:

public void run() {
    if (target != null) {
        target.run();
    }
}
// 进入target发现还是在Thread类 中声明了一个属性:
private Runnable target;
// 我们刚才new出来的构造器,调用Thread类里的run()时,target已经不为空了,所以调的是target的run();
public Thread(Runnable target){    
}

看例子:

//构造线程函数
public class Test02 implements Runnable{    //1、实现接口
    //实现函数功能在类里
       //2、通过传参手动实现线程定义
    
    private String name;//定义线程名称---全局变量name

    public Test02(String name){       //构造函数-----局部变量参数name
        this.name=name;
        }
        private String getName(){
        	return this.name;//返回当前线程名称的功能
        }
    //3覆写
   //需执行代码-----自定义线程必须要有run函数用于执行线程要执行的代码
    public void run(){
        System.out.println("我是"+this.getName()+"线程");
            for (int i = 0; i <=100 ; i++) {
                System.out.println(this.getName()+"跑了"+i+"米");
                /* 注意,这里有getName()方法是因为前面定义了这个功能,
                如果没有就可用Thread.currentThread().getName() */
               
                try{
                Thread.sleep((long)Math.random()*1000);//休眠---计时等待
                }catch(InterruptedException exp){
                    System.out.println(this.getName()+"发生异常!");
                }
            }
    }

    public static void main(String[] args) {
        //两个子线程创建及启动
        //构造实现类对象---并行运行
        Test02 WG_runable=new Test02("乌龟");
        //WG_runable必须依赖于Thread才可构建线程对象

        Test02 TZ_runable=new Test02("兔子");

       Thread WG=new Thread(WG_runable); //2、通过传参(target)手动实现线程定义
       Thread TZ=new Thread(TZ_runable);

       WG.start();
       TZ.start();
    }
}

例2:

public class NightRunnable implements Runnable {
    public void run(){
        for (int i = 0; i < 10; i++) {
            System.out.println("线程1正在执行"+new Date().getTime());
        }
    }

    public static void main(String[] args) {

        Thread thread=new Thread(new NightRunnable());//接口里无方法,只能手动传参
        thread.start();//start是Thread的方法
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName()+"正在执行"+new Date().getTime());
        }
    }
}

2.2.1 关于线程的两种创建方式

  • 开发时,优先考虑用实现Runnable接口的方式,它没有类的单继承的局限性而且它可更适合处理多个线程实现数据共享情况;

  • 联系:Thread类本身也实现了Runnable接口,public class Thread implements Runnable.

  • 相同点:两种方式都要重写run(),将线程要执行的逻辑声明在run()中,调用的都是Thread类里的start();

2.3 方式三:实现Callable接口

与方式二相比:

run()方法可以有返回值;

方法可以抛出异常;

支持泛型的返回值;

需要借助FutureTask类,比如获取返回结果;

1、创建一个实现Callable接口的实现类;

2、实现call(),call()里面声明的是需要执行的操作;

3、创建Callable接口实现类的对象;(A)

4、创建以Callable接口实现类的对象作为参数的FutureTask对象;

5、将FutureTask对象作为参数,传递到Thread类的构造器中,创建Thread类对象调用start()。

new Thread(new FutureTask(A)).start();

// 6、获取Callable实现类重写的call()的返回值。

public class CallableThread implements Callable {//接口定义了n种方法,class 就要定义n种方法
    @Override
    public String call()throws Exception/*函数定义,必须写*/{
        System.out.println("我是" + Thread.currentThread().getName() + "线程");
        //我是Thread-0线程(此名称为系统赋予)
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + "跑了" + i + "米");
            try {
                //                MyThread1.sleep(1000);//每跑一米睡一秒
                Thread.sleep((long) Math.random() * 10000);//随机休息,验证两个线程互不干扰
                throw new IOException(Thread.currentThread().getName() +"累了");
                /*throw new IOException();*/
            } catch (InterruptedException exp) {
                System.out.println(Thread.currentThread().getName() + "发生异常");
            }
        }
        return Thread.currentThread().getName()+"到达终点";/*函数定义,必须写*/
    }

}
public class NightCallable implements Callable<String> {

   public String call()throws Exception{
       //@return computed result
       //     * @throws Exception if unable to compute a result

        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"正在执行"+new Date().getTime());
        }
        return "执行完毕";
    }

    public static void main(String[] args) throws ExecutionException ,InterruptedException{
        FutureTask task=new FutureTask(new NightCallable());
        Thread thread=new Thread(task);
        thread.start();
        for (int i = 0; i <10 ; i++) {
            System.out.println(Thread.currentThread().getName()+"线程正在执行");
        }
        System.out.println(task.get());// 这是个对象,目的就只是为了获取call()的返回值//get异常
         // get()返回值即为FutureTask构造器参数Callable实现类重写的call()的返回值
    }
}

关于Future接口:

可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等;

FutureTask是Future接口的唯一实现类;

FutureTask同时实现了Runnable、Future接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值。

如何理解实现Callable接口比实现Runnable创建多线程要强大 ??

1、call() 可以有返回值;(分线程执行完可以给另外的线程返回一个结果)

2、call() 可以抛出异常,被外面的操作捕获,获取异常信息;

3、Callable支持泛型;
image

2.4 方式四:线程池

经常创建和销毁、使用量特别大的资源,比如并发情况下的线程对性能影响很大

  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建、销毁、实现重复利用,

  • 好处:

    • 提高响应速度(减少了创建新线程的时间)

    • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)

    • 便于线程管理

      • corePoolSize: 核心池的大小
      • maximumPoolSize:最大线程数
      • keepAliveTime:线程没有任务时最多保持多长时间后会终止
      • ……

3、线程的生命周期

生命的最终都走向死亡

打开源码可以看到里面的几种状态:
image

NEW,新建

  • 还没调用start()
  • 当一个Thread类或其子类的对象被创建时,新生的线程对象处于新建状态

RUNNABLE ,就绪

  • 处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,我已具备运行条件,等CPU分配资源
    (线程正在JVM中被执行,等待来自操作系统的调度)
    image

BLOCKED,阻塞

  • CPU因为某些原因不能立即执行,需要挂起,临时终止自己的执行
  • 外部原因

WAITING,无限期等待

如果没被唤醒,那就一直等待

TIMED_WAITING,有限期等等

等待一个指定时间

TERMINGATED,终止线程状态

  • 线程完成了自己的工作或被提前强制性地终止stop()或出现异常导致结束
  • 阻塞和等待

阻塞是因为外部原因需要等待,是个临时状态;

等待一般是主动调用方法,发起主动等待,等待可传参确定等待时间。

image

该图片来自网上,如有不妥,联系删

  • 只有就绪和运行之间是双向的,

  • 生命周期关注两个概念:状态、相应的方法

  • 关注状态A——> 状态B,哪些方法执行了(回调方法)

  • 某个方法主动调用:自己用对象去做事导致状态A——> 状态B

4、sleep()

sleep(long millitime): 让当前线程“睡眠” 指定睡眠时间,线程阻塞状态(单位是毫秒),这时要try-catch InterruptedException 异常,

5、join()

插队
:线程参与,在线程 a 中调用线程b的join(),此时线程 a 就进入阻塞状态,直到线程 b 完全执行完成,线程 a 才结束阻塞状态
image

其他

看到关于线程的一些问答题:

http://ifeve.com/java-multi-threading-concurrency-interview-questions-with-answers/

posted @ 2022-08-02 13:05  来日可追  阅读(23)  评论(0编辑  收藏  举报