java多线程[持续更新]

4 多线程

1 基本概念

1.1 程序,进程,线程

  • 程序:完成一定功能的静态代码,对比火车站售票厅
  • 进程:正在执行的静态代码,称为进程,对比北京西站售票厅
  • 线程:并发执行一些异步任务,比对售票的中多个窗口

1.2 并行和并发

  • 并行:多核cpu下,多线程同时执行
  • 并发:如果是单核cpu,采用抢占时cpu调度模型,让cpu在多个线程之间切换执行

完成一个任务需要多少个线程

  • io密集型 cpu核数*2
  • cpu密集型 cpu核数
  • 混合型 经验

1.3 线程使用的场景

  • 网络连接tomcat,mysql,一个连接对一个一个线程, one connection one thread
  • 文件操作,文件下载,后台启动一个线程异步执行长时间的任务

2 多线程实战

2.1 Thread方式实现多线程

  1. 定义子类继承Thread类。
  2. 类中重写Thread类中的run方法。
  3. 创建Thread子类对象,即创建了线程对象。
  4. 调用线程对象start方法:启动线程,调用run方法

启动一个线程,在线程中执行1-10000的偶数打印工作

public class MyThread extends Thread {

    @Override
    public void run() {
        for (int i = 1; i <= 10000; i++) {
            if (i % 2 == 0) {
                //Thread.currentThread().getName():得到线程的名字
                System.out.println(Thread.currentThread().getName() + "\t" + i);

            }
        }
    }
}

测试

public class Test1 {

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        myThread1.start();

        MyThread myThread2 = new MyThread();
        myThread2.start();

        System.out.println(Thread.currentThread().getName() + "  main 线程 over");

    }

}
        JOptionPane.showMessageDialog(null, "是否确认向下执行...."); //主线程进入IO阻塞
        System.out.println("main over");
        
  • 如果子线程执行,进程不会停止

2.2 Runable方式实现多线程

  • 定义子类,实现Runnable接口。
  • 类中重写Runnable接口中的run方法。
  • 通过Thread类含参构造器创建线程对象。
  • 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
  • 调用Thread类的start方法:开启线程, 调用Runnable子类接口的run方法。

public class ThreadDemo {

    public static void main(String[] args) {
        //方式1:Thread子类,启动
        MyThread thread1 = new MyThread();
        thread1.start();

        //方式2:Runable方式(推荐的方式)
        //优势:可以实现多继承,比如继承BaseDao,然后再实现Runnable
        MyTask task = new MyTask();
        Thread thread2 = new Thread(task);
        thread2.start();

        //方式3:匿名内部类
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i <= 100; i++) {
                    System.out.println(Thread.currentThread().getName() +  "\t" +  i);
                }
            }
        }).start();
    }

}

//方式1
class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() +  "\t" +  i);
        }
    }
}

//方式2
//优势:可以实现多继承,比如继承BaseDao,然后再实现Runnable
class MyTask implements Runnable {

    @Override
    public void run() {
        for (int i = 0; i <= 100; i++) {
            System.out.println(Thread.currentThread().getName() +  "\t" +  i);
        }
    }
}

2.3 Thread常见方法

  • 构造函数:
    • Thread(): 创建新的Thread对象
    • Thread(String threadname): 创建线程并指定线程实例名
    • Thread(Runnable target): 指定创建线程的目标对象,它实现了Runnable接口中的run方法
    • Thread(Runnable target, String name): 创建新的Thread对象
  • void start(): 启动线程,并执行对象的run()方法
  • run(): 线程在被调度时执行的操作
  • String getName(): 返回线程的名称
  • void setName(String name):设置该线程名称
  • static Thread currentThread(): 返回当前线程。在Thread子类中就是this,通常用于主线程和Runnable实现类
  • static void yield(): 线程让步
    • 暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
    • 若队列中没有同优先级的线程,忽略此方法
  • join() : 当某个程序执行流中调用其他线程的 join() 方法时, 调用线程将被阻塞,直到 join() 方法加入的 join 线程执行完为止
  • static void sleep(long millis): (指定时间:毫秒) 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
  • stop(): 强制线程生命期结束,不推荐使用
  • boolean isAlive(): 返回boolean,判断线程是否还活着

2.3 sleep

指定线程休眠的时间,单位毫秒,让出cpu时间片,其他线程可以抢占cpu时间片

public class MyTask implements Runnable {

    @Override
    public void run() {
        for (int i = 1; i <= 100; i++) {
            if (i % 2 == 0) {
                //Thread.currentThread().getName():得到线程的名字
                System.out.println(Thread.currentThread().getName() + "\t" + i);
                try {
                    Thread.sleep(1000);
                    Thread.yield();
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }

    }
}

2.4 线程优先级

  • 线程的优先级等级
    • MAX_PRIORITY: 10
    • MIN _PRIORITY: 1
    • NORM_PRIORITY: 5
    public static void main(String[] args) {
        MyTask myTask = new MyTask();
        Thread thread1 = new Thread(myTask, "t1");
        thread1.setPriority(Thread.MIN_PRIORITY);
        thread1.start();

        Thread thread2 = new Thread(myTask, "t2");
        thread1.setPriority(Thread.MAX_PRIORITY);
        thread2.start();
    }
    

2.5 守护线程

  • 其他线程都执行结束,守护线程自动结束
  • 守护启动子线程,也是守护线程
  • 守护线程的语法thread.(setDaemon(true)设置守护线程
public class Test2 {

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
//        myThread1.setDaemon(true);
        myThread1.start(); //守护线程, gc线程,jvm线程结束gc会自动结束
        JOptionPane.showMessageDialog(null, "是否确认向下执行...."); //主线程进入IO阻塞
        System.out.println("main over");
    }

}

class MyThread extends Thread {
    @Override
    public void run() {
        while (true) {
            System.out.println("a");
        }
    }
}

2.6 线程合并join

有两个线程1,2如下
在这里插入图片描述

执行join()方法后
在这里插入图片描述

案例:


public class Test1 {

    /**
     * CountDownLatch:可以实现相同的效果
     * @param args
     */
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 1; i <= 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "\t" + i);
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                }
            }
        });
        t1.start();

        try {
            t1.join();
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        System.out.println(Thread.currentThread().getName() +  "  main orver");

    }

}

2.7 线程退出

2.7.1 stop

不推荐,线程退出方式粗暴,不管线程正在执行的任务,直接退出,可能丢失数据
在这里插入图片描述

public class Test1 {

    public static void main(String[] args) {
        Thread t1 = new Thread(new MyTask());
        t1.start();

        Scanner in = new Scanner(System.in);
        System.out.println("输入1/0:0表示退出");
        int i = in.nextInt(); ///主线程进入IO阻塞

        if (i == 0) {
            t1.stop();
        }

        System.out.println("main over");
    }


    static class MyTask implements Runnable {

        @Override
        public void run() {
            while (true) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }
    }

}

2.7.3 中断信号interrupt

在这里插入图片描述

  • interrupt():发送中断信号(true)
    • 如果线程在阻塞状态,比如sleep(),join(),wait(),这时接收到中断信号会抛出一个异常InterruptException,同时中断信号清除(false)
    • 只是发送信号,不会对线程产生影响
  • static interrupted():得到中断信号(true),然后把中断信号设置成false
  • isInterrupted():得到中断信号,不会清除中断信号
public class Test1 {

    public static void main(String[] args) {
        Thread t1 = new Thread(new MyTask());
        t1.start();

        JOptionPane.showMessageDialog(null, "是否确认向下执行...."); //主线程进入IO阻塞
        t1.interrupt(); //发送中断信号给t1

        System.out.println("main over");
    }

    static class MyTask implements Runnable {

        @Override
        public void run() {
            while (true) {
                System.out.println("a");

                try {
                    Thread.sleep(500000);
                } catch (InterruptedException e) {
                    System.out.println("bbbbbbbbbbbbbbb");
                    e.printStackTrace();
                    Thread.currentThread().interrupt(); //再次发送中断信号,中断信号发给阻塞线程,抛出Interrupt异常,中断信号清除
//                    throw new RuntimeException(e);
                }

                //得到中断信号,优雅的退出
                if (Thread.interrupted()) {
                    break;
                }

            }
        }
    }

}

3 线程原理

3.1 线程的调度与时间片

由于 CPU 的计算频率非常高,每秒计算数十亿次,于是,可以将 CPU 的时间从毫秒的维度进行分段,每一小段叫做一个 CPU 时间片。不同的操作系统、不同的处理器,线程的 CPU 时间片长度都不同。假定操作系统的线程一个时间片的时间长度为 20 毫秒(比如 Windows XP),在一个 2GHz 的 CPU 上,那么一个时间片可以进行计算的次数是: 20 亿/(1000/20) =4 千万次,也就是说,一个时间片内的计算量是非常巨大的。 目前操作系统中主流的线程调度方式大都是:基于 CPU 时间片方式进行线程调度。线程只有得到 CPU 时间片,才能执行指令,处于执行状态;没有得到时间片的线程,处于就绪状态,等待系统分配下一个 CPU 时间片。由于时间片非常短,在各个线程之间快速地切换,表现出来特征是很多个线程在“同时执行”或者“并发执行”。线程的调度模型,目前主要分为两种调度模型:分时调度模型、抢占式调度模型。
(1)分时调度模型——系统平均分配 CPU 的时间片,所有线程轮流占用 CPU。分时调度模型在时间片调度的分配上,所有线程人人平等。
下图就是一个分时调度的简单例子:三个线程,轮流得到 CPU 时间片;一个线程执行时,另外两个线程处于就绪状态

在这里插入图片描述

(2)抢占式调度模型——系统按照线程优先级分配 CPU 时间片。优先级高的线程,优先分配 CPU 时间片;如果所有的就绪线程的优先级相同,那么会随机选择一个;优先级高的线程获取的 CPU 时间片相对多一些。 由于目前大部分操作系统都是使用抢占式调度模型进行线程调度。 Java 的线程管理和调度是委托给了操作系统完成的,与之相对应, Java 的线程调度也是使用抢占式调度模型

3.2 线程状态

操作系统,线程的状态
在这里插入图片描述
java的线程状态
得到java的线程状态

public Thread.State getState(); //返回当前线程的执行状态,一个枚举类型值

Thread.State 是一个内部枚举类,定义了 6 个枚举常量,分别代表 Java 线程的 6 种状态,具体如下:

public static enum State {
     NEW, //新建
     RUNNABLE, //可执行:包含操作系统的就绪、运行两种状态
     BLOCKED, //阻塞  -> 操作系统线程中的阻塞
     WAITING, //等待  -> 操作系统线程中的阻塞
     TIMED_WAITING, //计时等待 -> 操作系统线程中的阻塞
     TERMINATED; //终止
 }

在Thread.State 定义的 6 种状态中,有四种是比较常见的状态,它们是: NEW 状态、RUNNABLE状态、 TERMINATED 状态、 TIMED_WAITING 状态。

接下来,将线程的 6 种状态以及各种状态的进入条件,做一个总结。
1. NEW 状态 通过 new Thread(…)已经创建线程,但尚未调用 start()启动线程,该线程处于 NEW(新建)状态。虽然前面介绍了4种方式创建线程,但是其中的其他三种方式,本质上都是通过new Thread( )创建的线程,仅仅是创建了不同的 target 执行目标实例(如 Runnable 实例)。
2. RUNNABLE 状态 Java 把就绪(Ready)和执行(Running)两种状态合并为一种状态:可执行(RUNNABLE)状态(或者可运行状态)。调用了线程的 start()实例方法后,线程就处于就绪状态;此线程获取到 CPU 时间片后,开始执行 run( )方法中的业务代码,线程处于执行状态。
在这里插入图片描述

(1)就绪状态就绪状态仅仅表示线程具备运行资格,如果没有被操作系统的调度程序挑选中,线程就永远是就绪状态;当前线程进入就绪状态的条件,大致包括以下几种:

  • 调用线程的 start()方法,此线程进入就绪状态。
  • 当前线程的执行时间片用完。
  • 线程睡眠(sleep)操作结束。
  • 对其他线程合入(join)操作结束。
  • 等待用户输入结束。
  • 线程争抢到对象锁(Object Monitor)。
  • 当前线程调用了 yield 方法出让 CPU 执行权限。
    (2)执行状态
    线程调度程序从就绪状态的线程中选择一个线程,作为当前线程时线程所处的状态。这也是线程进入执行状态的唯一方式。
  1. BLOCKED 状态 处于阻塞(BLOCKED)状态的线程并不会占用 CPU 资源,以下情况会让线程进入阻塞状态:
    (1)线程等待获取锁 等待获取一个锁,而该锁被其他线程持有,则该线程进入阻塞状态。当其他线程释放了该锁,并且线程调度器允许该线程持有该锁时,该线程退出阻塞状态。
    (2) IO 阻塞 线程发起了一个阻塞式 IO 操作后,如果不具备 IO 操作的条件,线程会进入阻塞状态。 IO 包括磁盘 IO、 网络 IO 等。 IO 阻塞的一个简单例子:线程等待用户输入内容后继续执行。
  2. WAITING 状态
    处于 WAITING(无限期等待)状态的线程不会被分配 CPU 时间片,需要被其他线程显式地唤醒,才会进入就绪状态。线程调用以下 3 种方法,会让自己进入无限等待状态:
  • Object.wait() 方法,对应的唤醒方式为: Object.notify() / Object.notifyAll()。
  • Thread.join() 方法,对应的唤醒方式为:被合入的线程执行完毕。
  • LockSupport.park() 方法,对应的唤醒方式为: LockSupport.unpark(Thread)。
  1. TIMED_WAITING 状态 处于 TIMED_WAITING(限时等待)状态的线程不会被分配 CPU 时间片,如果指定时间之内没有被唤醒,限时等待的线程会被系统自动唤醒,进入就绪状态。以下 3 个方法会让线程进入限时等待状态:
  • Thread.sleep(time) 方法,对应的唤醒方式为: sleep 睡眠时间结束。
  • Object.wait(time) 方 法 , 对 应 的 唤 醒 方 式 为 : 调 用 Object.notify() /Object.notifyAll()去主动唤醒,或者限时结束。
  • LockSupport.parkNanos(time)/parkUntil(time) 方法,对应的唤醒方式为:线程调用配套的 LockSupport.unpark(Thread)方法结束,或者线程停止(park)时限结束。

进入 BLOCKED 状态、 WAITING 状态、 TIMED_WAITING 状态的线程都会让出 CPU 的使用权;另外,等待或者阻塞状态的线程被唤醒后,进入 Ready 状态,需要重新获取时间片才能接着运行。
6. TERMINATED 状态 线程结束任务之后,将会正常进入 TERMINATED(死亡)状态;或者说在线程执行过程中发生了异常(而没有被处理),也会导致线程进入死亡状态。

3.3 多线程自增i++和线程执行原理

4个线程自增一个堆(共享的)里的对象的值

public class Test {

    private int i =0;

    public void f1() {
        i++;
    }

}

i++的反编译结果

javap -c Test.class

在这里插入图片描述
在这里插入图片描述

线程产生为没有一个虚拟机栈分配1M内存
-Xss128K:指定没有线程的栈大小是128K

自增类

public class Plus {

    private int amount = 0;

    public void selfPlus() {
            amount ++;
    }

    public int getAmount() {
        return amount;
    }

}

自增任务类

public class PlusTask implements Runnable {

    private Plus plus;

    public PlusTask() {
    }

    public PlusTask(Plus plus) {
        this.plus = plus;
    }

    @Override
    public void run() {
        for (int i = 0; i < 100000000; i++) {
            plus.selfPlus();
        }
    }

}

测试

    public static void main(String[] args) throws InterruptedException {
        Plus plus = new Plus();
        PlusTask plusTask = new PlusTask(plus);

        Thread t1 = new Thread(plusTask);
        Thread t2 = new Thread(plusTask);
        Thread t3 = new Thread(plusTask);
        Thread t4 = new Thread(plusTask);

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

        t1.join();
        t2.join();
        t3.join();
        t4.join();

        System.out.println("实际的值 = " + plus.getAmount());

    }
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值