https://www.cnblogs.com/128-cdy/p/12454855.html
进程和线程:指的是一段正在运行的程序,一个程序支持很多任务执行,任务又称为线程。是资源分配的最小单位。 线程是程序执行(CPU调度)的最小单位。
线程的创建:
守护线程:具备自动结束生命周期的特点,当么有一个非守护线程时,则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(终止状态)。
注意:
java线程的生命周期?
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的时候会有如下情况:
2.monitorexit
锁的升级过程?
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前缀,即内存屏障。
悲观锁和乐观锁?
synchronized
和ReentrantLock
等独占锁就是悲观锁思想的实现。java.util.concurrent.atomic
包下面的原子变量类就是使用了乐观锁的一种实现方式CAS实现的。CAS的问题?
ReentrantLock
Lock接口下实现类,提供了一种可定时的,可轮询的以及可中断的锁获取操作。
Synchornized和Reentrantlock区别?
ReenTrantLock的实现是一种自旋锁,通过循环调用CAS操作来实现加锁。它的性能比较好也是因为避免了使线程进入内核态的阻塞状态。
Reentrantlock获取锁和释放锁的过程:
获取锁:
释放锁:
获取锁的状态值,判断当前释放锁的线程与锁中记录的线程信息是否一致,不一致就会抛出异常,如果一致,则判断锁的状态位是否为0,如果为0,则表示锁不在被占用,将锁中的信息清除掉。
什么是死锁?
在并发条件下,因为抢夺资源而造成的一种相互等待的现象,若无外力作用下,程序无法继续推进的情况。
死锁的四个必要条件:
常见死锁:
死锁的避免:
死锁的检测与恢复:
线程间的通信:
为什么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