美文网首页Java架构师专题
java多线程系列(1)入门和基础

java多线程系列(1)入门和基础

作者: 愚公要移山 | 来源:发表于2019-08-16 16:53 被阅读0次

    多线程这块一直是面试的重点,也是开发当中的重点,于是下决定把这一块的内容吃透它。整个系列的基础文章最少就在60篇以上,因为这块的知识看一两篇确实感觉没什么作用,需要有一个体系的才好。这也是第一篇文章。点到为止。

    一、认识线程

    1、概念

    什么是线程呢?

    线程是进程划分成的更小的运行单位。就好比电脑QQ是一个进程,里面还有各种子模块,比如QQ空间,个性皮肤等子功能。

    这里出现了另外一个名词进程。

    进程是系统运行程序的基本单位。就好比是一个个应用程序QQ、微信等等。

    看概念确实是一脸懵逼,举个例子就明白了。我们打开电脑的任务管理器,会发现上面就有进程,点击这个进程我们就能看到一个个应用程序,这就是进程。

    1-进程与线程.png

    那什么是线程呢?不知道我们注意到了没有,每一个进程最左边都有一个小箭头>,我们打开来看看:

    2-进程与线程.png

    这些小的模块就好比是一个个线程。有了这个印象我们重新来认识一下线程和进程就容易多了。

    线程:

    线程是一个比进程小的执行单位,也被称为轻量级进程。一个进程可以产生多个线程。多个线程共享同一块内存和系统资源,CPU在这多个线程间来回切换去执行。

    进程:

    进程是系统运行程序的基本单位,也就是一个程序。系统运行一个程序即是一个进程从创建,运行到消亡的过程。

    有了对线程的基本认识接下来我们就可以去理解一下,这一系列文章常用到的一些概念了。

    2、并行与并发

    其实他们俩区分很容易区分。

    并发是多个任务交替使用CPU,同一时刻还是只有一个任务在跑,并行是多个任务同时跑。举个例子就明白了。

    桌子上有三个馒头,每一时刻,小明只能咬一个馒头。

    桌子上有三个馒头,每一时刻,小明、小红、小华三个人同时咬三个馒头。

    明白了吧。

    3、同步和异步

    这两个名词我们在学习的时候经常会遇到,举个例子去理解,

    对于同步:我们去餐厅吃饭,只有一个客户的时候商家比较容易处理,但是当有两个人三个人的时候,这时候就需要排队了,也就是拥塞了,这就是同步。换个例子来说,就是我们请求服务器的时候,必须要等到服务器的反馈我们才能够去做其他的事。

    对于异步:就好比微信聊天,我们只管把信息发送给对方,不管对方有没有回复我们,我们都可以去做其他的事,也就是说执行完函数之后,不必等到反馈就可以去做其他的事。

    4、死锁

    和操作系统里面的死锁意思一样,也就是说多个人同时竞争一个资源,

    例子一:好比多个男孩追求同一个女孩,这时候女孩就不知道该嫁给谁了。

    例子二:我们去图书馆借书,发现这本书被借走了,我们只能等到那个人把书还到图书馆才可以看。

    5、原子变量与原子操作

    所谓原子操作,就是“不可中断的一个或一系列操作”。就好比你高考考试的时候,就算天塌下来也要把卷子做完。再急的事也不能抢夺他的优先权。

    原子变量其实是一个抽象的意思,因为本质上并没有严格意义上的原子变量,但是在这里,我们可以这样理解,原子变量提供原子操作,就好比变量a,多个线程对其操作改变时,每一次只能有一个线程拿到他的执行权进行操作。

    在这里我们基本上列举了一些基本的概念,但其实还有很多,我们在遇到的时候再去分析和理解会比较容易。我们说了这么久的线程,下面我们来认识一下java中的线程。
    二、基础案例

    我们以一个生活中的案例来解释说明,比如我们敲代码的同时还想听音乐。

    java中创建一个线程有两种方式,继承Thread类和实现runnable接口。我们两种都实现一下。

    1、继承Thread类

    在这里我们定义两个线程

    public class ThreadTest01 extends  Thread{
        @Override
        public void run() {
            for (int i=0;i<10;i++)
                System.out.println("听音乐");
        }
    }
    

    还有一个线程可以敲代码

    public class ThreadTest02 extends  Thread{
        @Override
        public void run() {
            for (int i=0;i<10;i++)
                System.out.println("敲代码");
        }
    }
    

    最后我们就可以一边敲代码一边听音乐了

    public class ThreadTest {
        public static void main(String[] args) {
            ThreadTest01 thread1=new ThreadTest01();
            ThreadTest02 thread2=new ThreadTest02();
            thread1.start();
            thread2.start();
        }
    }
    

    我们运行一边会发现,敲代码和听音乐是交叉输出的。这就体现了多线程的含义,因为要是平时输出,肯定就是谁在前先输出谁,比如说先输出十个听音乐,在输出十个敲代码。不会交叉输出。

    当然我们也可以使用一个线程类去演示,在这里,首先我们创建了两个类ThreadTest01和ThreadTest02,并且都继承了Thread,然后再测试类中,我们只需要调用相应的start方法即可。

    使用一个线程类和使用多个线程类的区别你可以这样理解,一个是多个不同的线程分别完成自己的任务,一个是多个相同的线程共同完成一个任务。

    2、实现Runnable接口

    我们同样拿上面的例子进行说明

    public class MyRunnable01 implements Runnable{
        @Override
        public void run() {
            for (int i=0;i<10;i++)
                System.out.println("听音乐");
        }
    }
    

    然后还可以敲代码

    public class MyRunnable02 implements Runnable{
        @Override
        public void run() {
            for (int i=0;i<10;i++)
                System.out.println("敲代码");
        }
    }
    

    最后我们可以测试一下了

    public class ThreadTest {
        public static void main(String[] args) {
            MyRunnable01 runnable01=new MyRunnable01();
            MyRunnable02 runnable02=new MyRunnable02();
            Thread thread1=new Thread(runnable01);
            Thread thread2=new Thread(runnable02);
            thread1.start();
            thread2.start();
        }
    }
    

    每次运行的时候,在控制台你都会看到不一样的效果。不过多运行几次依然能够发现交叉运行的效果。

    其实呢还有一种方式也可以实现线程,那就是实现Callable接口,不过很少用到,这种方式在以后的文章中再进行详细的介绍。毕竟这是第一篇文章。只是认识了解一下线程。

    通过Runnable接口的方式,我们依然发现,创建了两个MyRunnable,然后直接赋给Thread即可,调用的时候同样是使用start方法来启动。这就是简单的使用一下线程。

    不知道我们注意到没有,java其实为我们已经提供了Thread,我们可以直接进行实例化。有时候我们会经常使用匿名内部类的方式来创建一个线程,比如说下面这种

    new Thread("th1") {
         @Override
         public void run() {
              System.out.println( "匿名内部类");
         }
    }.start();
    

    注意:上面的两种创建线程的方式中,明明都是重写的run方法,为什么要去调用start启动线程呢?而且这两种方式有什么区别呢?在这里先留一个悬念,下一篇文章将会介绍道。

    三、分析多线程

    1、使用线程有什么好处呢?

    好处你已经能够看到了,就是我们可以同时做好几件事,在玩游戏的时候可以听歌,还可以看电影等等,多方便。

    2、使用线程有什么坏处嘛?

    坏处其实也很多,比如说对于单核 CPU,CPU 在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换,上下文切换是一个复杂的过程,比如要记录程序计数器、CPU寄存器的状态等信息,耗时又耗空间。

    为了解决这个问题,才有了现在的多核CPU。我们经常会听到手机或者是电脑是八核的,就是减少上下文切换带来的时间空间损耗,提高程序运行的效率。

    3、多线程带来一个问题

    从上面的例子其实我们发现只是各干各的事,相互之间互不干扰。还有一种情况是一个资源被多个线程所用到了,这就带来了线程安全问题。我们使用例子来演示一下这个问题,

    public class ThreadTest {
        private int value=0;
        public int getValue() {
            return value++;
        }
        public static void main(String[] args) throws InterruptedException {
            final ThreadTest test = new ThreadTest();
            //我们的本意可能是th1执行后value变为1
            new Thread("th1") {
                @Override
                public void run() {
                    System.out.println( test.getValue()+" "+super.getName());
                }
            }.start();
            //然后th2执行后value变为2
            new Thread("th2") {
                @Override
                public void run() {
                    System.out.println(test.getValue()+" "+super.getName());
                }
            }.start();
        }
    }
    

    在上面,我们直接创建了两个线程,在第一个线程执行完之后value,在第二个线程执行完之后value变为2。但是结果与我们想的往往不一样,我测试了N多次,结果总是0 th2和1 th1。或者是反过来,再或者是00、11等等。这就是线程不安全的例子。如何去确保线程安全呢?方式其实有很多种,我们一点一点深入之后再去解决。

    下一篇文章,我们将对线程的生命周期,常用API、以及源码(构造函数等)进行一个讲解,正式开始我们的多线程之旅。感谢支持。


    默认标题_方形二维码_2019.08.16.png

    相关文章

      网友评论

        本文标题:java多线程系列(1)入门和基础

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