• java-多线程的入门_进阶总结


    多线程

    概述图

    1.概述

    进程:正在执行中的程序,其实时应用程序在内存中运行的那片空间。

    线程:进程中的一个执行单元,负责进程中的程序的运行,一个进程中至少要有一个线程。

    (进程可以理解为是一个QQ程序,QQ运行本身就是一个线程(main),你可以在QQ上做好多事情,每个事情就相当于一个线程)

    一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序

    程序启动了多线程,有什么应用呢?
    可以实现多部分程序同时执行,专业术语称之为 并发

    1.1 并发与并行的区别?

    你吃饭吃到一半,电话来了,你一直到吃完了以后才去接,这就说明你不支持并发也不支持并行
    你吃饭吃到一半,电话来了,你停了下来接了电话,接完后继续吃饭,这说明你支持并发
    你吃饭吃到一半,电话来了,你一边打电话一边吃饭,这说明你支持并行

    并发的关键是你有处理多个任务的能力,不一定要同时。
    并行的关键是你有同时处理多个任务的能力。

            所以我认为它们最关键的点就是:是否是『同时』

    1.2 同步和异步的区别?

    在计算机领域,同步就是指一个进程在执行某个请求的时候,若该请求需要一段时间才能返回信息,那么这个进程将会一直等待下去,
    直到收到返回信息才继续执行下去;异步是指进程不需要一直等下去,而是继续执行下面的操作,不管其他进程的状态。
    当有消息返回时系统会通知进程进行处理,这样可以提高执行的效率

    1.3 多线程的使用可以合理使用cpu的资源

    如果线程过多会导致降低性能。

    CPU处理程序时是通过快速切换完成的,在我们看来好像随机一样。

    举例:金山卫士进行电脑体检的同时,还可以清理垃圾,木马查杀等,这就是多线程

    总结:什么时候使用多线程技术呢?
    当多部分代码需要同时执行时,就需要使用多线程技术。

    1.4 线程的存在解决什么问题?

        多部分代码同时执行的问题。
        传统的单个主线程从头执行到尾的运行安排,当遇到较多的操作次数时,效率偏低
    多线程的弊端:
         开启过多会降低效率,多线程的原理其实是多个执行单元执行一个程序,CPU高速在多个线程之间进行切换,
         当线程个数过多时,CPU切换一个循环的时间过长,相应的降低了程序运行的效率
    多线程的特性

          随机性,因为CPU的快速切换造成的。其他总结:线程有并发性

     

    2.创建线程

    2.1 thread

                1,通过原来的主线程的执行和需求做对比。
                2,需求:在主线程中执行到部分代码多次无法继续执行,如何实现让下面的代码和主线程同时执行。
                3,创建方式,通过查api。Thread类。
                    继承Thread类。
                    3.1 定义一个类继承Thread。
                    3.2 重写run方法。
                    3.3 创建子类对象,就是创建线程对象。
                    3.4 调用start方法,开启线程并让线程执行,同时还会告诉jvm去调用run方法。
                    目的让主线程和自定义线程同时执行。
                   

     class ThreadDemo
                    {
                        public static void main(String[] args)
                        {
                            Ticket t = new Ticket(100);
                            Ticket t1 = new Ticket(10);
    
                            t.start;
                            t1.run();
                        }
                    }
                    class Ticket extends Thread
                    {
                        private int tickets;
                        Ticket( int tickets ){
                            this.tickets = tickets;
                        }
                        public void run(){
                            if ( ticket>0 )
                            {
                                System.out.println(Thread.currentThread().getName()+tickets--);
                            }
                        }
                    }

    为什么要这么做?
    继承Thread类:因为Thread类描述线程事物,具备线程应该有功能。
    那为什么不只讲创建Thread类的对象呢?
    Thread t1 = new Thread();
    t1.start();//这么做没有错,但是该start调用的时Thread类中的run方法,
    而这个run方法没有做什么事情,更重要的是这个run方法中并没有定义我们需要让线程执行的代码。

    创建线程的目的是什么?

    是为了建立单独的执行路径,让多部分代码实现同时执行。
    也就是说线程创建并执行需要给定的代码(线程的任务)。
    对于之前所讲的主线程,它的任务定义在main函数中。
    自定义线程需要执行的任务都定义在run方法中。
    Thread类中的run方法内部的任务并不是我们所需要,只要重写这个run方法,
    既然Thread类已经定义了线程任务的位置,只要在位置中定义任务代码即可。
    所以进行了重写run方法动作。

    多线程执行时,在栈内存中,其实每一个执行线程都有一片自己所属的栈内存空间。
    进行方法的压栈和弹栈。

    当执行线程的任务结束了,线程自动在栈内存中释放了。
    但是当所有的执行线程都结束了,那么进程就结束了。

    2.2 体会调用run和调用start的区别?

           调用run方法仅仅是主线程执行run方法中的代码;调用start方法是开启线程,让新建的线程与主线程同时执行

    其他总结:注意在这个程序中,run()方法是中没有循环的;没有创建线程就不能执行;一个线程只有调用了start()方法才能开始线程,

    3.创建线程thread的原理

                   1,为什么要继承Thread,

                    因为Thread类描述了线程的任务存在的位置:run方法。
                2,为什么要覆盖run方法。
                    为了定义线程需要运行的任务代码。
                3,为什么调用start方法。
                    为了开启线程,并运行子类的run方法的任务代码。
                4,创建线程的目的是什么?
                    为了执行线程任务,而是任务都定义在run方法中。
                    run方法结束了,线程任务就是结束了,线程也就是结束了。
                5,线程任务:每一个线程都有自己执行的代码,主线程的代码都定义在主函数中,自定义线程的代码都定义在run方法中。

    3.1 多线程的运行原理

    1,疑问,如果多部分代码同时执行,那么在栈内存方法执行是怎么完成的呢?
    其实是,每一个线程在栈内存中都有自己的栈内存空间,在自己的栈空间中
    进行压栈和弹栈。
    2,主线程结束,程序就结束吗?
    主线程结束,如果其他线程还在执行,进程是不会结束了,当所有的线程都结束了,进程才会结束。
    3,画出多线程的运行内存图。

     

     3.2 异常在多线程中的特点

    throw异常是可以结束功能。
    如果功能是线程的任务,那么功能结束就代表着线程结束。
    而且异常信息中会体现出该异常在哪个线程上发生。


                Exception in thread "Thread-1" java.lang.ArrayIndexOutOfBoundsException: 4
                    at Demo.run(ThreadDemo.java:68)

    4,创建线程实现Runnable接口

                 1,通过查阅api得到第二种方式         

                2,不明确原理的情况下,先应用。

                3,方式二步骤:
                    3.1,定义类实现Runnable接口:
                    3.2,覆盖接口中的run方法。
                    3.3,创建Thread类的对象:
                    3.4,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
                    3.5,调用Thread类的start方法开启线程。
                    到这里,你就应该可以使用方式二创建线程了。动手自己通过方式二完成线程的创建并运行。★★★★★
                4,方式二的原理:★★★
                    4.1,定义类实现Runnable接口:避免了继承Thread类的单继承局限性。
                    4.2,覆盖接口中的run方法。将线程任务代码定义到run方法中。
                    4.3,创建Thread类的对象:只有创建Thread类的对象才可以创建线程。
                    4.4,将Runnable接口的子类对象作为参数传递给Thread类的构造函数。
                        因为线程已被封装到Runnable接口的run方法中,而这个run方法所属于Runnable接口的子类对象,
                        所以将这个子类对象作为参数传递给Thread的构造函数,这样,线程对象创建时就可以明确要运行的线程的任务。
                    4.5,调用Thread类的start方法开启线程。
                5,方式二和方式一的区别:【面试题】★★★★★
                    5.1 第二种方式实现Runnable接口避免了单继承的局限性,所以较为常用。
                    5.2 实现Runnable接口的方式,更加的符合面向对象,线程分为两部分,一部分线程对象,一部分线程任务。
                        继承Thread类:线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象,既是线程对象,有又有线程任务
                        实现runnable接口:将线程任务单独分离出来封装成对象,类型就是Runnable接口类型。
                        Runnable接口对线程对象和线程任务进行解耦。
              

               查询到的源码解释

    class Thread{
    
        private Runnable target;
    
        Thread(Runnable target)
        {
            this.target = target;
        }
        public void run() {
            if (target != null) {
                target.run();
            }
        }
        public void start()
        {
            run();
        }
    }
    
    Runnable d = new Demo();
    Thread t = new Thread(d);
    t.start();

    5,多线程的售票案例

    案例:售票的例子。

    售票的动作需要同时执行,所以使用多线程技术。

    class Tickets implements Runnable{
        private int tickets = 100;
    
        public void run() {
            while(tickets > 0)
            System.out.println(Thread.currentThread().getName()+"  "+tickets--);
            
        } 
        
        public static void main(String[] args) {
            Tickets t = new Tickets();
            Thread t1 = new Thread(t,"t1");
            Thread t2 = new Thread(t,"t2");
            Thread t3 = new Thread(t,"t3");
            Thread t4 = new Thread(t,"t4");
            t1.start();
            t2.start();
            t3.start();
            t4.start();
            
        }

    6,线程的状态

    1,线程运行是有多种状态的

     1.1 创建  new Thread类或者其子类对象。

      1.2 运行 start().  具备者CPU的执行资格和CPU的执行权。
      1.3 冻结 释放了CPU的执行资格和CPU的执行权。比如执行到sleep(time)  wait() 导致线程冻结。
      1.4 临时阻塞状态: 具备者CPU的执行资格,不具备CPU的执行权。
      1.5 消亡:线程结束。run方法结束。  

    举例:老师给几个小朋友喂饭

                    

     

    8 多线程的安全问题         

    分析上面的案例 (多个线程共享一个资源

    我下倒水呢,刚到一半(if语句判断后,不去打印了),我离开了(不去打印了),下一个(另一个线程)来了

    把我里面水喝了,(tickets--),我回来了,我这时不用在判断了,直接打印tickets--(但是ticktes 已经减了一次了)不合理的

    案例:售票的示例。

                1,通过案例让安全问题发生。
                2,安全问题产生的原因:
                    2.1 线程任务中有共享的数据。
                    2.2 线程任务中有多条操作共享数据的代码
                    当线程在操作共享数据时,其他线程参与了运算导致了数据的错误(安全问题)
                3,安全问题的解决思路:
                    保证在线程执行共享数据代码时,其他线程无法参与运算。
                4,安全问题的解决代码体现:
                    同步代码块。synchronized(obj){需要被同步的代码}  【举例:火车上的卫生间】
                5,同步的好处:
                    解决了安全问题。
                6,同步的弊端:
                    降低了程序的性能。
                7,同步的前提:
                    必须保证多个线程在同步中使用的是同一个锁
                    解决了什么问题?
                    当多线程安全问题发生时,加入了同步后,
                    问题依旧,就要通过这个同步的前提来判断同步是否写正确。

    其他总结:同步里面的对象相当于火车上卫生间的锁,火车上就你自己一个人(一个线程),你去卫生间不用锁,3号卫生间的锁和4号卫生间的锁是没有关系的.因此多个线程要用同一个锁.



    使用同步解决上面的问题

    class Ticket implements Runnable
    {
        //1,描述票的数量。
        private int tickets = 100;
        //2,售票的动作,这个动作需要被多线程执行,那就是线程任务代码。需要定义run方法中。
        //线程任务中通常都有循环结构。
        private Object obj = new Object();
        public void run()
        {
            while(true)
            {
                synchronized(obj)
                {
                    if(tickets>0)
                    {
                        //要让线程在这里稍停,模拟问题的发生。sleep  看到了0 -1 -2 错误的数据,这就是传说中的多线程安全问题。
                        try{Thread.sleep(1);}catch(InterruptedException e){/*未写处理方式,后面讲*/}
    
                        System.out.println(Thread.currentThread().getName()+"....."+tickets--);//打印线程名称。
                    }
                }
            }
        }
    }
    class ThreadDemo3 
    {
        public static void main(String[] args) 
        {
            //1,创建Runnable接口的子类对象。
            Ticket t = new Ticket();
    
            //2,创建四个线程对象。并将Runnable接口的子类对象作为参数传递给Thread的构造函数。
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            //3,开启四个线程。
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }

    其他总结:同步里面的对象相当于火车上卫生间的锁,火车上就你自己一个人(一个线程),你去卫生间不用锁,3号卫生间的锁和4号卫生间的锁是没有关系的.因此多个线程要用同一个锁.

    习题

    //写出下面代码执行的结果(此题需写出分析过程)
    
    class MyThread extends Thread{
        public void run(){
            try {
                Thread.currentThread().sleep(3000);
            } catch (InterruptedException e) {
            }
            System.out.println("MyThread running");
        }
    }
    
    public class ThreadTest{
        public static void main(String argv[]) {
            MyThread t = new MyThread();
            t.run();
            t.start();
            System.out.println("Thread Test");
          }
    }

    执行结果:
        MyThread running
        Thread Test
        MyThread running
    过程分析:
        1,MyThread t = new MyThread();
            创建线程对象,但并未开启。
        2,t.run();
            是主线程执行t对象的run方法。
            在run方法中,主线程执行到sleep(3000);主线程就处于冻结状态,
            3秒后,主线程恢复到运行状态,就打印了"MyThread running"。
        3,t.start();
            主线程执行该句,开启了一个新的线程Thread-0。
            这时程序中有两个线程,执行出现了两种情况:
                情况一:主线程执行完t.start()方法后,继续执行打印语句,输出"Thread Test"。
                        然后新线程Thread-0执行run方法,并sleep(3000)3秒,3秒后,执行"MyThread running"
                情况二:主线程执行完t.start()方法后,开启了一个新线程Thread-0,
                        该新线程Thread-0就开始执行,调用run方法,sleep(3000)3秒,这时主线程开始执行,
                        打印了"Thread Test",主线程结束,3秒后,新线程Thread-0打印"MyThread running"。

    //两个客户到一个银行去存钱,每个客户一次存100,存3次。
    //问题:该程序是否有安全问题,如果有,写出分析过程,并定义解决方案。

    class Bank
    {
        private int sum;
        public void add(int num)
        {
            sum = sum + num;
            System.out.println("sum="+sum);//每存一次,看到银行金额变化。
        }
    }
    class Consumer implements Runnable
    {
        private Bank b = new Bank();
        public void run()
        {
            for(int x=0 ; x<3; x++)
            {
                b.add(100);//一次存100.循环3次,
            }
        }
    }
    class Test
    {
        public static void main(String[] args)
        {
            Consumer c = new Consumer();
            Thread t1 = new Thread(c);
            Thread t2 = new Thread(c);
            t1.start();
            t2.start();
    
        }
    }

     

    分析依据:多线程安全问题产生的原因:
    1,共享数据,b对象中的sum。
    2,操作共享数据的多次运算。
    sum = sum + num;
    System.out.println("sum="+sum);


    作者:8亩田
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接.

    本文如对您有帮助,还请多帮 【推荐】 下此文。
    如果喜欢我的文章,请关注我的公众号
    如果有疑问,请下面留言

    学而不思则罔 思而不学则殆
  • 相关阅读:
    ASP.NET学习笔记(1)
    vs2005新建项目中没有ASP.NET WEB应用程序
    IE无法安装Activex控件
    【Android】SDK工具学习
    【英语】Bingo口语笔记(22)
    【Python】实践笔记
    【学习】纪录片笔记
    【英语】Bingo口语笔记(20)
    【英文】20141027 生词
    【英文】Bingo口语笔记(18)
  • 原文地址:https://www.cnblogs.com/liu-wang/p/6067525.html
Copyright © 2020-2023  润新知