当前位置 博文首页 > 邱天的henry的博客:关于多线程的简单概念(JAVA)

    邱天的henry的博客:关于多线程的简单概念(JAVA)

    作者:[db:作者] 时间:2021-07-19 13:17

    1.并发与并行

    • 并发:指两个或多个事件在同一个时间段内发生。
    • 并行:指两个或多个事件在同一时刻发生(同时发生)。

    在这里插入图片描述
    在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多个程序同时运行,这在单 CPU 系统中,每
    一时刻只能有一道程序执行,即微观上这些程序是分时的交替运行,只不过是给人的感觉是同时运行,那是因为分
    时交替运行的时间是非常短的。
    而在多个 CPU 系统中,则这些可以并发执行的程序便可以分配到多个处理器上(CPU),实现多任务并行执行,
    即利用每个处理器来处理一个可以并发执行的程序,这样多个程序便可以同时执行。目前电脑市场上说的多核
    CPU,便是多核处理器,核 越多,并行处理的程序越多,能大大的提高电脑运行的效率。

    注意:单核处理器的计算机肯定是不能并行的处理多个任务的,只能是多个任务在单个CPU上并发运行。同
    理,线程也是一样的,从宏观角度上理解线程是并行运行的,但是从微观角度上分析却是串行运行的,即一个
    线程一个线程的去运行,当系统只有一个CPU时,线程会以某种顺序执行多个线程,我们把这种情况称之为
    线程调度。

    2.线程与进程

    • 进程:是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间,一个应用程序可以同时运行多
      个进程;进程也是程序的一次执行过程,是系统运行程序的基本单位;系统运行一个程序即是一个进程从创
      建、运行到消亡的过程。
    • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程
      中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

    简而言之:一个程序运行后至少有一个进程,一个进程中可以包含多个线程

    我们可以再电脑底部任务栏,右键----->打开任务管理器,可以查看当前任务的进程:

    进程

    在这里插入图片描述
    线程
    在这里插入图片描述
    线程调度:

    • 分时调度
      所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

    • 抢占式调度
      优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

      1.设置线程的优先级
      在这里插入图片描述
      2.抢占式调度详解
      大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我
      们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是
      在同时运行,”感觉这些软件好像在同一时刻运行着“。
      实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而
      言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是
      在同一时刻运行。 其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的
      使用率更高。

    在这里插入图片描述
    3.创建新线程
    Java语言内置了多线程支持。当Java程序启动的时候,实际上是启动了一个JVM进程,然后,JVM启动主线程来执行main()方法。在main()方法中,我们又可以启动其他线程。

    要创建一个新线程非常容易,我们需要实例化一个Thread实例,然后调用它的start()方法:

    public class Main {
        public static void main(String[] args) {
            Thread t = new Thread();
            t.start(); // 启动新线程
        }
    }
    

    但是这个线程启动后实际上什么也不做就立刻结束了。我们希望新线程能执行指定的代码,有以下几种方法:

    方法一:从Thread派生一个自定义类,然后覆写run()方法:

    public class Main {
        public static void main(String[] args) {
            Thread t = new MyThread();
            t.start(); // 启动新线程
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            System.out.println("start new thread!");
        }
    }
    
    

    执行上述代码,注意到start()方法会在内部自动调用实例的run()方法。

    方法二:创建Thread实例时,传入一个Runnable实例:

    public class Main {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable());
            t.start(); // 启动新线程
        }
    }
    
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            System.out.println("start new thread!");
        }
    }
    
    

    4.线程的5中状态
    线程从创建、运行到结束总是处于下面五个状态之一:新建状态、就绪状态、运行状态、阻塞状态及死亡状态。
    在这里插入图片描述
    1.新建状态(New):

    当用new操作符创建一个线程时, 例如new Thread?,线程还没有开始运行,此时线程处在新建状态。 当一个线程处于新生状态时,程序还没有开始运行线程中的代码

    2.就绪状态(Runnable)

    一个新创建的线程并不自动开始运行,要执行线程,必须调用线程的start()方法。当线程对象调用start()方法即启动了线程,start()方法创建线程运行的系统资源,并调度线程运行run()方法。当start()方法返回后,线程就处于就绪状态。

    处于就绪状态的线程并不一定立即运行run()方法,线程还必须同其他线程竞争CPU时间,只有获得CPU时间才可以运行线程。因为在单CPU的计算机系统中,不可能同时运行多个线程,一个时刻仅有一个线程处于运行状态。因此此时可能有多个线程处于就绪状态。对多个处于就绪状态的线程是由Java运行时系统的线程调度程序(thread scheduler)来调度的。

    3.运行状态(Running)

    当线程获得CPU时间后,它才进入运行状态,真正开始执行run()方法.

    4. 阻塞状态(Blocked)

    线程运行过程中,可能由于各种原因进入阻塞状态:

    线程通过调用sleep方法进入睡眠状态;
    线程调用一个在I/O上被阻塞的操作,即该操作在输入输出操作完成之前不会返回到它的调用者;
    线程试图得到一个锁,而该锁正被其他线程持有;
    线程在等待某个触发条件;

    所谓阻塞状态是正在运行的线程没有运行结束,暂时让出CPU,这时其他处于就绪状态的线程就可以获得CPU时间,进入运行状态。

    5. 死亡状态(Dead)

    有两个原因会导致线程死亡:

    run方法正常退出而自然死亡,
    个未捕获的异常终止了run方法而使线程猝死。
    为了确定线程在当前是否存活着(就是要么是可运行的,要么是被阻塞了),需要使用isAlive方法。如果是可运行或被阻塞,这个方法返回true; 如果线程仍旧是new状态且不是可运行的, 或者线程死亡了,则返回false.

    6.多线程的一个卖票实例

    题目:5个售票点售卖一共30张票,每卖完一张票停1s,如果票都卖完了显示票卖完了
    程序分析:
    (1)票数要使用同一个静态值
    (2)为保证不会出现卖出同一个票数,要java多线程同步锁。
    设计思路:
    (1)创建一个站台类Station,继承Thread,重写run方法,在run方法里面执行售票操作!售票要使用同步锁:即有一个站台卖这张票时,其他站台要等这张票卖完!
    (2)创建主方法调用类

    (一)创建一个站台类,继承Thread

    class Ticket extends Thread{
        //定义一个静态的变量
        public static int ticket=30;
        //用来标识票卖完语句只输出一次
        public static boolean flag=true;
        //定义一个锁变量
        public static String  str="aa";   //提取出来提高可维护性,同时定义为static静态变量,使得str是公共的
        //如果不定义成static静态,则两个线程各自有各自的str,那么大家竞争的就不是同一个资源
        public Ticket(String name){  //初始化线程名称
            super(name);
        }
        @Override
        public void run() {
            //判断票是否卖完
            while (ticket>0){
                synchronized (str){  // 这个很重要,必须使用一个锁,
                    // 进去的人会把钥匙拿在手上,出来后才把钥匙拿让出来
                    if (ticket>0){
                        ticket--;
                        System.out.println(Thread.currentThread().getName()+"售卖第"+(30-ticket)+"票");
                    }else {
                        if (flag==true){
                            System.out.println("票已经卖完了");
                            flag=false;
                        }
                    }
                    //卖完一张票休息1s
                    try {
                        sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    

    (二)创建主方法调用类

    public class ThreadDemo4 {
        public static void main(String[] args) {
            Ticket ticket1 = new Ticket("1号窗口");
            Ticket ticket2 = new Ticket("2号窗口");
            Ticket ticket3 = new Ticket("3号窗口");
            Ticket ticket4 = new Ticket("4号窗口");
            Ticket ticket5 = new Ticket("5号窗口");
    
            ticket1.start();
            ticket2.start();
            ticket3.start();
            ticket4.start();
            ticket5.start();
        }
    }
    
    cs