day19-线程-锁机制

  1. 并行:指两个或多个事件在同一时刻点发生;
    并发:指两个或多个事件在同一时间段内发生。
  2. 并发性是指在一段时间内宏观上有多个程序在同时运行,但在单CPU系统中,每一时刻却仅能有一道程序执行(时间片),故微观上这些程序只能是分时地交替执行。
    倘若计算机系统中有多个CPU,则这些可以并发执行的程序便可被分配到多个处理器上,实现多任务并行执行,即利用每个处理器来处理一个可并发执行的程序,

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

  3. 进程是指一个内存中运行中的应用程序。每个进程都有自己独立的一块内存空间,一个应用程序可以同时启动多个进程。大多数操作系统都不需要一个进程访问其他进程的内存空间,也就是说进程之间的通信很不方便。此时我们得引入“线程”这门技术,一个进程至少有一个线程,为了提高效率,可以在一个进程中开启多个执行任务,即多线程。
    多进程:操作系统中同时运行的多个程序。
    多线程:在同一个进程中同时运行的多个任务。

    多任务系统,允许多个任务,每一个任务就是一个进程,每一个进程也可以同时执行多个任务,每一个任务就是线程。

    进程与线程的区别:
    进程:有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
    线程:堆空间是共享的,栈空间是独立的,线程消耗的资源也比进程小,相互之间可以影响的
    因为一个进程中的多个线程是并发运行的,那么从微观角度上考虑也是有先后顺序的,那么哪个线程执行完全取决于CPU调度器(JVM来调度),
    我们可以把多线程并发性看作是多个线程在瞬间抢CPU资源,谁抢到资源谁就运行,这也造就了多线程的随机性。

    Java程序的进程(Java的一个程序运行在系统中)里至少包含主线程和垃圾回收线程(后台线程)。

  4. 线程调度:
    计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令.
    所谓多进程并发运行,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务.
    那么,在可运行池中,会有多个线程处于就绪状态等待CPU,JVM就负责了线程的调度.
    JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性。

  5. 多线程优势:
    多线程作为一种多任务、并发的工作方式,当然有其存在优势:
    ①  进程之前不能共享内存,而线程之间共享内存(堆内存)则很简单。
    ② 系统创建进程时需要为该进程重新分配系统资源,创建线程则代价小很多,因此实现多任务并发时,多线程效率更高.
    ③ Java语言本身内置多线程功能的支持,而不是单纯第作为底层系统的调度方式,从而简化了多线程编程.

    多线程下载:可以理解为一个线程就是一个文件的下载通道,多线程也就是同时开起好几个下载通道。当服务器提供下载服务时,多个客户端是共享带宽的,在优先级相同的情况下,总服务器会对总下载线程进行平均分配。
    不难理解,如果你线程多的话,那下载的越快。
    多线程是为了同步完成多项任务,不是为了提高程序运行效率,但是却通过提高资源使用效率来提高系统的效率。

    宽带带宽是以位(bit)计算,而下载速度是以字节(Byte)计算,1字节(Byte)等于8位(bit),
    所以1024kb/s是代表上网带宽为1024千位(1M),而下载速度需要1024千位/秒(1024kb/s)带宽除以8,得出128千字节/秒(128KB/s)。

  6. Java运行进程:
      在Java代码中如何去运行一个进程
      方式1:Runtime类的exec方法:
      方式2:ProcessBuilder的start方法

  7. day19-线程-锁机制

  8. 创建和启动线程,传统有两种方式:
    方式1:继承Thread类;
    方式2:实现Runnable接口;
    ----------------------------------------------------------------
    线程类(java.lang.Thread): Thread类和Thread的子类才能称之为线程类. 别忘了主线程(mainf方法运行,表示主线程).

  9. 方式1:继承Thread类:
    步骤:
    1):定义一个类A继承于java.lang.Thread类.
    2):在A类中覆盖Thread类中的run方法.
    3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
    4):在main方法(线程)中,创建线程对象,并启动线程.
        创建线程类对象:                              A类   a  =  new   A类();
        调用线程对象的start方法:     a.start();//启动一个线程                                   day19-线程-锁机制

    注意:千万不要调用run方法,如果调用run方法好比是对象调用方法,依然还是只有一个线程(main线程),并没有开启新的线程.

  10. 方式2:实现Runnable接口;
    步骤:
    1):定义一个类A实现于java.lang.Runnable接口,注意A类不是线程类.
    2):在A类中覆盖Runnable接口中的run方法.
    3):我们在run方法中编写需要执行的操作---->run方法里的,线程执行体.
    4):在main方法(线程)中,创建线程对象,并启动线程.
       创建线程类对象:   Thread  t = new Thread(new  A());     Runnable/A  a= new A(); 不能用Thread,A没有继承它                  
         调用线程对象的start方法:          t.start();       Thread  t = new Thread(a);

    day19-线程-锁机制

    day19-线程-锁机制Thread构造器,需要Runnable对象/Runnable实现类的对象

    1.  

      使用匿名内部类来创建线程:

      day19-线程-锁机制

      day19-线程-锁机制

      吃苹果比赛

    2. day19-线程-锁机制day19-线程-锁机制

    3. 继承方式和实现方式的区别:能否共享资源:在这里三个同学完成抢苹果的例子的使用实现方式才合理.
      -------------------------------------------------------------------------------------
      继承方式:  因为 每个new Person都是一个线程,都有各自的苹果
                      1):Java中类是单继承的,如果继承了Thread了,该类就不能再有其他的直接父类了.
                      2):从操作上分析,继承方式更简单,获取线程名字也简单.(操作上,更简单)
                      3):从多线程共享同一个资源上分析,继承方式不能做到.
      实现方式:  虽然每个new  Thread都是一个线程,但是都是一个new Apple(),他们共享苹果
                      1):Java中类可以多实现接口,此时该类还可以继承其他类,并且还可以实现其他接口(设计上,更优雅).
                      2):从操作上分析,实现方式稍微复杂点,获取线程名字也比较复杂,得使用Thread.currentThread()来获取当前线程的引用.
                      3):从多线程共享同一个资源上分析,实现方式可以做到(是否共享同一个资源).

    4. 只有实现方式,才可能出现线程不安全的问题. 

    5. 线程同步 --------线程不安全的问题分析:----锁机制
      当多线程并发访问同一个资源对象的时候(实现方式),可能出现线程不安全的问题. 
          Thread.sleep(10);//当前线程睡10毫秒,当前线程休息着,让其他线程去抢资源.  经常用来模拟网络延迟.              
      ----------------------------------------------------------
      在程序中并不是使用Thread.sleep(10)之后,程序才出现问题,而是使用之后,问题更明显.

      在线程的run方法上不能使用throws来声明抛出异常,只能在方法中使用try-catch来处理异常.

    6. 要解决上述多线程并发访问多一个资源的安全性问题:
       A线程进入操作的时候,B和C线程只能在外等着,A操作结束,A和B和C才有机会进入代码去执行.
      -------------------------------------------------------------------------------
      方式1:同步代码块
      方式2:同步方法
      方式3:锁机制(Lock)

    7.  

      同步代码块:
      语法:
      synchronized(同步锁)
      {
           需要同步操作的代码
      }
      ---------------------------------------------------
      同步锁:
      为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制.
      同步监听对象/同步锁/同步监听器/互斥锁:
      对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
      Java程序运行使用任何对象作为同步监听对象,但是一般的,我们把当前并发访问的共同资源作为同步监听对象.
      注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着.

      day19-线程-锁机制

      同步方法:使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着.
      synchronized public    void  doWork(){
           ///TODO
      }
      同步锁是谁:
            对于非static方法,同步锁就是this.  
            对于static方法,我们使用当前方法所在类的字节码对象(Apple2.class).

      不要使用synchronized修饰run方法,修饰之后,某一个线程就执行完了所有的功能. 好比是多个线程出现串行.

      解决方案:把需要同步操作的代码定义在一个新的方法中,并且该方法使用synchronized修饰,再在run方法中调用该新的方法即可.

      day19-线程-锁机制

      synchronized的好与坏:
      好处:保证了多线程并发访问时的同步操作,避免线程的安全性问题.
      缺点:性能低.
      建议:尽量减小synchronized的作用域.

    8. 懒汉式的线程安全问题,???都是没有实现Runnable,怎么会线程不安全
      day19-线程-锁机制

    9. 双重检查加锁:
      可以使用“双重检查加锁”的方式来实现,就可以既实现线程安全,又能够使性能不受很大的影响。那么什么是“双重检查加锁”机制呢?
      所谓“双重检查加锁”机制,指的是:并不是每次进入getInstance方法都需要同步,而是先不同步,进入方法后,先检查实例是否存在,如果不存在才进行下面的同步块,这是第一重检查,进入同步块过后,再次检查实例是否存在,如果不存在,就在同步的情况下创建一个实例,这是第二重检查。这样一来,就只需要同步一次了,从而减少了多次在同步情况下进行判断所浪费的时间。

      “双重检查加锁”机制的实现会使用关键字volatile,它的意思是:被volatile修饰的变量的值,将不会被本地线程缓存,所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。

      注意:在java1.4及以前版本中,很多JVM对于volatile关键字的实现的问题,会导致“双重检查加锁”的失败,因此“双重检查加锁”机制只只能用在java5及以上的版本。
      提示:由于volatile关键字可能会屏蔽掉虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此一般建议,没有特别的需要,不要使用。也就是说,虽然可以使用“双重检查加锁”机制来实现线程安全的单例,但并不建议大量采用,可以根据情况来选用。

      day19-线程-锁机制

    10.  

      同步锁(Lock):
      Lock机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象.

      day19-线程-锁机制