美文网首页
Java多线程基础学习

Java多线程基础学习

作者: 学习也是一種潮流 | 来源:发表于2020-10-17 12:58 被阅读0次

    Java多线程基础

    1.多线程简介

    在了解多线程之前我们要先知道什么是进程和线程:

    • 进程:进程是系统进行调度和分配资源的基本单位,通俗的讲就是一个正在运行和后台运行的程序。我们可以通过任务管理器看到系统有哪些进程在运行。
    • 线程: 线程是CPU调度的最小单位,一个进程可能由多个或一个线程组成。比如我们在看视频是同时还能发弹幕。

    核心概念:

    • 线程就是独立的执行路径
    • 在Java程序运行时,即使我们自己没有创建线程,JVM也会创建多个线程,如主线程(main())、GC线程。
    • main()被称为主线程,是正程序的入口,用于执行整个程序
    • 在一个线程中,如果开辟了多个线程,线程的运行是由调度器安排调度的,调度器是与操作系统紧密相关的。
    • 对于同一份资源操作时,会出现资源抢夺问题,需要加入并发控制。
    • 线程会带来额外的开销,如CPU调度时间、并发控制开销等。
    • 每个线程在自己的工作内存中交互,内存控制不当会造成数据不一致问题。

    2.线程实现方法

    创建线程有多种方式,最基本的三种: Thread类、Runnable接口、Callable接口

    1.通过继承Thread类:

    重写Thread类中的run(),run()方法中执行我们自己的逻辑

    package com.zw.tread.base;
    
    /**
     * @author zhouwei
     * @version 1.00
     * @className CreateThreadTest
     * @describe 创建线程测试类
     * @since 2020/4/19
     */
    public class ThreadTest extends Thread {
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("我是创建的线程"+ Thread.currentThread().getName()+ "--->i==" + i);
            }
        }
        public static void main(String[] args) {
         // 创建线程 --- 线程创建
         ThreadTest threadTest = new ThreadTest();
         threadTest.setName("superMan");
         // 线程进入就绪状态
         threadTest.start();
         //主线程中的for循环
        for (int n = 0; n < 5; n++) {
            System.out.println("我是主线程"+"--->n==" + n);
        }
        }
    }
    
    

    运行结果如下图,可以看出线程之间是交替执行的,每次执行结果可能都不一样,线程的运行是由调度器安排调度的,并且是被CPU轮流执行的:


    image.png

    2.通过实现Runnable接口创建线程

    package com.zw.tread.base;
    
    /**
     * @author zhouwei
     * @version 1.00
     * @className RunnableTest
     * @describe Runnable接口创建线程
     * @since 2020/4/19
     */
    public class RunnableTest  implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 5; i++) {
                System.out.println("我是创建的线程"+ Thread.currentThread().getName()+ "--->i==" + i);
            }
        }
    
        public static void main(String[] args) {
            // 创建Runnable接口实现类
            RunnableTest runnableTest = new RunnableTest();
            // 创建线程
            Thread thread = new Thread(runnableTest);
            // 调用线程
            thread.start();
            // 执行主线程for循环
            for (int n = 0; n < 5; n++) {
                System.out.println("我是主线程"+"--->n==" + n);
            }
        }
    }
    
    

    运行结果:


    image.png

    3.线程状态

    下面简单的介绍一下线程的五种状态:

    • 创建状态(new): 线程被创建后,就进入了创建状态。例如:Thread thread = new Thread()。
    • 就绪状态(Runnable): 就绪状态也被称为“可执行状态”。线程对象被创建后,调用其start(),该线程就进入就绪状态了,例如: thread.start()。线程进入就绪状态后就随时可能被CPU调用。
    • 运行状态(Running):当线程获得CPU资源后,线程就进入运行状态,进入运行状态后,才会执行我们所写的逻辑代码。这里需要注意线程只能从就绪状态进入运行状态
    • 阻塞状态(Blocked):阻塞状态是线程由于某种原因失去了CPU资源使用权,暂时停止运行。阻塞情况分为三种:
      • 等待阻塞:通过调用线程wait()方法,让线程等待某种工作的完成在继续执行。
      • 同步阻塞:线程获取synchronized同步锁失败,线程会进入同步阻塞状态。
      • 其他阻塞:其他阻塞产生的情况比较多,通过调用线程的sleep()、join()方法或者发出IO请求时,线程会进入阻塞状态,只有当sleep()超时、join等待线程终止或超时、IO处理完毕是,线程重新进入就绪状态。
    • 死亡状态(Dead):线程执行完成或因为异常退出run()方法。
    image.png image.png

    4.线程同步

    1.线程并发问题:
    在多线程编程中,有些共享数据是不允许多个线程同时访问的,如果同时访问可能会出现数据同步问题。
    下面通过买票例子,展现多线程并发问题。

    /**
     * @author zhouwei
     * @version 1.00
     * @className ThreadDemo01
     * @describe 线程并发问题,例子买票功能
     * @since 2020/4/20
     */
    public class ThreadDemo01 implements Runnable {
        /**
         * 门票总个数
         */
        private int ticketNum = 30;
    
        @Override
        public void run() {
            while (true){
                if(ticketNum <= 0){
                    System.out.println("票买完了。。。");
                    break;
                }
                System.out.println("线程" + Thread.currentThread().getName() + "买了一张票,剩余"+ ticketNum--+"张");
                try {
                    // 模仿延迟
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public static void main(String[] args) {
            ThreadDemo01 demo01 = new ThreadDemo01();
            new Thread(demo01,"AA").start();
            new Thread(demo01,"BB").start();
            new Thread(demo01,"CC").start();
        }
    }
    

    运行结果如果下图,从我们可以看出多线程并发问题,剩余票数出现混乱问题:


    image.png

    5.线程通讯问题

    简介:线程与线程之间不是相互独立的个体,它们彼此之间需要相互通信和协作,最典型的例子就是生产者-消费者问题:当队列满时,生产者需要等待队列有空间才能继续往里面放入商品,而在等待的期间内,生产者必须释放对临界资源(即队列)的占用权。因为生产者如果不释放对临界资源的占用权,那么消费者就无法消费队列中的商品,就不会让队列有空间,那么生产者就会一直无限等待下去。因此一般情况下,当队列满时,会让生产者交出对临界资源的占用权,并进入挂起状态。然后等待消费者消费了商品,然后消费者通知生产者队列有空间了。同样地,当队列空时,消费者也必须等待,等待生产者通知它队列中有商品了。这种互相通信的过程就是线程间的协作。

    下面介绍通过Object类中的wait()和notify方法实现线程通信:

    例子:定义一个仓库类(Depot),有两个方法一个是生产商品(produce),一个是消费商品(consume),启动两个线程,一个线程值生产,一个线程值消费。

    public class WaitAndNotifyTest03 {
    
    
        static class Depot {
            /**
             * 商品数量
             */
            private Integer goodNum = 0;
    
            private synchronized void produce(){
                    // 如果商品数量大于0,则等待
                    while (goodNum > 0){
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int  num = new Double(Math.random() * 100).intValue();
                    goodNum =+ num;
                    System.out.println("我生产了商品" + num + "个");
    
                    // 通知消费者消费
                    notify();
    
            }
    
            private synchronized void consume(){
                    // 如果商品数量大于0,则等待
                    while (goodNum <= 0){
                        try {
                            this.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    int n = goodNum;
                    goodNum =- goodNum;
                    // 通知消费者消费
                    System.out.println("我消费了商品" + n + "个");
                    notify();
            }
        }
    
        public static void main(String[] args) {
            Depot depot = new Depot();
            new Thread(() ->{
                for(int i=0;i<10;i++){
                    depot.produce();
                }
            }).start();
            new Thread(()->{
                for(int i=0;i<10;i++){
                    depot.consume();
                }
            }).start();
        }
    }
    

    6.线程其他方法

    下面介绍一下Thread类的一些常用方法:

    1. setName(String): 该方法可以给线程设置名称,方便查询
    2. setPriority(Integer) : 该方法可以给线程设置优先级,但是线程的执行顺序还是有cpu决定的。
    3. setDeamon(boolean): 该方法设置线程是否由后台运行,false表示是,true表示不是。
    4. interrupt(): 中断线程,并不会真正中断线程,只是修改中断状态
    5. isInterrupted(): 获取线程是否被中断
    6. Interrupted(): 获取线程是否被中断,然后清除状态
    7. supspend()暂停、resume()恢复、stop()停止,这些方法已经过期不建议使用,原因如 supspend()调用这个方法线程并不会释放资源,而是占用资源进入休眠状态,可能会引发死锁。stop()方法同样没有给线程释放资源的机会就结束线程了。
    8. sleep(Integer):线程休眠,并不会释放锁
    9. yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态方法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入到就绪状态。即让当前线程暂停一下,让系统的线程调度器重新调度一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。
    10. join():A线程调用B线程的join()方法,将会使A等待B执行,直到B线程终止。如果传入time参数,将会使A等待B执行time的时间,如果time时间到达,将会切换进A线程,继续执行A线程。

    相关文章

      网友评论

          本文标题:Java多线程基础学习

          本文链接:https://www.haomeiwen.com/subject/qxkubhtx.html