JavaEE之多线程

线程概念:

1)线程定义:线程 (Thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际执行单元。 每个线程都可以按照自己的顺序执行各自的代码,多个线程之间同时执行着多份代码。

2)为什么要有线程?:

单核cpu算力遇到瓶颈,需要多核cpu来提高算力。而并发编程可以解决这一问题。

其次,虽然多进程也可以实现并发编程,但是线程比进程更轻量,更具有优势:

1.创建线程比创建进程更快

2.销毁线程比销毁进程更快

3.调度线程比调度进程更快

所以大家通常会使用多线程

可以举一个形象的例子来形容多进程和多线程:

有一个房间,一张桌子,一个人和桌子上的一百只鸡,这个人的任务是将这一百只鸡吃完。

为了提高效率,引入多进程: 有两个房间,房间里分别有一个桌子,一个人和50只鸡,相比之前,效率会大幅度提升。

而多线程则是:一个房间,一张桌子,一百只鸡,两个人共同来完成这个任务,节省下了房间和桌子的开销,效率仍然能够大幅提高。

3.试想,如果引入更多的线程效率是否会进一步提高?

答案是否定的:

当线程数目太多,线程调度的开销也会进一步扩大,进而拖慢程序的性能。

4.在任务进行的过程中,如果有两个人同时看中了同一只鸡,就有可能产生"冲突",导致线程不安全,有可能会使代码产生bug。当这两个人中的一个没能抢到鸡时,非常生气,就把桌子给掀了,我吃不了,大家都别吃了。

此时这个线程便会抛出异常(如果及时捕获处理掉,也不一定会导致进程终止),可能会带走整个进程,所有其他的线程都无法再继续工作。

3).进程和线程的区别:

1.进程包含线程,每个进程至少有一个线程存在,即主线程

2.进程与进程之间不共享内存空间,同一个进程的线程之间共享同一个内存空间。

3.进程是系统分配资源的最小单位,线程是系统调度的最小单位

4.一个进程挂了不会影响其他进程,但一个线程挂了,可能会把同进程内的其他线程一同带走(整个进程崩溃)

4)创建线程的四种写法:

4.11.继承Thread,重写run
  1. 首先定义一个类(这里我的类名为MyThread),这个类需要继承Thread
  2. 然后需要重写run方法,run方法内部就是我们要执行的线程代码
  3. 最后启动线程
java 复制代码
class MyThread extends  Thread{
    @Override
    public void run(){
        while(true){
            System.out.println("hello Thread");
            try {
                  Thread.sleep(1000);
            }catch (InterruptedException e){
                throw new RuntimeException(e);
            }
        }
    }
}


public class Demo1 {
    public static void main(String[] args) throws InterruptedException {
             //创建Thread类的子类,在子类中重写Run方法
        Thread t  = new MyThread();
        t.start();

        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }

}

在Thread父类中,本身有一个run方法,而我们可以重写这个方法,编写属于自己的逻辑

该代码中共有两个线程:

1.由newThread创建并调用t.start()启动,进入while循环后每秒打印"hello Thread".

2.主线程(main):进入while循环每秒打印"hello main"

4.111sleep简单介绍:

sleep为休眠,意味着让当前线程放弃cpu,休息1000毫秒后再开始执行该任务。

注:

调用sleep会让当前线程处于阻塞状态(TIMED_WAITING),在此期间,其他线程会调用该线程的Interrupt()方法来中断它的休眠,被中断后,sleep便会抛出InterruptedException异常,从而让线程有机会响应中断请求。

而Java要求强制要求处理这个异常,通常在run方法中用try-catch或在方法签名中throws异常。

运行结果:

我们可以看到,执行过程中,有的时候main方法在前,有的时候Thread在前,

这是因为多个线程,调度顺序是随机的(操作系统内核控制),无法预测执行顺序,称为抢占式调度。

复制代码
将t.start改为t.run后,由于主线程中并没有创建新的线程,而run又只是一个普通方法调用,主线程会直接执行run方法,进入while死循环打印hellothread。
4.112: 可以借助第三方工具来查看线程的具体执行情况:

上方的图标代表线程数量,左下角的代表当前进程中的线程。

4.12. 实现Runnable方法,重写run
  1. 定义一个类实现Runnable接口
  2. 重写run方法
  3. 构建Thread对象,将创建的Runnable对象作为参数传入
  4. 启动线程(t.start)
java 复制代码
class MyRunnable implements  Runnable {
    @Override
    public void run() {
        while (true) {
            System.out.println("hello Thread");

            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
public class Demo2 {
    public static void main(String[] args) throws InterruptedException {
        Runnable runnable = new MyRunnable();
        Thread t = new Thread(runnable);
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }

    }
}

这两种方式推荐哪一个?

第二种,因为Runnable更加解耦合,方便后续代码的修改

耦合:两个代码的关联关系越大,耦合越大,推荐低耦合(后续代码如果出错,不会影响其他代码)

高内聚:将与某种逻辑关联的代码放到一块。(有条理),反之同理。

写代码的时候推荐低耦合高内聚。

4.13:使用Thread的匿名内部类:

1.创建一个Thread类的子类(匿名)

2.{}里编写子类的定义代码

3.创建这个匿名内部类的实例,将实例的引用赋值给t

java 复制代码
public class Demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new Thread() {
            public void run() {
                while (true) {
                    System.out.println("hello thread");

                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }

                }
            }
        };
        t.start();


        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
    }
4.14 使用Runnable的匿名内部类
java 复制代码
public class Demo4 {
    public static void main(String[] args)throws InterruptedException {
         Runnable runnable = new Runnable() {
             @Override
             public void run() {
                 while(true){
                     System.out.println("hello thread");
                     try{
                         Thread.sleep(1000);
                     }catch (InterruptedException e){
                       throw new RuntimeException(e);
                     }

                 }
             }
         };
         Thread t = new Thread(runnable);
         t.start();
         while(true){
             System.out.println("hello main");
             Thread.sleep(1000);
         }
    }


}
4.15:引入lambda表达式(推荐)
java 复制代码
public class Demo5 {
    public static void main(String[] args)throws InterruptedException {
        Thread t = new Thread(()->{
            while(true){
                System.out.println("hello thread");
                try{
                    Thread.sleep(1000);
                }catch(InterruptedException e){
                    throw new RuntimeException(e);
                }
            }
        });
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

lambd表达式相当于是匿名内部类的替换写法,这种方法可以快速方便的就创建出一个线程

//lambda表达式本质上,是一个匿名函数(没有名字的函数,用一次就完了),主要来实现"回调函数"的效果

4.2 Thread类的其他属性和方法

前台线程与后台线程的区别:

前台线程:main线程及用户创建的线程,

后台线程:垃圾回收等辅助作用的线程(也叫做守护线程)

注:jvm会等待所有前台线程结束后,才会结束运行(不会等待后台进程)

前台线程好比一个酒桌中的话事人,而后台线程好比酒桌里的透明人,什么时候结束酒席由酒桌中的多个话事人决定。

4.21: 以下代码来形象的说明前台线程:

javascript 复制代码
public class Demo6 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                System.out.println("hello 1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });

        t1.start();

        Thread t2 = new Thread(() -> {
            while (true) {
                System.out.println("hello 2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t2.start();

        Thread t3 = new Thread(() -> {
            while (true) {
                System.out.println("hello 3");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        });
        t3.start();

        for (int i = 0; i < 3; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

虽然main线程结束了,但自己创建的3个前台线程还存在,所以进程还会存在,继续执行三个线程

4.22 IsAlive:判断线程是否存活:
java 复制代码
public class Demo8 {
    public static void main(String[] args) throws InterruptedException{
        Thread t1 = new Thread(()->{
            while(true){
                System.out.println("hello thread");
                try{
                    Thread.sleep(1000);
                }catch (InterruptedException e){
                    throw new RuntimeException(e);
                }
            }
        });
        System.out.println(t1.isAlive()); //false 因为线程还没有创建
        t1.start();
        System.out.println(t1.isAlive());
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

在t1线程还未创建之前,线程不存在,false,创建之后,为true(线程存在)。

4.23: setDaemon: 设置为后台进程。
java 复制代码
public class Demo7 {
    public static void main(String[] args) throws InterruptedException{
         Thread t = new Thread(()->{
             while(true){
                 System.out.println("hello thread");
                 try{
                     Thread.sleep(1000);
                 }catch (InterruptedException e){
                     throw new RuntimeException(e);

                 }
             }
         });
         t.setDaemon(true);
          t.start();

        for (int i = 0; i <3 ; i++) {
            System.out.println("hello main");
            Thread.sleep(1000);

        }
        System.out.println("线程结束");
    }
}

将前台线程t设置为后台线程后,当main线程(前台线程)三次执行完后,后台进程也会随之关闭

4.24:中断线程interrupt:

常见的有两种方法来实现:

1.通过共享的标记来沟通、

2.调用interrupt方法来沟通

1.使用自定义的变量来做标志位:

定义一个当作线程中断标志的变量,通过其他线程对这个变量的修改,实现线程中断:

java 复制代码
public class Demo10 {
    private static boolean isFinished = false;

    public static void main(String[] args) throws InterruptedException{
             Test test = new Test();

             Thread t = new Thread(()->{
                  while(!isFinished) {
                      System.out.println("hello thread");
                      try {
                          Thread.sleep(1000);
                      } catch (InterruptedException e) {
                          throw new RuntimeException(e);
                      }
                  }
                 System.out.println("thread 结束");

             });
             t.start();
             Thread.sleep(3000);
             isFinished = true;

    }

}

通过设置一个成员变量isFinished,在线程执行3s后更改成员变量isfinished的值,从而中断t线程。

lambda表达式会自动捕获方法内,之前出现的变量

lambda表达式内使用的标志,必须是final或者常量

2. 使⽤ Thread.interrupted() 或者 Thread.currentThread().isInterrupted() 代替⾃定义标志位
java 复制代码
public class Demo11 {
    public static void main(String[] args) throws InterruptedException{
        Thread t = new Thread(()->{
            System.out.println(Thread.currentThread().getName());
           while(!Thread.currentThread().isInterrupted()){
               System.out.println("hello thread");
               try{
                   Thread.sleep(1000);
               }catch (InterruptedException e){
                   //throw new RuntimeException(e);
                   break;
               }
           }
            System.out.println("t线程结束");
        });
        t.start();

        Thread.sleep(3000);
        System.out.println("main线程尝试终止t线程" );
        t.interrupt();


    }
}
复制代码

Thread.currentThread(),静态方法,哪个线程调用,就获取到哪个线程的引用

isInterrupted,是为了判断线程是否被中断,thread变量里的Boolean值,中断为true,反之为false。

而interrupt方法会发送中断请求,修改Boolean变量里的值,还能唤醒像sleep这样的阻塞方法。

以上就是博主对线程知识的分享,在之后的博客中会陆续分享有关线程的其他知识,如果有不懂的或者有其他见解的欢迎在下方评论或者私信博主,也希望多多支持博主之后和博客!!🥰🥰

相关推荐
摇滚侠41 分钟前
Maven 入门+高深 jar 包冲突 167-171
java·maven·jar
我是唐青枫43 分钟前
Java Optional 实战指南:优雅处理空值与链式转换
java·开发语言
摇滚侠43 分钟前
SpringBoot 升级,依赖冲突如何解决
java·spring boot·spring
jack@london1 小时前
eclipse启动tomcat6时报错OutOfMemoryError: PermGen space
java·ide·eclipse
小江的记录本1 小时前
【JVM虚拟机】类加载机制:类加载器、双亲委派模型、好处、破坏双亲委派的场景(附《思维导图》+《面试高频考点清单》)
java·jvm·spring boot·后端·python·spring·面试
李少兄1 小时前
Spring 对象创建范式:依赖注入与直接实例化的边界抉择
java·后端·spring
basketball6161 小时前
设计模式入门:2. 工厂模式详解 C++实现
开发语言·c++·设计模式
Lumbrologist1 小时前
【C++】零基础入门 · 第 16 节:智能指针
开发语言·c++
yu85939581 小时前
MATLAB 分支定界法(Branch and Bound)实现
开发语言·matlab