首页 > 编程语言 > 详细

多线程总结

时间:2020-08-24 09:44:46      阅读:59      评论:0      收藏:0      [点我收藏+]

https://www.cnblogs.com/128-cdy/p/12454855.html

进程和线程:指的是一段正在运行的程序,一个程序支持很多任务执行,任务又称为线程。是资源分配的最小单位。 线程是程序执行(CPU调度)的最小单位。

线程的创建:

  • 继承Thread类,重写run方法
  • 实现Runnable接口,重写run方法
  • 实现Callable接口 ,重写call方法,存在返回值,需要抛异常,FutureTask对象可以获取返回值(get()方法),异步计算的结果。

守护线程:具备自动结束生命周期的特点,当么有一个非守护线程时,则JVM会在某一程序结束的时候自动退出;线程.setDeamon(true)将其设置为守护线程(后台程序可以设置为守护线程)。

线程六状态的转换

技术分享图片

        通过new创建一个Thread对象进入新建状态调用start()方法后,进入Runnable(就绪状态),当处于就绪状态的线程得到CPU的使用权,也就是被调度,将会进入Running(运行状态),当此时的线程调用wait(),join()方法,便会进Waiting(等待阻塞状态),此时的线程会释放CPU的使用权,并且会释放资源(锁),调用wait()方法后,在调用notify(),或者notifyAll()方法后便会进入到Blocking(阻塞状态),此时线程获取到监视器锁会再次进入到Runnable(就绪状态);在Running(运行状态),线程等待监视器锁等待用户输入,也会进入到Blocking(阻塞状态),此时线程获取到监视器锁并且用户输入完成会进入到Runnable(就绪状态);在Running(运行状态),调用sleep(time)方法,会进入到Timed_Waiting(睡眠阻塞状态),睡眠时间到了后会进入到Runnable(就绪状态);在Running(运行状态),调用yeid()方法,会释放一次CPU的使用权,进入到就绪状态;在Running(运行状态)的run()方法执行完毕后,线程就会进入到Terminated(终止状态)。

 注意:

  •  wait()方法会释放CPU使用权以及锁;
  • 但是sleep()方法仅仅只会释放掉CPU的使用前并不会释放锁(抱着锁睡觉),线程被放入超时等待队列,与yield()相比,他使得线程较长时间得不到运行;
  • yield()方法放弃一次CPU的使用权,让给谁是由操作系统决定的,会使当前的线程由运行态进入到就绪态,是使用在同步方法或者同步代码块中,不会释放当前的监视器锁,并且线程很快会执行;
  • wait()方法需要和notify()/notifyAll()方法用在同步方法和同步块中;
  • yield()是启发式的方法,提醒调度器愿意放弃当前的CPU资源,当CPU不紧张时会忽略这种请求。

java线程的生命周期?

  • new    当前线程并不存在。
  • Runnable    调用start()方法启动之后进入该状态,线程并不是一启动就会得到执行,需要等到调度器,得到CPU的使用权,该状态的线程存放在可运行的线程的线程池中。(Running 状态表示当前线程正在执行)。
  • Blocked     
  • Waiting   
  • Time_Waiting   
  • Terminated

start()方法的调用和run()方法的区别?

         在start()方法里调用了一次start0()方法,该方法使用了native关键字进行定义,native指的是调用本机的原生系统函数。调用start方法会告诉JVM去分配本机系统的资源,才能实现多线程,最终让run()方法在开启的线程中执行,执行完毕后会将started标识置为true。但是调用run()只是普通方法的调用,无法启动一个线程。

sleep()方法和yield()方法的区别?

         sleep()方法使程序进入到阻塞状态,并且可以设置时间,能够使的线程较长时间得不到运行,但是yield()是启发式方法,提醒调度器愿意放弃当前的CPU资源,当CPU不紧张时会忽略这种请求,并且其放弃一次CPU使用权以后会使得线程进入到就绪状态。

sleep()方法与wait()方法的区别? 

        sleep()是属于Thread类中的,用来控制线程,而wait()是属于Object中的方法,作用于线程间通信的;sleep()方法使用在同步代码块或者同步方法中,不会释放当前持有的监视器锁,但是wait()方法会释放掉监视器锁以及CPU的使用权;调用wait()方法会使得线程进入到等待池中。       (sleep()方法只能传毫秒,TimeUnit枚举类可以设置不同单位的睡眠时间)。

interrupted()和isInterrupted方法的区别?

       首先看看 interrupt()方法,该方法用于中断线程,将中断位设置为true,线程中断仅仅是设置线程的中断位状态,不会停止线程,需要用户自己去监视线程的状态并且做处理,支持线程中断的方法<wait(),jion(),sleep()>(也就是线程中断会抛出interruptedException的异常的方法)就是在监视线程的中断状态,一旦线程被设置为“中断状态”,就会抛出中断异常。interrupted()查询当前线程的中断状态,并且清除原状态,如果一个线程被中断了,第一次调用该方法返回true,第二次和后面的就返回false。isInterrupted仅仅是查询当前线程的中断状态。

并发编程的三大特性:

  • 原子性     即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。
  • 可见性    可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
  • 有序性     即程序执行的顺序按照代码的先后顺序执行;它不保证程序中各个语句的执行先后顺序同代码中的顺序一致,但是它会保证程序最终执行结果和代码顺序执行的结果是一致的。

临界区:访问临界资源的代码段(特点:是一段供线程独占式访问的代码)。

临界资源:某一时刻只能由一个线程访问的资源。

synchronized关键字(可重入锁):

     synchronized 获取监视器锁,使得当前的某一段代码只能被独占式访问。

synchronized同步代码块和同步方法的底层原理:

     synchronized同步方法,修饰一个普通方法,获取的是当前类对象的(this引用)的锁;修饰的静态(static)方法,获取的就是当前类的锁。

     静态常量池多了一个ACC_SYNCHRONIZED标识符,标识当前是一个同步方法,获取锁以及释放锁也是使用了monitorenter与monitorexit的字节码指令。

     synchronized同步代码块,Object obj = new Object();   synchronized(obj){}通过反编译可以看出,访问同步代码块会获取到monitorenter,出了同步代码块的右括号,就会有一个monitorexit字节码指令。

monitor  lock

每一个Java对象天生都带有一把锁(monitor  lock),一个monitor lock只能被一个线程在某一时刻内获得,通过synchronized关键字就是去获取这一把锁,在一个线程尝试获取与对象关联的monitor lock的时候会有如下情况:

  1. monitorenter
  • 如果monitor的计数器为0,表示当前的monitor  lock么有被任何线程获取,被某个线程获取该锁以后,会对计数器进行加1操作,表示当前的线程是这个monitor  lock的所有者,这个已经拥有monitor  lock的线程想要再次拥有该锁,便会对monitor的计数器累加;
  • 如果monitor的计数器不为零,表示锁已经被其他线程所拥有,当前线程想要获取monitor  lock,会被阻塞,直到计数器为零,然后再次尝试获取monitor  lock的使用权;

         2.monitorexit

  • 释放对monitor  lock的所用权,将计数器减1,如果计数器为0,表示当前的线程不在拥有该锁的所有权。

锁的升级过程?

        JDK 1.5以后对synchronized做了优化,对象的对象头的Mark Word的不同的锁状态下存储的内容不同:

技术分享图片

无锁:普通的对象,Mark Word记录的是对象的hashCode值,锁的标志位为01,是否偏向位为0;

偏向锁:当对象获取到同步锁,Mark Word记录的是当前线程的ID,锁的标志位为01,是否偏向位为1,表示进入到偏向锁模式,此时若有线程再去尝试获取锁,JVM发现当前锁是偏向模式,Mark Word记录的  不是此线程的ID,便会采用CAS操作尝试去获取当前这把锁,有可能成功有可能失败,失败的话表示抢锁失败,竞争比较激烈,此时就会升级到轻量级锁;

CAS是一个原子操作(不可中断),其中含有三个值(内存位置值V,期望的值A,要修改的值B),首先判断内存V所保存的值与所期望的值A是否相等,如果相等,则可以修改。

轻量级锁:JVM会在当前线程的线程栈中开辟一 块内存空间lock record,里面保存指向要获取的锁的Mark Word的引用,同时在要获取锁的Mark  Word保存指向lock record,这两个保存操作(CAS操作)成        功,代表线程抢到了轻量级锁,如果失败,则会进行自旋操作循环一定次数,如果还是失败,表示竞争更加激烈,继续锁升级;

volatile关键字:保证有序性,可见性(修饰共享资源的共享变量);在Java的内存模型中,每个线程都有自己的工作内存,当操作一个变量时,需要在主内存拷贝到自己的工作内存中,操作完成后再写入到主  内存中,在并发情况下,该过程并不能保证可见性,所以就需要volatile。要保证线程安全,需要保证并发的三个条件(volatile+synchronized / volatile+CAS)。

被volatile关键字修饰的变量会有一个lock前缀,即内存屏障。

  • 确保指令重排序不会将后面的代码排在内存屏障的前面;
  • 确保指令重排序不会将前面的代码排在内存屏障的后面;
  • 强制的将工作内存中的值刷新到主内存中;
  • 如果是写操作,则会导致其他工作内存中缓存的数据失效。

悲观锁和乐观锁?

  • 悲观锁    总是假设最坏的情况,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会阻塞直到它拿到锁(共享资源每次只给一个线程使用,其它线程阻塞,用完后再把资源转让给其它线程)。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronizedReentrantLock等独占锁就是悲观锁思想的实现。
  • 乐观锁   总是假设最好的情况,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号机制和CAS算法实现。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库提供的类似于write_condition机制,其实都是提供的乐观锁。在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。
CAS的ABA问题?
        假如线程1要修改主内存的值A,但是在这之前已经有线程2将主内存的值改为B,这时又来了线程3将主内存的值改为了A,但是线程1并不会发现这些问题,在这之前已经做出了修改,需要版本号来解决。

CAS的问题?

  • ABA;
  • 自旋CAS如果长时间不成功,会给CPU带来很大的开销,居高不下;
  • 只能保证一个共享变量的原子操作。

ReentrantLock

      Lock接口下实现类,提供了一种可定时的,可轮询的以及可中断的锁获取操作。

  • Lock() 加锁  unLock() 解锁。Lock接口中的方法lock() /unlock()/ trylock() /lockInterruptily() /trylock(long time,TimeUnit unit) 方法。

Synchornized和Reentrantlock区别?

  • 直观上来看synchornized是一个关键字,而Reentrantlock是一个类;
  • synchornized获取的是内置的monitor锁,而Reentrantlock加锁时需要new Reentrantlock()的对象,通过对象.lock()获取到独占锁;
  • Reentrantlock相比于synchornized更加的灵活,等待可中断,只有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,相当于synchornized来说可以避免出现死锁的情况。通过lock.lockinterruptibly()来实现这个机制。公平锁,多个线程等待同一个锁的时候,必须按照申请锁的时间顺序获得锁,synchornized锁非公平锁,ReentrantLock默认的构造函数是创建非公平锁,可以通过参数true设为公平锁,但非公平锁的性能更好。一个ReentrantLock对象可以同时绑定多个对象。ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。

           ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。

Reentrantlock获取锁和释放锁的过程:

获取锁:

  • 非公平性锁中,当前线程会通过CAS操作来抢占锁,抢占成功修改锁的状态state为1 ,并且将线程的信息记录到锁当中( setExclusiveOwnerThread(Thread.currentThread()) );
  • 抢占不成功,则进入队列acquire(1),tryAcquire()尝试着去获取锁,如果成功则返回,否则以当前线程作为一个节点插入到一个AQS的队列的尾部;
  • 公平锁就相当于买票,后来的人需要排到队尾依次买票,不能插队。所以,在第一部尝试获得锁的时候需要去判断有没有队列,而不是直接去抢占。

释放锁:

        获取锁的状态值,判断当前释放锁的线程与锁中记录的线程信息是否一致,不一致就会抛出异常,如果一致,则判断锁的状态位是否为0,如果为0,则表示锁不在被占用,将锁中的信息清除掉。

什么是死锁?

        在并发条件下,因为抢夺资源而造成的一种相互等待的现象,若无外力作用下,程序无法继续推进的情况。

死锁的四个必要条件:

  • 互斥条件一个资源只能被一个进程占用。
  • 请求与保持    一个进程因为请求资源而阻塞时,对已获得的资源保持不放。
  • 不可剥夺   进程已经获得的资源在没有使用完之前,不能强行剥夺。
  • 循环等待    若干进程之间形成一种头尾相接的循环等待资源关系。

常见死锁:

  • 交叉锁可能导致死锁的发生       threadA: A获取R1的锁等待获取R2的锁    threadB: B获取R2的锁等 待获取R1的锁
  • 内存不足    并发的请求系统可用的内存资源,系统资源不足可导致死锁
  • 一问一答的交互

死锁的避免:

  • 打破互斥条件    运行多个线程同时访问某些资源
  • 打破请求和保持条件    资源的预分配策略(银行家算法)
  • 打破不可抢占    允许线程强行从资源占有者手中去剥夺某些资源
  • 打破循环等待     实现资源有序分配策略(银行家算法)

死锁的检测与恢复:

  • 剥夺资源    剥夺某些现成的资源
  • 撤销线程    撤销代价最小的线程

线程间的通信:

  • synchronized加锁的线程0bject类 中wait()/notify()/notifyAll()
  • ReentrantLock类加锁的线程Condition类 中的await()/signal()/signalAll()

为什么wait/notify/notifyAll是Object中的方法而不是Thread里的?

       这与他们使用的场景有关,他们使用在synchronized的同步块中,获取的是对象的monitor,是要用对象操作,而不是用线程去操作。

wait/notify/notifyAll方法的作用?

       wait()方法会使得线程进入到等待阻塞状态,并且释放掉synchronized的锁,进入到对象的等待池中;notify()会唤醒一个调用wait()方法的等待线程,在使用前,线程必须要去拥有调用该方法的对象的锁,如果未获取到该对象的锁,则会进入到锁池中;notifyAll()是唤醒所有的调用wait()方法的等待线程。

锁池与等待池:

锁池: 假设thread A已经拥有了某个对象的锁,而其他的线程想要获取当前的对象的锁(进入当前对象的synchornized方法或者synchronzied代码块中),这些线程就进入到当前这个对象的锁池。

等待池:假设thread A调用某个对象的wait ()方法,thread A就会释放该对象的锁,进入到该对象的等待池中。

notify/notifyAll方法的区别?

      notify方法会唤醒一个等待线程,但是notifyAll会唤醒所有的等待线程;唤醒一个线程就不会导致等待池中的线程去竞争锁,而唤醒所有线程会导致线程竞争锁,会导致线程流入到锁池中。

 生产者消费者模型(代码)

Condition

Condition是一个接口,基本的方法await()/signal()/signalAll(),并且Condition依赖于Lock接口,采用Lock . newCondition () 生成一个Condition的对象。

await()方法为什么要绑定到Condition对象上,一个ReentrantLock可以绑定多个condition对象有什么作用?

        wait()在synchronized同步的对象中使用一个队列;但await()在Lock子类以newCondition方法创建多个等待队列,一个锁有多个等待队列,可以提高效率,这也就是ReentrantLock较于synchronized效率高的原因,绑定condition对象用于线程间通信。在多个线程中想要唤醒某个指定的线程,便需要多个condition对象。

阻塞队列

 

多线程总结

原文:https://www.cnblogs.com/128-cdy/p/13545121.html

(0)
(0)
   
举报
评论 一句话评论(0
关于我们 - 联系我们 - 留言反馈 - 联系我们:wmxa8@hotmail.com
© 2014 bubuko.com 版权所有
打开技术之扣,分享程序人生!