基本概念
- 进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位。
- 线程:作为一个一个进程里面最小的执行单元他就叫一个线程,用简单的话说一个程序不同的执行路径叫做一个线程。
线程启动的几种方法
- new Thread().start()
- new Thread(Runnable).start()
- 线程池 ,如:Executors.newCacheThreadPool()
线程的方法
- sleep,意思就是睡眠,当前线程暂停一段时间给别的线程去运行,sleep是如何复活的,有你的睡眠时间而定,等睡眠到规定的时间自动复活。
- yield,就是当前线程正在执行的时候停止下来进入等待队列,回到等待队列在系统的调度算法里头还是依然有可能把你刚回去的这个线程拿回来继续执行,当然,更大可能是把原来的等待的那些线程拿出一个来执行,所以yield的意思就是我让出一下CPU,后面你能不能抢夺到我不管咯。
- join,意思就是在自己当前线程加入你调用join的线程,本线程等待,等调用的线程运行完了,自己再去执行,t1和t2两个线程,在t1线程的某个点调用了t2.join,它会跑到t2去运行,t1等待t2运行完毕后继续t1运行(自己join自己没有意义)
线程状态
常见线程状态有六种:
当我们new一个线程时,还没有调用start()该线程处于新建状态
线程对象调用start()方法时候,它会被线程调度器来执行,也就是交给操作系统来执行了,那么操作系统来执行的时候,这个整个状态叫Runnable,Runnable内部有两个状态(1)Ready就绪状态(2)Running运行状态。就绪状态是说扔到CPU的等待队列里面排队等待CPU执行,等真正扔到CPU上去运行的时候叫做Running运行状态。(调用yile的时候会从Running状态跑到Ready状态中,线程调度器选中执行的时候又从Ready状态跑到Running状态去)
如果你线程顺利的执行完了就会进去(3)Teminated结束状态,(需要注意Teminated完了之后还可不可以回到new状态在调用start呢?这是不行的,完了这就是结束了)
在Runnable这个状态里头还有其他的一些状态的变迁
(4)TimeWaiting(5)Waiting(6)Blocked阻塞,在同步代码块的情况下没有得到锁就会阻塞状态,获得锁的时候是就绪状态运行。在运行的时候如调用了o.wait(),t.join(),LockSupport.park()进入Waiting状态,调用o.notify(),o.notifyAll(),LockSupport.unpark()
就又回到Running状态。TimeWaiting按照等待时间等待,等时间结束后自己就回去了。Thread.sleep(time),o.wait(time),t.join(time)
,LockSupport.parkNanos(),LockSupport.parkUntil()这些就是关于时间等待的方法。
问题:哪些是由JVM管理?哪些是由操作系统管理?
上面这些状态全是由JVM管理,因为JVM管理的时候也是需要通过操作系统,所以,那个是操作系统那个是JVM他俩分不开,JVM是跑到操作系统的一个普通程序。
synchronized
- 静态方法static是没有this对象的,你不需要new一个对象就能执行这个方法,但如果这上面加一个synchronized的话就代表synchronized(T.class)。这里这个synchronized(T.class)锁的就是T类的对象。
- synchronized一个属性:可重入,一个同步方法可以调用另一个同步方法,一个线程已经拥有某个对象的锁,再次申请的时候仍然会得到该对象的锁。
- 异常锁,程序在执行过程中,如果出现异常,默认情况会释放锁。
- synchronized(Object),避免锁String常量 Integer Long
- synchronized 加上final修饰
synchronized底层实现
当我们使用synchronized的时候HotSport的实现是这样的:上来之后第一个去访问某把锁的线程 比如sync(Object),来了之后先在Object的头上面markword记录这个线程。(如果只有第一个线程访问的时候实际上是没有给Object加锁的,在内部实现的时候,只是记录这线程的ID(偏向锁)。
偏向锁如果有线程争用的话,就会升级为自旋锁(不进入CPU的就绪队列,用while循环尝试获取锁)
自旋转圈10次之后,升级会重量级锁,重量级锁就是去操作系统那里申请资源,这是一个锁升级的过程。
需要注意并不是CAS的效率就一定比系统锁要高,这个要区分实际情况。
执行时间短(加锁代码),线程数少,用自旋
执行时间长(加锁代码),线程数多,用系统锁。
volatile
- 保证线程可见性
- MESI
- 缓存一致性协议
- 禁止指令重排序
- DCL单例(Double Chek Lock)
- CPU原语 (读写屏障)
CAS
CompareAndSet,比较并且设定。
ABA问题:
- 可加版本号解决
- 如果是基础类型无所谓
- 如果是引用类型,可能有问题(银行转账例子)
Atomic
为什么Atomic会比synchronized 快?
因为不加锁。synchronized 可能会去操作系统申请重量级锁,所以synchronized偏低,在这种情形下效率偏低。
ReentranLock
- 必须必须必须手动释放锁(try...finally...)
- 可以使用tryLock进行尝试锁定
- lockInterruptibly 可被打断的加锁
- 可指定公平锁/非公平锁
CountDownLatch(倒数
CyclicBarrier(循环栅栏)
Phaser(阶段)
ReadWriteLock
这个ReadWriteLock是读写锁。读写锁的概念其实就是共享锁和排他锁,读锁就是共享锁,写锁就是排他锁。
Semaphore(信号灯)
Exchanger(交换器)
LockSupport
- LockSupport不需要synchronized加锁可以实现线程的阻塞和唤醒
- LockSupport.unpark()可以先于LockSupport.park()执行,并且线程不会阻塞
- 如果一个线程处于等待状态,连续调用两次park方法,就会使该线程永远不会被唤醒
public static void main(String[] args) {
Thread t = new Thread(() -> {
for (int i = 0; i < 10; i++) {
System.out.println(i);
if (i == 5) {
LockSupport.park();
}
//两次park就会让现场无法被唤醒
/* if (i==8){
LockSupport.park();
}*/
}
});
try {
TimeUnit.SECONDS.sleep(5);
} catch (InterruptedException e) {
e.printStackTrace();
}
t.start();
LockSupport.unpark(t);
}
}
LockSupport中park()和unpark()实现原理
park()和unpark()方法的实现是由Unsafe类提供的,而Unsafe类是由C和C++语言完成的,其实原理也比较好理解,他主要通过一个变量作为一个标识,变量值在0/1之间来回切换,当这个变量大于0的时候这个线程就获得了令牌