java中线程的创建方式-休眠-生命周期-工作方式

进程

进程的定义:进程是操作系统分配资源的基本单位。每个进程都有自己独立的内存空间和系统资源。

进程的独立性:进程之间是相互独立的,一个进程的崩溃不会影响到其他进程。

java中的体现:在Java中,每个运行的JVM实例就是一个进程。

也就是是说:咋们自己的java程序就是一个进程。 我们可以把工厂看作进程。流水线看作线程【好理解】

进程开销较大: 创建和销毁进程的开销较大

线程

线程的定义: 线程是进程内的执行单元,一个进程可以包含多个线程,所有线程共享进程的内存和资源。

1个进程中可以包含多个线程。

1个进程中至少有一个线程

线程开销较小:线程的创建和切换开销较小

进程与线程的区别

特性	           进程	                         线程
资源分配   	  独立的内存和系统资源	      共享进程的内存和资源
创建开销	              较大	                     较小
通信方式	         进程间通信(IPC)	          直接共享内存
独立性	          进程间相互独立	             线程间相互依赖
崩溃影响	     一个进程崩溃不会影响其他进程	一个线程崩溃可能导致整个进程崩溃

java默认就会产生一个进程

java在运行的时候默认就会产生一个进程(在Java中,每个运行的JVM实例就是一个进程)

这个进程会有一个主线程,代码都在主线程中执行

获取当前线程的名称

java 复制代码
package part;
public class Java01 {
    // main方法,运行在main线程中
    public static void main(String[] args) {
       // 线程: Thread就是线程类
        // Thread.currentThread() 获取当前正在运行的线程
        // getName获取线程的名称
        System.out.println(Thread.currentThread().getName()); // main
    }
}

创建1个自定义线程,并启动

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
       // 线程: Thread就是线程类
        // Thread.currentThread() 获取当前正在运行的线程
        // getName获取线程的名称
        System.out.println(Thread.currentThread().getName()); // main

        // 创建1个线程对象
        Thread t1 = new MyThread();
        // 开始启动这个线程
        t1.start();
    }
}

class MyThread extends Thread {
    // ctrl+o可以重写
    @Override
    public void run() {
        // 输出 线程名称:Thread-0
       System.out.println("线程名称:"+Thread.currentThread().getName());
    }
}

为啥先执行输出的是main 然后才是Thread-0

因为main线程最初进开始了,它没有准备时间。直接就开始了。

而我们的Thread-0需要准备时间

线程的生命周期7种

https://blog.csdn.net/weixin_46703995/article/details/131263544 [Java线程生命周期详解]参考的这里

线程的 7 种状态,其中1,2,3,7肯定是会有的。4,5,6可能不会出现的

1.新建状态(New): 当我们创建一个新的线程实例时,线程就处于新建状态。

这时候线程的start()方法还未被调用,线程对象还未开始执行。

在这个状态下,Java虚拟机(JVM)已经为此线程分配了必要的内存。

Thread t = new Thread(); // 线程此时处于:新建状态(New)

2.就绪状态(Runnable):当线程对象调用了start()方法后,该线程就处于就绪状态。

这个状态的线程位于可运行线程池中,等待被线程调度选中,获得CPU的使用权。

Thread t = new Thread(); // 线程此时处于新建状态(New)
t.start(); // 线程此时处于:就绪状态(Runnable)

3.运行状态(Running):线程获取到CPU时间片后,就进入运行状态,开始执行run()方法中的代码。

值得注意的是,代码执行的实际速度和效率与处理器的速度以及多核处理器的核数有关。

public void run() {
    System.out.println("Thread is running.");
}
// 如果此时这个方法正在执行,那么线程就处于Running状态

4.阻塞状态(Blocked): 当一个线程试图获取一个内部的对象锁(也就是进入一个synchronized块)

而该锁被其他线程持有,则该线程进入阻塞状态。

阻塞状态的线程在锁被释放时,将会进入就绪状态。

5.等待状态(Waiting):线程通过调用其自身的wait()方法、join()方法或LockSupport.park()方法

或者通过调用其他线程的join()方法,可以进入等待状态。

在等待状态的线程不会被分配CPU时间片,它们只能通过被其他线程显式唤醒进入就绪状态。

// 线程此时处于:等待状态(Waiting)
t.wait(); 
t.join();

6.超时等待状态(Timed Waiting): 当线程调用了sleep(long ms),wait(long ms),join(long ms)

或者LockSupport.parkNanos(), LockSupport.parkUntil()等具有指定等待时间的方法.

线程就会进入超时等待状态。当超时时间到达后,线程会自动返回到就绪状态。

// 线程此时处于:超时等待状态(Timed Waiting)
Thread.sleep(3000); // 单位是毫秒的哈 

7.终止状态(Terminated): 当线程的run()方法执行完毕,或者线程中断,线程就会进入终止状态。在这个状态下,线程已经完成了它的全部工作。

java 复制代码
// 当run()方法执行完毕,线程处于 Terminated 状态
public void run() {
    System.out.println("Thread is running.");
}

并发执行:多个线程之间是独立的

并发执行:多个线程之间是独立的。

谁先抢到CPU的执行权,谁就先执行。

或者说:线程1和线程2我们不知道谁先执行。每次运行的结果可能都不一样

下面我们通过代码来解释

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
        // 线程: Thread就是线程类
        // Thread.currentThread() 获取当前正在运行的线程
        // getName获取线程的名称
        // 创建2个线程对象
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        // 开始这2个启动这个线程
        t1.start();
        t2.start();
        System.out.println(Thread.currentThread().getName()); // main
    }
}

class MyThread1 extends Thread {
    @Override
    public void run() {
        // 输出 线程名称:Thread-0
       System.out.println("线程名称1:");
    }
}

class MyThread2 extends Thread {
    @Override
    public void run() {
        // 输出 线程名称:Thread-0
        System.out.println("线程名称2:");
    }
}

出现的结果可能是: main--》 线程名称1--》 线程名称2

也可能是:main--》 线程名称2--》 线程名称1

也就是说:不知道线程1和线程2我们不知道谁先执行。

多个线程之间是独立的。谁先抢到CPU的执行权,谁就先执行。

串行执行: 多个线程连接成串,然后按照顺序去执行。但是先执行的不一定先完成。

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
        // 线程: Thread就是线程类
        // Thread.currentThread() 获取当前正在运行的线程
        // getName获取线程的名称

        // 创建2个线程对象
        Thread t1 = new MyThread1();
        Thread t2 = new MyThread2();
        // 开始这2个启动这个线程
        t1.start();
        t2.start();
        // 将线程连接成串,先执行t1线程,然后再t2线程,最后是main线程。
        // 先执行的不一定先完成。再说一句:执行的不一定先完成
        try {
            t1.join();
            t2.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println(Thread.currentThread().getName()); // main
    }
}

class MyThread1 extends Thread {
    @Override
    public void run() {
        // 输出 线程名称:Thread-0
       System.out.println("线程名称1:");
    }
}

class MyThread2 extends Thread {
    @Override
    public void run() {
        // 输出 线程名称:Thread-0
        System.out.println("线程名称2:");
    }
}

输出的结果可能是:线程名称2:》线程名称1:》main。

也可能是:线程名称1:》线程名称2:》main。

因为: 先执行的不一定先完成。

休眠 Thread.sleep(3000) 等待3s在执行后面的代码

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
        // 线程: Thread就是线程类
        try {
            // sleep只和当前的类有关系。在哪一个线程中调用这个方法。哪个线程就会休眠。
            Thread.sleep(3000); // 单位是毫秒的哈
            // 上面的代码会等待3s,然后再执行
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        System.out.println("等3s在执行:Hello World");
    }
}

每隔1s输出Hello World

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
       while (true){
           Thread thread = new Thread();
           try {
               thread.sleep(1000);
               System.out.println("等待1s在输出");
           } catch (InterruptedException e) {
               throw new RuntimeException(e);
           }
       }
    }
}

自定义线程类

1,所有的自定义线程类都应该是去继承 Thread 这个线程类。

2,重写线程类中的run方法

3,启动线程 start

创建2个线程,输出线程的名称

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
        //创建线程对象
       Thread t1 = new MyThread1();
       // 启动线程
       t1.start();
       
        //创建线程对象
        Thread t2 = new MyThread2();
        // 启动线程
        t2.start();
    }
}

// 1.继承线程类Thread
class MyThread1 extends Thread {
    // 重写线程的run方法
    @Override
    public void run() {
        // 输出 线程名称1
       System.out.println("线程名称1");
    }
}

// 1.继承线程类Thread
class MyThread2 extends Thread {
    // 重写线程的run方法
    @Override
    public void run() {
        // 输出 线程名称2
        System.out.println("线程名称2");
    }
}

现在是在2个线程中去输出他们各自的线程名称。

通过这一段代码,我们发现有点复杂了。

怎么来优化一下呢?

我们只创建1个线程,通过传递的参数不同。输出不同的线程名称。

创建1个线程通过传递的参数不同输出不同的线程名称

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
        //创建线程对象
       Thread t1 = new MyThread1("线程1");
       // 启动线程
       t1.start();
    }
}

class MyThread1 extends Thread {
    private  String threadName;
    // 创建一个构造方法,通过参数来进行输出不同的语句
    public MyThread1(String name) {
        threadName = name;
        // 或者 this.threadName = name;
    }
    // 重写线程的run方法
    @Override
    public void run() {
       System.out.println(threadName);
    }
}

我们除了这种通过构造方法的方式还有其他方式吗?

其实是有的。我们在构建线程对象时,把逻辑传递给这个线程对象就可以。

语法就是: new Thread(()->{你的逻辑})

ps:与js的箭头函数有点相似

下面我们就来尝试一下

我们在构建线程对象时,把逻辑传递给这个线程对象

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
        //创建线程对象
       Thread t1 = new Thread(()-> {
           System.out.println("我是线程1");
       });
       // 启动线程
       t1.start();
    }
}

构建线程对象传递实现了 Runnable的匿名类

构建线程对象时,可以传递实现了 Runnable(线程的就绪状态)接口的类的对象。一般使用匿名类

java 复制代码
package part;
public class Java01 {
    public static void main(String[] args) {
        //创建线程对象,这里我们使用了 Runnable接口的匿名类
      Thread t =new Thread(new Runnable() {
          @Override
          public void run() {
            System.out.println("线程执行了哈哈");
          }
      });
      // 开始启动线程的哈
      t.start();
    }
}

匿名类

在某些场景下,类的名称并不重要;我们只想使用类中的方法;

这个时候我们可以采用特殊语法;匿名类

匿名类是一种没有名字的内部类

匿名类可以直接嵌入到代码中,通常出现在需要直接创建对象并使用其行为的地方。

例如事件监听器、回调、或者某个接口的具体实现。

匿名类的语法形式如下:

new NiNameClass() {

// 方法实现或者具体的逻辑

};

匿名类的应用场景

1.实现接口:实现简单接口而不必创建单独的类。

2.继承类:临时重写父类的某个方法,适合做简单扩展。

3.处理事件: 在 GUI 编程中,匿名类经常用于事件监听器的实现。

4.线程执行:用于 Runnable 接口的快速实现,简化代码。

匿名类的局限性

虽然匿名类能够简化代码的编写,但在某些情况下它也有一定的局限性:

1,只能使用一次:匿名类是一次性使用的类,不能在其他地方重复使用。如果需要复用代码,还是需要定义一个具名类。

2,不能定义构造函数:匿名类无法定义自己的构造函数,但可以调用父类的构造函数。

3,代码可读性差:当匿名类逻辑复杂时,可读性可能较差,尤其是当类体过长、代码嵌套较深时,维护起来较为困难。

4.局部变量的限制:匿名类只能访问外部类的有效最终变量(即外部方法的局部变量在匿名类中未被修改),这也是 Java 的作用域限制之一。

尾声

准备开始学习java了。

今天学习的第七天,每天都会发文章,我要卷起来。

请小伙伴们监督我,奥利给