美文网首页
多线程入门

多线程入门

作者: 若兮缘 | 来源:发表于2018-12-26 22:18 被阅读49次

    线程相关概念

    线程

    是依赖于进程的执行绪(执行路径/控制单元),是程序使用CPU的基本单位

    进程

    进程是指可执行程序并存放在计算机存储器的一个指令序列,它是一个动态执行的过程。
    表示当前正在执行的程序,代表一个应用程序在内存中的执行区域。

    单进程

    一个进程中,只有一个线程执行

    多进程

    同一时间段内执行多个任务,同一时刻只能执行一个任务,如Windows为代表的操作系统。
    多进程并不提高某个程序的执行速度,仅仅是提高了CPU的使用率。真正的多进程执行是指多核同时计算。

    线程两种调度模型

    分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
    抢占调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个。(线程随机性)
    Java使用的为抢占调度模型

    线程并行与并发

    线程并行:正常的多线程执行就是线程并行。即逻辑上同一时间同时运行
    线程并发(异常):由于线程抢占而不应出现的某一时刻的线程及相关数据状态。如并发修改异常的产生

    JVM的多线程

    JVM启动至少启动了垃圾回收线程和主线程,所以是多线程的。
    Java命令会启动java虚拟机,启动JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个“主线程”, 然后主线程去调用某个类的main方法。所以main方法运行在主线程中

    多个程序同时运行

    多个程序都是通过cpu来运行的,cpu的执行时间被分为多个等大小的时间片,比如是1ms, 对于cpu来说程序实际是轮流运行的,多个程序是通过随机的抢占cpu时间片来运行,因为时间间隔非常短,所以感官上是多个程序同时运行的,这个称之为cpu时间片的轮转

    线程创建

    1. 创建一个Thread类、或者一个Thread子类的对象
    2. 创建一个实现Runnable接口的类的对象
    继承Thread类

    实现:自定义线程类继承Thread类,重写run方法,run方法内为该线程执行代码。将其理解为其他线程的main方法,即该线程的执行入口。
    使用:创建线程对象,开启线程,即调用start方法,该方法会自动调用这个线程的run方法

    实现Runnable接口

    实现:自定义Runnable的子类(非线程类),重写run方法,run方法内为该类对象所在线程的执行代码。同样可将其理解为其他线程的main方法,即该线程的执行入口。
    使用:创建Runnable的子类对象,使用Runnable的子类对象创建线程对象,开启线程,即调用start方法,该方法会自动调用这个线程的run方法。

    主要区别
    • 继承Thread类
      — 继承Thread类会导致每一个线程对象中都存储一份属性数据,无法在多个线程中共享该数据。如果加上静态,虽然实现了共享但是生命周期过长。
      — 如果一个类明确了自己的父类,那么它就不可以再继承Thread。因为java不允许类的多继承。
    • 实现Runnable接口
      — 将线程与运行的业务逻辑分离,可以让多个线程共享业务逻辑中的数据。
      — 可以让业务类不再继承Thread而专注于业务继承其他类,避免了单继承的局限性。
    火车站卖票示例

    1.总共5张火车票,通过Thread方式,3个窗口卖出15张,因为成员变量没有共享,票数加static修饰后正常

    class MyThread extends Thread{
    
        private static int ticketCount = 5;//一共5张火车票 
        private String name;//窗口,线程名称
        
        public MyThread(String name) {
            this.name = name;
        }
        @Override
        public void run() {
            while(ticketCount > 0){
                ticketCount--;//如果还有票,就卖掉一张 
                System.out.println(name + "卖了一张票,剩余票数为:"+ticketCount);
            }
        }
    }
    
    public class TicketsThread {
    
        public static void main(String[] args) {
            //创建3个线程,模拟3个窗口卖票
            MyThread mt1 = new MyThread("窗口1");
            MyThread mt2 = new MyThread("窗口2");
            MyThread mt3 = new MyThread("窗口3");
            //启动线程,开始卖票
            mt1.start();
            mt2.start();
            mt3.start();
        }
    }
    

    2.总共5张火车票,通过实现Runnable接口方式,3个窗口卖出5张票,因为共享了同一个对象的成员变量

    class MyRunnable implements Runnable{
    
        private int ticketCount = 5;//一共5张火车票 
        
        @Override
        public void run() {
            while(ticketCount > 0){
                ticketCount--;//如果还有票,就卖掉一张 
                System.out.println(Thread.currentThread().getName() + "卖了一张票,剩余票数为:"+ticketCount);
            }
        }
    }
    
    public class TicketsRunnable {
    
        public static void main(String[] args) {
            //创建3个线程,模拟3个窗口卖票
            MyRunnable mt = new MyRunnable();
            Thread th1 = new Thread(mt, "窗口1");
            Thread th2 = new Thread(mt, "窗口2");
            Thread th3 = new Thread(mt, "窗口3");
            //启动线程,开始卖票
            th1.start();
            th2.start();
            th3.start();
            /*窗口3卖了一张票,剩余票数为:2
              窗口1卖了一张票,剩余票数为:2
              窗口2卖了一张票,剩余票数为:2
              窗口1卖了一张票,剩余票数为:0
              窗口3卖了一张票,剩余票数为:1
            */
        }
    }
    

    以上执行都存在线程安全问题,票数的显示与预期效果可能不一致,后续我们通过线程同步来解决这个问题

    总结
    1. Runnable方式可以避免Thread方式由于Java单继承特性带来的缺陷
    2. Runnable的代码可以被多个线程(Thread实例)共享,适合于多个线程处理同一资源的情况
    使用匿名内部类创建线程
    public class ThreadDemo {
        
        public static void main(String[] args) {
            method();
            //method2();
        }
        //使用匿名内部类第一种方式
        public static void method(){
            //继承Thread类
            Thread thread = new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println(this.getName()+i);
                    }
                }
            };
            thread.setName("糖糖");
            thread.start();
            //直接start
            new Thread(){
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println(this.getName()+i);
                    }
                }
            }.start();
        }
        //使用匿名内部类第二种方式
        public static void method2(){
            //实现Runnable接口
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println(Thread.currentThread().getName()+i);
                    }   
                }
            }, "糖糖");
            thread.start();
            //直接start
            new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 20; i++) {
                        System.out.println(Thread.currentThread().getName()+i);
                    }
                }
            },"浅浅").start(); 
        }    
    }
    

    相关文章

      网友评论

          本文标题:多线程入门

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