JavaEE|多线程(一)

并发编程

多进程和多线程是实现并发编程的两种技术手段。例如,每一个客户端请求发送到服务器上,服务

器提供一个进程,给这个客户端进行服务

线程和进程

通过对操作系统的初步认识,我们可以知道操作系统是多任务操作系统,同时CPU是操作核心,因

此通过多进程编程方式,可以实现"并发编程"的效果。

进程整体是一个比较"重"的概念,创建进程/销毁进程开销比较大,尤其是对于频繁的创建销毁进程

为了解决上述问题,引入线程(Thread),轻量级进程,这种创建销毁的开销更小

每个进程,都相当于是要执行的任务;每个线程,也是一个要执行的任务(运行一段代码指令)

进程和线程的区别

  • 进程包含线程,进程和进程之间所涉及到的资源则是各自独立的,确保了进程的稳定性,相互之间是不干扰的

  • 进程是操作系统分配资源的基本单位,线程是CPU上调度执行的基本单位,资源分配包括:CPU,内存,硬盘资源(文件描述符)...,每个线程上都会有状态,优先级,记账信息,上下文这样调度相关的数据。但这些数据都会共用同一个文件描述符和内存指针
  • 进程之间管辖的多个线程之间,会共享上述的内存资源和硬盘资源,网络宽带
  • 进程创建,需要申请资源;进程销毁,需要释放资源
  • 对于线程来说,只是第一个线程创建的时候(和进程一起创建的时候)申请资源,后续再创建线程,不涉及到资源申请操作(干的是少,快)
  • 只能所有的线程都销毁(进程销毁)才真正释放资源,运行过程中销毁某个线程,也不会释放资源
  • 一个进程挂了一般不会影响到其他进程, 但是一个线程挂了, 可能把同进程内的其他线程一起带走(整个进程崩溃)

多线程的不足

  1. 虽然提高线程的数目,能提升效率,也不是"线性增长",线程数目达到一定程度之后,就算线程再多,也没法起到效果
  2. 线程数目如果太多,线程的调度开销也会非常明显。因为调度开销拖慢程序的性能。
  3. 多个线程处理任务时,可能会出现线程安全问题/线程不安全,这样的冲突可能会导致代码出现bug
  4. 一个线程抛出异常·,可能会带走整个进程所有的线程都无法继续工作,但是如果及时捕获,处理掉,也不一定会导致进程终止

线程的实现

线程的实现

Thread类

线程是操作系统提供的概念,操作系统提供一些原生线程api,java对这些api进行了进一步的抽象

和封装,形成了Thread类

java 复制代码
class Mythread extends Thread{
    public void run(){
        System.out.println("hello thread");
    }
}
public class demo1 {
    public static void main(String[] args) {
        Thread t=new Mythread();
        t.start();
        System.out.println("hello main");
    }
}

Thread父类中,本身有一个run方法,程序员编写自己的逻辑,替代自身的run

start创建了一个新线程,多了一个执行流,能够干活(这个代码可以"一

心两用"同时做两件事)

第一个线程是main,第二个线程就是我们自己创建的MyThread

sleep()

sleep是Thread的静态方法,意思是让程序休眠,让当前的线程暂时放弃CPU,休息一会,时间过

了之后再执行

java 复制代码
 while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }

但要注意的是,调用sleep方法是会抛出异常,要进行异常捕获

对于main方法,我们可以用throws或try catch;但是在MyThread类中我们只能try catch,无法进

行throws,因为父类Thread run中,没有throws

有时候是main在前,有时候是thread在前。对于这个"抢占式执行"现象,是因为多个线程调度顺序

是随机的,这两线程谁先执行谁后执行都有可能,无法进行准确的预测

这个调度顺序是操作系统内核的调度器控制的,咱们无法在应用程序中编写代码控制,唯一能做的

是给线程设置优先级,但操作系统也不一定会严格执行

线程的运行

这个操作并没有创建线程,只是调用刚才重写的run,此时整个进程中只有一个main线程

main方法对应的线程就是一个进程至少要包含的那个线程,即主线程

使用jconsole方法观察线程

main和Thread-0线程是我们创建的线程,其他线程都是JVM内置的线程,启动任何一个java进程,都会自动带这些线程

线程的调用栈,获取线程状态的时刻,线程里的代码执行到哪了

通过调试,我们可以关注我们想要关注的线程

创建线程的写法

1.继承线程的写法,重写run

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 t=new Mythread();
        t.start();
        while (true) {
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

真正在系统中创建线程,JVM调用操作系统的API完成线程创建的操作

线程的入口方法,新的线程启动,就要执行这里的代码,run相当于"回调函数"

2.实现Runnable,重写run

java 复制代码
class MyRunnable implements Runnable{
    @Override
    public void run() {
        while (true){
            System.out.println("hello runnable");
            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);
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

是一个要执行的任务,就是一段要执行的逻辑,最终还是要通过Thread来创建真正的线程,线程要干啥,通过Runnable来表示(而不是通过直接重写Thread run来表示)

线程要执行的任务的定义放在Thread里面是继承,放到外面是Runnable

这种方式能更好的解耦合,代码修改会更加简单

3.使用匿名内部类

本质上就是方法一,使用匿名内部类

在{}里面就可以编写子类的定义代码,子类里有哪些属性,要有哪些方法,重写父类的哪些方法

创建了这个匿名内部类的实例,并且把实例的利用赋值给t

java 复制代码
public class demo3 {
    public static void main(String[] args) throws InterruptedException {
        Thread t=new Thread(){
            @Override
            public void run() {
                while(true){
                    System.out.println("hello thread");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        };
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}

这种代码具有一次性的特点,只能在匿名内部类使用一次

4.使用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);
        }
    }
}

使用Runnable,任务和线程的概念是分离的

5.针对三和四进一步改进,引入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(10000);
               } catch (InterruptedException e) {
                   throw new RuntimeException(e);
               }
           }
        });
        t.start();
        while(true){
            System.out.println("hello main");
            Thread.sleep(1000);
        }
    }
}
相关推荐
逻辑驱动的ken2 小时前
Java高频面试考点场景题05
java·开发语言·深度学习·求职招聘·春招
SamDeepThinking2 小时前
秒杀系统需求PRD
java·后端·架构
一 乐2 小时前
咖啡商城|基于springboot + vue咖啡商城系统(源码+数据库+文档)
java·数据库·vue.js·spring boot·论文·毕设·咖啡商城系统
Royzst2 小时前
String方法
java·开发语言
学习使我健康2 小时前
Android 事件分发机制
android·java·前端
代码羊羊2 小时前
Rust基础类型与变量全解析
开发语言·后端·rust
环流_2 小时前
【多线程初识】
linux·运维·服务器
纤纡.2 小时前
基于 PyQt5 的桌面应用开发实战:登录、预测、计算器、摄像头多功能系统
开发语言·人工智能·qt·计算机视觉
瀚高PG实验室2 小时前
因磁盘IO性能低导致程序An I/O error 报错
java·jvm·数据库·瀚高数据库