java多线程之概念和3种创建方式(详解)

前言必读

读者手册(必读)_云边的快乐猫的博客-CSDN博客

一、概念讲解

1.进程和线程的概念

进程:一个执行的应用程序

线程:一个应用程序内的具体执行不同模块

2.进程和进程之间的关系

进程和进程内存独立不共享 

3.线程和线程之间的关系 

(1)Java中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程

(2) 一个线程一个栈(每个栈之间独立执行,互不干扰,即多线程并发),栈内存独立。堆内存和方法区内存共享

(3)多线程的目的:提高程序的处理效率

(4)单核CPU能做到多线程并发吗:不能(cpu切换处理很快,超过肉眼速度,给人能多线程的假象)

4.并发和并行的概念

并发:一个cpu去执行不同的线程任务(哪里有任务就去哪里执行)

并行:许多个cpu一起去执行不同线程任务(对一个cpu说,好兄弟,我们一起来齐心协力干吧)

5.线程对象的生命周期:

(1)新建状态(new):

程序使用new关键字创建了一个进程后,该线程就是新建状态,内存由JVM分配,并初始化成员变量的值。 

(2)就绪状态(runnable):

线程对象调用start方法后,该线程就变成了就绪状态 ,JVM会为其创建方法栈和程序计数器,等待调度运行。

(3)运行状态(running)

处于就绪的线程获得了cpu,开始执行run方法的线程

(4)阻塞状态(blocked) 

线程因为某种原因放弃了cpu的使用权,暂停运行,直到线程通过就绪状态再进入运行状态。阻塞的情况有三种。

(4.1)等待阻塞(o.wait):等待队列。运行的线程执行o.wait方法,JVM会把该线程放入等待队列

(4.2)同步阻塞(lock):锁池。运行的线程在获取对象的同步锁时,若该同步锁被其他线程占用,JVM会把该线程放入锁池中

(4.3)其他阻塞(sleep/join):运行的线程执行sleep、join或者IO请求时,JVM会把该线程设为阻塞状态。当sleep、join等待线程超时或终止或者IO处理完毕时,该线程重新进入就绪状态。

(5)线程死亡(dead)

线程结束就是死亡状态。线程死亡分为三种

(5.1)正常结束:run或call方法执行完成

(5.2)异常结束:线程抛出一个未捕获的Exception或者Error

(5.3)强制结束:通过调用stop方法来结束线程,容易导致线程进入死锁,不推荐使用

二、创建线程的方式

方式一:编写一个类,继承java.lang.Thread类 

优缺点:

优点:编码简单

缺点:继承存在单继承的局限性,线程类继承Thread后,不能继承其他类,不利于扩展

步骤:

1.main方法外定义一个子线程继承Thread类。

2.重写run方法。(线程具体要执行的事情写在run方法里面)

3.new出一个Thread子线程对象-----要在main方法里面。(子线程要写在主线程方法之前)

4.调用start方法启动线程(调用这个才代表多线程启动,调用run只是单线程)

代码例子: 

package bao;
public class Test {
    public static void main(String[] args) {
        //4.创建Thread子线程的对象(要创建在主线程之前,要不然失去了多线程的意义)
        Thread t = new MyThread();
        //5.启动线程(执行的是run方法)
        t.start();
        //6.创建一个main方法里面的主线程进行比较
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程执行输出:"+i);
        }
    }
}

    //1.定义一个子线程继承Thread类
    class MyThread extends Thread{
    //2.重写run方法:在里面定义子线程要做什么   快捷键:run+回车键
        @Override
        public void run() {
            super.run();
    //3.子线程要做的事情  例子:要看执行循环多少次
            for (int i = 0; i < 5; i++) {
                System.out.println("子线程执行输出:"+i);
            }
        }
    }

运行结果:

 子线程执行输出:0
主线程执行输出:0
子线程执行输出:1
主线程执行输出:1
子线程执行输出:2
主线程执行输出:2
子线程执行输出:3
主线程执行输出:3
子线程执行输出:4
主线程执行输出:4

方式二: 编写一个类,实现java.lang.Runnable接口

优缺点:

优点:线程任务类只是实现接口,还可以继续继承类和实现接口,可扩展性强,容易实现资源共享

缺点:编程多一层对象包装,如果线程有执行结果是不能直接返回的

步骤:

1.main方法外定义一个子线程MyRunnable实现Runnable接口

2.重写run方法。(线程具体要执行的事情写在run方法里面)

3.new出一个MyRunnable对象(然后传给Thread对象)

4.new出一个Thread子线程对象-----要在main方法里面。(子线程要写在主线程方法之前)

5.调用start方法启动线程(调用这个才代表多线程启动,调用run只是单线程)

ps:这个创建线程方法和第一个创建线程方法就第一步不一样。还有在new子对象出来之前要创建一个MyRunnable对象,把这个值传入子线程对象里面。其他的都一样步骤 

代码例子: 

package bao;
public class Test {
    public static void main(String[] args) {
        //4.创建MyRunnable对象,并传给Thread子线程
        Runnable target = new MyRunnable();
        //5.创建Thread子线程对象
        Thread t = new Thread(target);
        //6.启动线程
        t.start();
        //7.创建一个main方法里面的主线程进行比较
        for (int i = 0; i < 5; i++) {
            System.out.println("主线程输出:"+i);
        }
    }
}

//1.定义一个线程类,实现Runnable接口
class MyRunnable implements Runnable{
//2.重写run方法。run方法里面定义线程要执行的代码。快捷键:run+回车键
    @Override
    public void run() {
//3.定义线程要执行的任务
        for (int i = 0; i < 5; i++) {
            System.out.println("子线程输出:"+i);
        }
    }
}

运行结果:

子线程输出:0
子线程输出:1
主线程输出:0
子线程输出:2
主线程输出:1
子线程输出:3
主线程输出:2
子线程输出:4
主线程输出:3
主线程输出:4

 方式三: 编写一个类,实现Callable接口

 优缺点:

优点: 在方式二的优点上,还能获取线程执行结果(上面两个就不能)

缺点:编程麻烦

步骤:

1.定义一个MyCallable线程类,实现Callable接口

2.重写Call方法(线程具体要执行的事情写在Call方法里面)

3.创建MyCallable任务对象,值交给下一步

4.创建FutureTask,值交给下一步

5.创建Thread线程对象

6.调用start方法启动线程

7.get方法输出线程执行结果

代码例子: 

package bao;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //4.创建MyCallable任务对象,值交给下一步
        Callable<String> call = new MyCallable(100);
        //5.创建FutureTask,值交给下一步
        FutureTask<String> f1 = new FutureTask<>(call);
        //6.创建Thread线程对象
        Thread t = new Thread(f1);
        //7.启动线程
        t.start();
        //输出结果
        System.out.println(f1.get());//遇到异常抛出或者捕捉处理就好了
    }
}

//1.定义一个MyCallable线程类,实现Callable接口
class MyCallable implements Callable<String>{  //填写String就返回类型结果
    //3.2写在这里方便阅读
    private int n;
    public MyCallable(int n) {
        this.n=n;
    }

    //2.重写Call方法,并在里面写子线程要执行的任务  快捷键:call+回车键
    @Override
    public String call() throws Exception {
        //return null;   需要返回值这个就要注释掉
//3.1定义线程要执行的任务  例子为求和
        int sum = 0;
        for (int i = 0; i < n; i++) {
            sum += i;
        }
        return "子线程的执行结果为"+sum;
    }
}

运行结果:

子线程的执行结果为4950 

三、常用API(其实也不常用)

1.给线程改名字,为了方便区分线程。 

 作用:为了更加方便看到是哪个线程在执行

代码例子来自方式一的代码,拿来修改添加

详解: 

1.按照这个格式输入

2.这个位置生成name的有参构造器

3.输入线程的名字 

运行结果:(输出的基数大了才能看到主子线程交叉运行,比如500)

主线程执行:0
主线程执行:1
主线程执行:2
主线程执行:3
主线程执行:4
子线程:0
子线程:1
子线程:2
子线程:3
子线程:4

2.线程睡眠(延迟执行) 

作用:延迟线程的执行结果 

代码例子来自方式一的代码,拿来修改添加

运行结果:

子线程输出后,延迟了5秒后才执行主线程 

旁白:掌握第一种创建方式,后面的那两种方式也差不多,就是改变接口和创建线程对象之前加了1层或者2层对象而已。还有重写的方法不一样,其他的都一样。根据不同的场景来用。常用的API知识点看看就好了。也不难 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

云边的快乐猫

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值