多线程编程

Java内置了对多线程的支持,
线程和进程的区别:进程是操作系统资源分配的基本单位,线程是操作系统的最小单元,操作系统运行一个程序时,就会为其创建一个进程,进程中可能包含多个线程
* 例子:一个Java程序,从最开始的mian()方法执行,然后按照既定的逻辑执行,看似没有其它的线程参与,但是,Java程序本身就是一个多线程的程序,是main线程和多个其它线程同时运行。
* 为什么使用多线程:执行一个Hello World,却启动了那么多无关线程,是不是把简单的问题复杂化了?
使用多线程的原因:更多的处理器核心、更快的响应时间、更好的编程模型

两种线程:用户线程、守护线程
* 用户线程:用户自定义创建的线程,主线程停止,用户线程不会停止
* 守护线程:当进程不存在或主线程停止,守护线程也会被停止。使用thread.setDaemon(true)方法设置为守护线程。

如何终止一个线程?
* 1.执行结束
* 2.new Thread.stop()、new Thread.destroy() ,不推荐使用,都已过期
* 3.中断线程,最优雅的方式,new Thread.interrupt() 或 Thread.interrupted()
 区别:interrupted():返回当前线程的中断标志位,并设置中断标志位false;
     interrupt():设置线程对象的中断标志位为 true;
     isInterrupted():返回线程对象的中断标志位;
     interrupted()属于类方法,而interrupt()和isInterrupted()属于对象方法。

内存模型相关概念:计算机在执行程序时,每条指令都是在CPU中执行,而执行指令过程中,势必涉及到数据的读取和写入,临时数据是存放在主存(物理内存),速度慢,所以需要cpu高速缓存。
* 由于CPU执行速度很快,当程序在运行过程中,会将运算需要的数据从主存复制一份到CPU的高速缓存当中,那么CPU进行计算时就可以直接从它的高速缓存读取数据和向其中写入数据,当运算结束之后,再将高速缓存中的数据刷新到主存当中

JMM(Java内存模型):屏蔽各个硬件平台和操作系统的内存访问差异,以实现让Java程序在各种平台下都能
  达到一致的内存访问效果。它定义了程序中代码的执行次序

什么是线程安全:可见性,原子性,有序性(也是并发编程中的三个概念)
* 原子性:在Java中,对基本数据类型变量的读取和赋值操作是原子性操作,即这些操作不可被中断,要么执行,要么不执行
* 可见性:Java提供了volatile关键字来保证可见性。当一个共享变量被volatile修饰时,它会保证修改的值会立即被更新到主存,当有其他线程需要读取时,会去内存中读取新值。
* 有序性:在Java内存模型中,允许编译器和处理器对指令进行重排序,但是重排序过程不会影响到单线程程序的执行,却会影响到多线程并发执行的正确性。通过volatile可保证一定的“有序性”,另外可以通过synchronized和Lock来保证有序性

获取程序执行的线程信息

public class MultiThread {
    public static void main(String[] args) {
        // 获取Java线程管理MXBean
        ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
        // 不需要获取同步的monitor和synchronizer信息,仅获取线程和线程堆栈信息
        ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
        // 遍历线程信息,仅打印线程ID和线程名称信息
        for (ThreadInfo threadInfo : threadInfos) {
            System.out.println("[" + threadInfo.getThreadId() + "] " + 
              threadInfo.getThreadName());
        }
        // =》得出结论:Java程序执行,是main线程和多个其它线程同时运行。
    }
}
线程生命周期
* 1、新建状态:使用 new 或其子类建立一个线程对象后,该线程就处于新建状态。它保持这个状态直到调用start()
* 2、就绪状态:当线程对象调用start()方法后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,等待JVM里线程调度器的调度
* 3、运行状态:就绪状态的线程获取 CPU 资源后,就会执行 run(),此时线程便处于运行状态。运行状态的线程最复杂,它可以变为阻塞状态、就绪状态和死亡状态。
* 4、阻塞状态:一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源后,就会进入阻塞状态,在睡眠时间已到或获得设备资源后可以重新进入就绪状态。
      可以分为三种情况:
     等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态
     同步阻塞:线程在获取 synchronized 同步锁失败(因为同步锁被其他线程占用)。
     其他阻塞:通过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当          sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程重新转入就绪状态。
* 5、死亡状态:一个运行状态的线程完成任务或者其他终止条件发生时(如调用interrupt()方法),该线程就切换到终止状态。

为什么线程执行不直接run()启动执行,而要先start()启动,再run()?
线程启动使用start(),run()只是一个回调方法而已。如果你直接调用run()方法,JVM是不会去创建线程的,run()方法只能用于已有线程中。Java里面创建线程之后必须要调用start方法才能真正的创建一个线程,该方法会调用虚拟机启动一个本地线程,本地线程的创建会调用当前系统创建线程的方法进行创建,并且线程被执行的时候会回调 run方法进行业务逻辑的处理。

thread.join() 控制线程执行顺序:
 t.join()方法只会使主线程进入等待池并等待t线程执行完毕后才会被唤醒,不影响同一时刻处在运行状态的其他线程。

Yield方法
thread.yield():暂停当前正在执行的线程,并执行其他线程。(可能没有效果)
使用yield()的目的是让具有相同优先级的线程之间能够适当的轮换执行。实际中无法保证yield()达到让步的目的,因为,让步的线程可能被线程调度程序再次选中

线程的执行顺序: 真正取决于CPU调度器(在Java中是JVM来调度),我们无法控制
可控操作:设置线程优先级,maxThread.setPriority(Thread.MAX_PRIORITY); 将线程 maxThread设置为最大值10
-》具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源
-》但是,线程优先级并不能一定保证线程执行的顺序,因为它有很大的随机性,只有抢到CPU资源的线程才会执行,只是优先级高的线程抢占到CPU资源的机会更大,

多执行几遍下面程序,可发现线程max并不总是先执行
public class PriorityDemo {
    public static void main(String[] args) {
        //创建线程max最大
        Thread max = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println("max");
                }
            }
        };
        //创建线程min最小
        Thread min = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println("min");
                }
            }
        };
        //创建线程norm默认
        Thread norm = new Thread() {
            @Override
            public void run() {
                for (int i = 0; i < 20; i++) {
                    System.out.println("norm");
                }
            }
        };
        //将线程max设置为最大值10
        max.setPriority(Thread.MAX_PRIORITY);
        //将线程min设置为最小大值1
        min.setPriority(Thread.MIN_PRIORITY);
        min.start();
        norm.start();
        max.start();
    }
}
线程实例化3种方式
 * 1、继承Thread类,重写run方法
 * 2、实现Runnable接口,实现run方法
 * 3、实现Callable接口,重写call方法
区别:Callable的任务执行后可返回值,Runnable不能;call方法可以抛出异常,run方法不可以
   (当需要让子线程在执行完成以后,提供一个返回值给到当前的主线程,使用Callable接口会非常适用)

通过 Callable 和 Future 创建线程,带返回值的,示例如下:
 * 1)创建 Callable 实现类实例,使用 FutureTask类 来包装该Callable实现类实例对象
 * 2)创建并启动新线程,使用 FutureTask 对象作为 新线程 对象(即子线程)的 target
 * 3)调用 FutureTask 对象的 get() 方法来获得子线程执行 call() 结束后的返回值
public class ImplCallable implements Callable<Integer> {
    public static void main(String[] args) {
        ImplCallable callable = new ImplCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(callable);
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " 的循环变量i的值" + i);
            if (i == 2) {
                new Thread(futureTask, "有返回值的线程").start();
            }
        }
        try {
            System.out.println("子线程的返回值:" + futureTask.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }

    @Override
    public Integer call() {
        int i = 0;
        for (; i < 3; i++) {
            System.out.println(Thread.currentThread().getName() + " " + i);
        }
        return i;
    }
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值