java多线程

1、多线程的意义 

  操作系统可以多任务执行,每个任务就是就是一个进程。
  每个任务(进程)可以分多工作流分别执行。

  比较:进程:有独立的代码和数据空间(进程上下文),进程切换开销大,进程是资源分配的最小单位。

     线程:每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。线程是cpu调度的最小单位。

   


 

  并行与并发:

  • 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
  • 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。

 


 

  进程和线程的生命周期:创建、就绪、运行、阻塞、终止

  

    1)wait,notify和notifyAll只能在同步控制方法或者同步控制块里面使用,而sleep可以在任何地方使用
    2) sleep必须捕获异常,而wait,notify和notifyAll不需要捕获异常。sleep()睡眠时,保持对象锁,仍然占有该锁;而wait()睡眠时,释放对象锁。
    3)wait、sleep和join都可以用interrupt()打断,如果此刻线程B正在wait/sleep /join,则线程B会立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。

 

2、java多线程的实现

  Java线程的实现是基于一对一的线程模型,所谓的一对一模型,实际上就是通过语言级别层面程序去间接调用系统内核的线程模型,即我们在使用Java线程时,Java虚拟机内部是转而调用当前操作系统的内核线程来完成当前任务。

  由于我们编写的多线程程序属于语言层面的,程序一般不会直接去调用内核线程,取而代之的是一种轻量级的进程(Light Weight Process),也是通常意义上的线程,由于每个轻量级进程都会映射到一个内核线程,因此我们可以通过轻量级进程调用内核线程,进而由操作系统内核将任务映射到各个处理器,这种轻量级进程与内核线程间1对1的关系就称为一对一的线程模型。

  每个线程最终都会映射到CPU中进行处理,如果CPU存在多核,那么一个CPU将可以并行执行多个线程任务。

  

  创建多线程的方式:

    1)继承Thread类:new myThread(“A线程”).start();

    2)实现Runnable接口:new Thread(new myThread("A线程")).start();

    3)实现Callable接口

 

 

3、java多线程的内存模型

 

     前面说了,线程是cpu的最新调度单位,即cup在执行一个任务(进程)时,是执行的进程中的更小粒度线程。

  执行方式:1)(创建)线程对象。即为该线程分配内存并初始化,其中一个工作就是拷贝主存中的共享变量到线程工作内存(拷贝时:对于基本类型,直接拷贝值;对于引用类型,拷贝引用变量,其值仍保存堆中);

       2)线程调用start()方法(就绪)。

       3)线程获取到cpu时间片后,恢复cpu(运行)现场。即将线程的相关数据写入cpu缓存,再到cpu寄存器等进行执行。

       4)(结束)将在cpu中处理后的数据刷新到主存。然后空出的cpu继续加载其他线程的数据进行执行。

public class MyThread extends Thread{
    
     int a=1;//多线程中的共享数据(基本数据类型),运行时直接拷贝值到线程工作内存
     String str;//多线程中的共享数据(引用类型),运行时拷贝引用变量到线程工作内存
      MyThrad(String str){this.str = str;}

      @Override
       public void run(){
            str=str+a;
      }

      public static void main(String[] args){
          String str=new String("hgp");
           //启动线程后会拷贝共享变量到各自线程的工作内存,处理完了再分别刷新到主存(就是上面堆内存中的str对象中)  
           new Mythread(str).start();
           new Mythread(str).start();  
    }  
  
}
View Code

 

  

 

4、线程同步

  不管是什么内存模型,最终还是运行在计算机硬件上的。当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存,进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时,同样会先flush寄存器到CPU缓存,然后再在某些节点把缓存数据flush到主存。

      

 

  前提:上面说了对于线程的执行,并不是直接进行的,主存共享变量的操作要经过读入线程内存、cpu缓存等,待数据在cpu中执行完了再经过缓存、线程内存写入主存。也就是说线程的执行有一个时间过程,在这个过程中多线程对同一代码块的执行是彼此独立的,因为cpu操作的操作数已经是主存数据的拷贝了。

  1)这样的话若是非原子性操作就易出现错误,解决方法——线程同步,通过对象锁机制实现,用关键字synchronized声明。

    synchronized关键字的锁机制,锁的是对象:

        当synchronized使用在方法上或成员变量上,锁的是该实例对象;

        当synchronized使用在static静态成员上,锁的是该类型的Class对象。

        当synchronized使用在自定义代码块上,可以自定义执行该代码块需要的锁对象。

    注:synchronized锁的是对象,不同对象锁不同,如当访问一个同步方法时,该对象的其他同步方法也被锁;普通同步方法与静态同步方法互不干扰。还有synchronized关键字不能被继承。

  2)在线程把操作的共享数据刷新到主存之前,对共享数据的更改对其他线程来说是不可见的,解决方法——volatile 关键字,保证变量会直接从主存读取,而对变量的更新也会直接写到主存。

        voilatile只是解决可见性,对于非原子操作并不保证线程安全。

 

  总结:原子性——原子性指的是一个操作是不可中断的,即使是在多线程环境下,一个操作一旦开始就不会被其他线程影响。

     可见性——指的是当一个线程修改了某个共享变量的值,其他线程是否能够马上得知这个修改的值。

     重排序——计算机在执行程序时,为了提高性能,编译器和处理器的常常会对指令做重排。volatile会禁用重排序。

     有序性——指的是单线程内保证串行语义执行的一致性,后半句则指指令重排现象和工作内存与主内存同步延迟现象。

 

参考:http://blog.csdn.net/suifeng3051/article/details/52611310

   http://blog.csdn.net/javazejian/article/details/72772461

   https://www.cnblogs.com/wxd0108/p/5479442.html

   https://www.cnblogs.com/GarfieldEr007/p/5746362.html

posted @ 2018-03-10 17:39  衿沫青冥  阅读(136)  评论(0编辑  收藏  举报