多线程【基础】

一、进程与线程[了解]

什么是进程?

  • 一个进程就是一个运行的程序(app)

  • 例如: idea,内网通,qq,微信

  • 进程是电脑资源分配的最小单位

  • 进程中至少包含一个线程

  • 进程中的资源,线程会共享,比如堆中的对象,静态域中的数据

什么是线程?

  • 线程是进程中的一个执行路径

  • 线程是资源调度的最小单位

  • 在内存栈中的数据,是属于线程的

为什么需要多线程?

  • 因为代码运行是有顺序的,执行完前面才能执行后面

  • 但是很多时候,需要功能同时执行

  • 顺序执行的代码,功能太简单,有局限性

  • 多线程,可以实现代码并发执行,即同时执行

  • 提升资源利用率,提升效率

Java代码是多线程的吗?

  • 是,至少有两个

    • 代码运行的main线程

    • 垃圾回收线程

二、创建线程[重点]

main方法是一个线程,我们需要额外单独再开一条独立线程.方式

  • 继承Thread类

  • 实现Runnable接口

  • 实现Callable接口

  • 线程池

2.1 继承Thread类

Thread类,在java中代表一个线程,想要实现多线程,方式1

  • 继承Thread

  • 重写run方法,写的是线程的任务

  • 创建线程对象

  • 开启线程,调用的是start

需求: main线程输出1-100,再开一个线程输出101-200,两个线程同时执行

// 线程类

public class MyThread1 extends Thread{
​
    /**
     * 重写run方法
     * 该方法内,就是新线程的执行任务代码
     */
    @Override
    public void run() {
        for (int i = 101; i < 201; i++) {
            System.out.println("新线程--->" + i );
        }
    }
}

//测试

public class TestThread {
​
    public static void main(String[] args) {
        // 创建线程对象
        MyThread1 t1 = new MyThread1( );
​
        // 开线程,注意!!! 此处开线程是start方法,不是run()
        // 调用start方法,开启新线程,自动调用run方法
        t1.start();
​
        //===============================
​
        // main线程
        for (int i = 1; i < 101; i++) {
            System.out.println("主线程--->" + i );
        }
    }
}

执行效果,出现main线程和新线程在争抢执行....

匿名内部类实现继承Thread

new Thread(){ // 相对于是在创建子类对象
    @Override
    public void run() {
        for (int i = 201; i < 301; i++) {
            System.out.println("线程3--->" + i );
        }
    }
}.start();

3.2 实现Runnable接口

  • 子类实现Runnable接口

  • 重写run方法

  • 创建子类对象

  • 将子类对象当做参数,传递给Thread类的构造方法,创建出thread对象

  • 使用thread对象开启start

// 子实现类

public class MyThread2 implements Runnable{
​
    @Override
    public void run() {
        for (int i = 1; i < 101; i++) {
            System.out.println("R1 线程执行 -->" + i  );
        }
    }
}

// 测试

public class TestThread2 {
​
    public static void main(String[] args) {
        // 创建子类型对象
        MyThread2 m2 = new MyThread2( );
​
        // 将子类对象,当参数传递给Thread类构造
        Thread t2 = new Thread(m2);
        // 开启线程
        t2.start();
​
        // =================================
        // 主线程
        for (int i = 1; i < 101; i++) {
            System.out.println("main线程执行 -->" + i  );
        }
    }
}

特别注意,第4步 第5步

image-20240611104913861

匿名内部类,实现RUnnable接口完成多线程

// 使用匿名内部类完成开启多线程
​
new Thread(new Runnable( ) { // 创建子类对象
    @Override
    public void run() {
        for (int i = 1; i < 101; i++) {
            System.out.println("R2 线程执行 -->" + i);
        }
    }
}).start();

3.3 二者区别

  • 从使用方便程度来说,继承Thread类实现多线程更直接方便点,因为创建对象,直接调用start即可

  • 但是呢,继承Thread类有局限性,类只能单继承,即一个类如果已经继承过别的,此时无法再继承Thread,只能实现Runnable接口开启线程

  • 所以,建议还是使用实现Runnable接口完成多线程

3.4 Callable接口

目前,实现多线程无论是继承Thread,还是实现Runnable接口,都必须重写run方法,但是这个run方法无返回值,且无异常抛出,只能捕获


基于以上问题,jdk提供了可以有返回值和抛出异常的开启线程的方案

  • Callable + FutureTask

// 实现Callable接口

public class MyThread3 implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        System.out.println("--- Callable线程开始工作 ---" );
        int sum = 0;
        for (int i = 1; i < 101; i++) {
            sum += i;
        }
        System.out.println("--- Callable线程结束工作 ---" );
        return sum;
    }
}

// 测试,将对象交给FutureTask

public class TestThread3 {
​
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 创建Callable子实现类对象
        MyThread3 c = new MyThread3( );
​
        // 将对象交给FutureTask的构造
        FutureTask<Integer> task = new FutureTask<>(c);
​
        // 将task对象,当参数传递给Thread对象.创建Thread
        Thread t = new Thread(task);
​
        // 开启线程
        t.start();
​
        // 一定注意!!!要线程结束,获得返回值
        Integer i = task.get( );
        System.out.println("线程返回结果:" + i );
    }
}

四、线程的API

已经掌握了开启线程的方式,下面学习线程对象的一些方法,这些方法可以改变线程的运行状态,例如线程名字,运行,停止,加入,阻塞,优先级和守护

4.1 关于线程名字[常用]

线程在创建时,默认都会给其一个指定名字,thread-0,-1,-2

  • getName()

也可以通过 方法设置名字

  • setName(String n)

也可以在创建对象时,通过构造方法设置名字

  • Thread(String name)

public static void main(String[] args) {
    // 自定义线程
    Thread t1 = new Thread("按摩线程"){
        @Override
        public void run() {
            Thread thread = Thread.currentThread( );
            System.out.println(thread.getName()+"自定义线程" );
        }
    };
    // 设置线程名
    // t1.setName("洗浴线程");

    // 获得线程名
    String name = t1.getName( );
    System.out.println("name = " + name);
    t1.start();
}

4.2 获得线程对象[重要]

JDK提供了一个方法,可以获得当前正在运行的线程对象

  • Thread Thread.currentThread();

public static void main(String[] args) {
    // 自定义线程
    Thread t1 = new Thread(  ){
        @Override
        public void run() {
            Thread thread = Thread.currentThread( );
            System.out.println(thread.getName()+"自定义线程" );
        }
    };
    t1.start();

    // 获得主线程对象
    Thread main = Thread.currentThread( );
    System.out.println("主线程名字 = " + main.getName() );
}

4.3 线程优先级[了解]

线程是有优先级,最高优先级是10,最低优先级是1,默认初创线程优先级是5

  • getPriority() 获得优先级

  • setPriority() 设置优先级

public class Demo2 {

    public static void main(String[] args) {
        Thread t1 = new Thread("T1") {
            @Override
            public void run() {
                for (int i = 1; i < 101; i++) {
                    System.out.println(getName()+"执行-->" +i );
                }
            }
        };
        t1.setPriority(1);


        Thread t2 = new Thread("T2") {
            @Override
            public void run() {
                for (int i = 1; i < 101; i++) {
                    System.out.println(getName()+"执行-->" +i );
                }
            }
        };
        t2.setPriority(1);

        Thread t3 = new Thread("T3") {
            @Override
            public void run() {
                for (int i = 1; i < 101; i++) {
                    System.out.println(getName()+"执行-->" +i );
                }
            }
        };
        t3.setPriority(10);

        t1.start();
        t2.start();
        t3.start();
    }
}

注意: 经过测试,虽然设置优先级,但是效果并不明显,不会保证绝对的顺序,只能是大概率优先..

且main方法只要在最上面先执行,优先级最高!

想要保证绝对的顺序或者交替执行等效果需要后续其他api...

4.4 守护线程

守护线程就是守护别的线程,随着守护的线程消失

需要在开启线程前,设置某一线程为守护线程,其他的线程就是被守护的 ; 即被守护的线程停止了,守护线程会随之停止

例如: qq聊天窗口和主面板,聊天窗口就是守护线程,主面板就是被守护.即聊天窗口可以随意关闭,但是主面板如果推出聊天窗口立即关闭


setDaemon(boolean n); 标记为true即为守护线程

public static void main(String[] args) {

    Thread dw = new Thread("大王") {
        @Override
        public void run() {
            System.out.println("大王打仗");
        }
    };

    Thread xb = new Thread("小兵") {
        @Override
        public void run() {
            for (int i = 1; i < 10000; i++) {
                System.out.println("小兵" + i + "在攻击...");
            }
        }
    };

    // 开启之前,设置小兵线程为守护线程
    xb.setDaemon(true);

    /**
     * 大王线程结束
     * 小兵线程发现后,也会结束不再执行完
     */
    dw.start();
    xb.start();
}

4.5 礼让线程

public class Demo4 {

    public static void main(String[] args) {
        Thread t1 = new Thread("汽车") {
            @Override
            public void run() {
                for (int i = 1; i < 1001; i++) {
                    if (i == 100){
                        Thread.yield();// 让出线程
                    }
                    System.out.println("汽车" + i + "执行...");
                }
            }
        };

        Thread t2 = new Thread("行人") {
            @Override
            public void run() {
                for (int i = 1; i < 1001; i++) {
                    System.out.println("行人" + i + "执行...");
                }
            }
        };

        t1.start();
        t2.start();
    }
}

4.6 join(插队)

void join() 等待该线程终止。 (无限期等待) void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。 (有限期等待)

public class Demo5 {

    public static void main(String[] args) {
        Thread t2 = new Thread("行人") {
            @Override
            public void run() {
                for (int i = 1; i < 1001; i++) {
                    System.out.println("行人" + i + "执行...");
                }
            }
        };

        Thread t1 = new Thread("汽车") {
            @Override
            public void run() {
                for (int i = 1; i < 1001; i++) {
                    if (i == 100){
                        try {
                            //t2.join();// 让线程2加入,直到线程2执行完毕,线程1才会继续
                            t2.join(1);// 让线程2加入,但是线程2只运行1毫秒,1毫秒后,线程1 2继续争抢
                        } catch (InterruptedException e) {
                            throw new RuntimeException(e);
                        }
                    }
                    System.out.println("汽车" + i + "执行...");
                }
            }
        };
        t1.start();
        t2.start();
    }
}

4.7 sleep(线程休眠)[重要]

static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行),

public static void main(String[] args) throws InterruptedException {
    Thread t2 = new Thread("行人") {
        @Override
        public void run() {
            for (int i = 1; i < 1001; i++) {
                System.out.println("行人" + i + "执行...");
            }
        }
    };

    Thread t1 = new Thread("汽车") {
        @Override
        public void run() {
            for (int i = 1; i < 1001; i++) {
                if (i == 100){
                    try {
                        /**
                         * 让当前线程休眠
                         * 让出系统资源,别的线程执行
                         * 等当前线程休眠结束,继续执行
                         */
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
                System.out.println("汽车" + i + "执行...");
            }
        }
    };

    // t1.start();
    // t2.start();

    for (int i = 10; i > 0; i--) {
        System.out.println("倒计时: "+i+ "秒");
        Thread.sleep(999);
    }
    System.out.println("发射!" );
}

4.8 线程停止stop

public static void main(String[] args) {
    new Thread(  ){
        @Override
        public void run() {
            for (int i = 1; i < 1001; i++) {
                if (i == 100) {
                    // this.interrupt();// 给线程一个中断的信号
                    
                    this.stop();// 立即让线程停止
                }
                System.out.println("线程执行 --> " +i );
            }
        }
    }.start();
}

五、线程的状态[面试]

线程的几种状态:

  • 创建/新建/初始

    • new 完线程对象

  • 就绪

    • 调用start

  • 等待/阻塞

    • join()或者sleep()

  • 运行

    • 执行run()方法

  • 死亡/销毁/终止

    • run方法执行完,或者调用stop()或者interrupt()中断等


清楚状态之间的转换

image-20240611163017552

线程对象 new完是没有开启线程

  • new 完属于初始状态(New)

  • 调用start了开启线程,如果抢到资源执行了run方法,属于运行状态(Running)

  • 如果没抢到,属于就绪(Ready)状态

  • 线程正常执行完就进入死亡(Terminated)状态(结束)


  • 如果过程中有sleep,那么当前线程进入等待状态,但是是有期限的等待(Timed Waiting),到时进入就绪状态,准备争抢!!

  • 如果别的线程使用了join(),那么当前线程会进入无限期等待(Waiting)

  • 在加锁的情况下,一个线程获得锁执行代码,另外一个线程就进入阻塞状态(Blocked)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值