java多线程(超详细)

1 - 线程

1.1 - 进程

进程就是正在运行中的程序(进程是驻留在内存中的)

  • 是系统执行资源分配和调度的独立单位

  • 每一进程都有属于自己的存储空间和系统资源

  • 注意:进程A和进程B的内存独立不共享。

1.2 - 线程

线程就是进程中的单个顺序控制流,也可以理解成是一条执行路径

  • 单线程:一个进程中包含一个顺序控制流(一条执行路径)

  • 多线程:一个进程中包含多个顺序控制流(多条执行路径)

  • 在java语言中:
    线程A和线程B,堆内存和方法区内存共享。
    但是栈内存独立,一个线程一个栈。

  • 假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

  • java中之所以有多线程机制,目的就是为了提高程序的处理效率。

  • 对于单核的CPU来说,不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,跟人来的感觉是多个事情同时在做。

1.3-java中多线程的生命周期

就绪状态:就绪状态的线程又叫做可运行状态,表示当前线程具有抢夺CPU时间片的权力(CPU时间片就是执行权)。当一个线程抢夺到CPU时间片之后,就开始执行run方法,run方法的开始执行标志着线程进入运行状态。

运行状态:run方法的开始执行标志着这个线程进入运行状态,当之前占有的CPU时间片用完之后,会重新回到就绪状态继续抢夺CPU时间片,当再次抢到CPU时间之后,会重新进入run方法接着上一次的代码继续往下执行。

阻塞状态:当一个线程遇到阻塞事件,例如接收用户键盘输入,或者sleep方法等,此时线程会进入阻塞状态,阻塞状态的线程会放弃之前占有的CPU时间片。之前的时间片没了需要再次回到就绪状态抢夺CPU时间片。

锁池:在这里找共享对象的对象锁线程进入锁池找共享对象的对象锁的时候,会释放之前占有CPU时间片,有可能找到了,有可能没找到,没找到则在锁池中等待,如果找到了会进入就绪状态继续抢夺CPU时间片。(这个进入锁池,可以理解为一种阻塞状态)

1.4- 多线程的实现方式(一)

  • 继承Thread类

    1、自定义一个类MyThread类,用来继承与Thread类

    2、在MyThread类中重写run()方法

    3、在测试类中创建MyThread类的对象

    4、启动线程

    /**

    • @author Mr.乐

    • @Description
      */
      public class Demo01 {
      public static void main(String[] args) {
      //创建线程
      MyThread t01 = new MyThread();
      MyThread t02 = new MyThread();
      MyThread t03 = new MyThread(“线程03”);

       //开启线程
      

    // t01.run();
    // t02.run();
    // t03.run();
    // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
    // start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
    // 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
    // 启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
    // run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。
    t01.start();
    t02.start();
    t03.start();
    //设置线程名(补救的设置线程名的方式)
    t01.setName(“线程01”);
    t02.setName(“线程02”);
    //设置主线程名称
    Thread.currentThread().setName(“主线程”);
    for (int i = 0; i < 50; i++) {
    //Thread.currentThread() 获取当前正在执行线程的对象
    System.out.println(Thread.currentThread().getName() + “:” + i);
    }
    }
    }
    class MyThread extends Thread{
    public MyThread() {
    }

    public MyThread(String name) {
        super(name);
    }
    
    //run方法是每个线程运行过程中都必须执行的方法
    @Override
    public void run() {
        for (int i = 0; i < 50; i++) {
            System.out.println(this.getName() + ":" + i);
        }
    }
    

    }

此处最重要的为start()方法。单纯调用run()方法不会启动线程,不会分配新的分支栈。

start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。线程就启动成功了。

启动成功的线程会自动调用run方法(由JVM线程调度机制来运作的),并且run方法在分支栈的栈底部(压栈)。

run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

单纯使用run()方法是不能多线程并发的。

1.5- 设置和获取线程名

  • 设置线程名

    • setName(String name):设置线程名

    • 通过带参构造方法设置线程名

  • 获取线程名

    • getName():返回字符串形式的线程名

    • Thread.CurrentThread():返回当前正在执行的线程对象

1.6- 多线程的实现方式(二)

  • 实现Runnable接口

    1、自定义一个MyRunnable类来实现Runnable接口

    2、在MyRunnable类中重写run()方法

    3、创建Thread对象,并把MyRunnable对象作为Tread类构造方法的参数传递进去

    4、启动线程

    /**

    • @author Mr.乐
    • @Description
      */
      public class Demo02 {
      public static void main(String[] args) {
      MyRunnable myRun = new MyRunnable();//将一个任务提取出来,让多个线程共同去执行
      //封装线程对象
      Thread t01 = new Thread(myRun, “线程01”);
      Thread t02 = new Thread(myRun, “线程02”);
      Thread t03 = new Thread(myRun, “线程03”);
      //开启线程
      t01.start();
      t02.start();
      t03.start();
      //通过匿名内部类的方式创建线程
      new Thread(new Runnable() {
      @Override
      public void run() {
      for (int i = 0; i < 20; i++) {
      System.out.println(Thread.currentThread().getName() + " - " + i);
      }
      }
      },“线程04”).start();
      }
      }
      //自定义线程类,实现Runnable接口
      //这并不是一个线程类,是一个可运行的类,它还不是一个线程。
      class MyRunnable implements Runnable{
      @Override
      public void run() {
      for (int i = 0; i < 50; i++) {
      System.out.println(Thread.currentThread().getName() + " - " + i);
      }
      }
      }

1.7- 多线程的实现方式(三)

实现Callable接口(java.util.concurrent.FutureTask; /JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。)

1、自定义一个MyCallable类来实现Callable接口

2、在MyCallable类中重写call()方法

3、创建FutureTask,Thread对象,并把MyCallable对象作为FutureTask类构造方法的参数传递进去,把FutureTask对象传递给Thread对象。

4、启动线程

这种方式的优点:可以获取到线程的执行结果。

这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
/**
 * @author Mr.乐
 * @Description  线程实现的第三种方式
 */
public class Demo04 {
    public static void main(String[] args) throws Exception {

        // 第一步:创建一个“未来任务类”对象。
        // 参数非常重要,需要给一个Callable接口实现类对象。
        FutureTask task = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
                // 线程执行一个任务,执行之后可能会有一个执行结果
                // 模拟执行
                System.out.println("call method begin");
                Thread.sleep(1000 * 10);
                System.out.println("call method end!");
                int a = 100;
                int b = 200;
                return a + b; //自动装箱(300结果变成Integer)
            }
        });

        // 创建线程对象
        Thread t = new Thread(task);

        // 启动线程
        t.start();

        // 这里是main方法,这是在主线程中。
        // 在主线程中,怎么获取t线程的返回结果?
        // get()方法的执行会导致“当前线程阻塞”
        Object obj = task.get();
        System.out.println("线程执行结果:" + obj);
        // main方法这里的程序要想执行必须等待get()方法的结束
        // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
        // 另一个线程执行是需要时间的。
        System.out.println("hello world!");
    }
}

1.8-线程控制

方法名

说明

void yield()

使当前线程让步,重新回到争夺CPU执行权的队列中

static void sleep(long ms)

使当前正在执行的线程停留指定的毫秒数

void join

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值