Java中的多线程

关于Java中的多线程信息量可以说是相当大的,并不是一篇博客就能道尽的,而今天我们主要分享的是线程的一些基本知识,得其根本方能成神么。。。

    1、线程和进程

        提到线程,很多时候会与进程做比较,它们很像但是又有所区别。

        相似点:线程和进程的相似之处在于它们都是单个顺序控制流。

        不同点:

           进程:是指运行中的程序,每个进程都有自己独立的代码和数据空间,进程间的切换会有较大的消耗,一个进程包含1到n 个线程(进程是资源分配最小单位);

           线程 :是进程中的一个单个的顺序控制流,同一类线程共享代码和数据空间,每个线程有独立的运行站和程序计数器,线程切换开销小。(线程是cpu调度的最小单位)    

            关于多线程:是指在单个程序内可以同时运行多个不同的线程完成不同的任务。

                                   Java中的多线程

2、线程的创建

        线程的创建一般有两种方法:继承Thread类、实现Runnable接口

  a、继承Thread类:                                                                b、实现Runnable接口:

 Java中的多线程     Java中的多线程

注意:在启动多线程的时候,需要通过Thread类的构造方法来构造出对象,然后调用该对象的start()方法来运行多线程代码。实际上所有的多线程的代码都是通过运行start()方法来运行的。但是我们特别需要明白的一点就是start()方法的调用并不是立即就执行多线程代码,而是使得该线程变成可运行状态,什么时候运行是由操作系统来决定的。也就是看线程什么时候能获得系统分配给它的CPU资源。

3、Thread和Runnable的区别

        上面我们知道了创建线程的两种方式,但是在实际使用中我们更加的偏向于Runnable,为什么呢?

        实现Runnable接口比继承Thread类所具有的优势:

         1):适合多个相同的程序代码去处理用一个资源

         2):可以避免java中的单继承的限制

         3):增加程序的健壮性,代码可以被多个线程共享,代码和数据独立

         4):线程池只能放入实现了Runnable或callable类线程,不能直接放入继承Thread的类

4、线程的几种形态

                 Java中的多线程

        线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

如上图所示(关于线程五个阶段的相互关系非常详细了),Runnable表示就绪阶段,Running表示运行阶段,Blocked表示阻塞阶段。

        我们来详细介绍一下这几个阶段设计到的一些方法:

        a、start()和run()方法:

                新建的线程仅仅只是空的线程对象,并没有给其分配系统资源,而我们调用Start()方法时,只是启动了该线程,使其进入到就绪状态,调用任何其他方法是无意义的且还会引发异常,当该线程获得cpu时间后,调用run()方法,进入到运行状态。

        b、synchronized关键字,wait(),join(),yield()和sleep()方法

              synchronized关键字:该关键字我们称之为同步锁,用synchronized修饰的方法我们称之为同步方法,其实java平台为每个具有synchronized代码段的对象关联一个对象锁,这样任何线程在访问对象的同步方法的时候,都会先获得对象的锁,然后才能进入到synchronized方法,这时其他线程就不能同时访问该对象的同步方法了(包括其他的同步方法,但是非同步方法还是可以访问的)

                注意:无论synchronized关键字加在方法上还是对象上,如果它作用的对象是非静态的,则取得的锁是对象;如果synchronized作用的对象是一个静态方法或一个类,则它取得的锁是对类,该类所有的对象同一把锁。事实上实现同步是要很大的系统开销作为代价的,甚至可能造成死锁,所以尽量避免无畏的同步控制。

                wait()方法:该方法是Object类方法,对此对象调用wait方法导致本线程放弃对象锁,进入到等待次对象的等待池中,只有针对此对象发出notify方法或notifyAll方法后本线程才进入等锁池准备获得对象锁。由此可见wait,notify,notifyAll只能在同步控制方法或者同步控制块中使用(这个很好理解,既然调用wait会放弃锁,那么是不是有锁才能去释放啊)

                注意:一旦一个对象调用了wait方法,必须采用notify或notifyAll方法唤醒该线程;如果线程拥有某个或某些对象的同步锁,那么在调用了wait方法后,这个线程就会释放它所持有的所有同步资源,而不是仅限于这个被调用了wait方法的对象。

                join()方法:Thread类的join方法主要作用是同步,它可以是的线程之间的并行执行变为串行执行。join的意思是使得放弃当前线程的执行,并返回对应的线程(简单理解就是在A线程中调用了B线程的join方法时,表示只有当B线程执行完了之后,A线程才能继续执行)

                注意:其实join方法是通过调用线程的wait方法来达到同步的目的的,例如:A线程调用了B线程的join方法,则相当于A线程调用了B线程的wait方法。在调用了B线程的wait方法后,A线程就会进入阻塞状态,当B线程执行完了之后,它自动的调用自身的notifyAll方法来唤醒A线程,从而到达同步的目的。

                yield()方法:是让当前的运行线程回到可运行状态,以允许具有相同优先等级的其他线程获得运行机会。但是,实际中无法保证yield()达到让步的目的,因为让步的线程还有可能被线程调度程序再次选中。

               sleep()方法:Thread.sleep(Long millis)方法,使线程暂停指定时间,以毫秒为单位。当睡眠结束后,就转为就绪状态(这里注意,是就绪状态而不是执行状态,它的执行还是需要等待系统分配cpu时间,所以说我们指定的时间,只能算是最小等待时间),在使用该方法时明确要捕获并处理异常。

                 注意:sleep方法是帮助其他线程获得运行的最好方法,但是如果当前线程获取到的有锁,sleep是不会让出这个锁的(这也是sleep方法与wait方法很重要的一个区别)。另外值得注意的是,sleep方法是静态的,只能控制当前正在运行的线程。


5、线程的优先级和调度

        Java中的每个线程都有优先级,当有多个线程处于就绪状态时,线程调度程序根据线程的优先级调度线程运行。关于线程的优先级通常会接触到两个方法:

        public final void setPriority(int newPriority)设计线程的优先级

                newPriority为线程的优先级,其取值为1到10之间的整数,数值越大优先级越高。当创建java线程时,我们没有指定它的优先级,则它从创建该线程那里继承优先级。

        public final int getPriority()返回线程的优先级

        JVM提供了10个线程的优先级,但与常见的操作系统都不能很好的映射。如果希望程序能够很好的映射到各个操作系统中应该使用Thread类有以下三个静态常量作为优先级,这样就能够保证同样的优先级采用了同样的调度方式。

        Thread类下的三个静态常量:

                static int MAX_PRIORITY   线程可以具有的最高优先级,取值为10;

                static int MIN_PRIORITY    线程可以具有的最低优先级,取值为1;

                static int NORM_PRIORITY   分配给线程的默认优先级,取值为5;


6、关于多线同步问题

       提到线程的同步问题我们首先想到的一定是synchronized关键字,那么对于synchronized我们需要明确几点:

             a、无论synchronized关键字加在方法上还是对象上,它取得的锁都是对象,所以说同步方法很有可能还会被其他线程的对象来访问。

            b、每个对象只有一个锁与之相关联。

            c、实现同步是需要很大的系统开销作为代价的。

        线程同步总结:

            a、如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

            b、线程同步的目的就是为了保护多个线程访问一个资源时对资源的破坏。

           c、线程同步方法是通过锁来实现的,每个对象都有且仅有一个锁,这个锁与一个特定的锁关联,线程一旦获取了对象锁,其他访问该对象的线程就无法在访问该对象的其他非同步方法。

            d、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁互不干扰。一个线程获得锁,当在一个同步方法中访问另外对象上的同步方法时,会获取这两个对象锁。