多线程
- 线程常用的方法:
- join方法:线程一执行过程中调用的线程二的join(),则线程一进行等待,等待线程二执行结束之后再接着执行线程一。
- yield方法:谦让,代表调用该方法的线程让出cpu的执行权,自己进入等待队列中,即从运行状态转换到就绪状态。
-
线程状态:新建、就绪、等待、计时等待、阻塞、终止。
-
线程被挂起:就是线程再某个时刻失去cpu的执行权,进入到等待队列,就是线程被挂起。
-
synchronized,具有可重入性,允许同一个线程多次申请同一把锁。比如一个线程执行一个synchronized修饰的非静态方法m1,m1又调用了同一个类的非静态方法m2,这个线程能够正常运行。
-
默认抛出异常之后会释放锁资源。导致其他线程会进行操作共享资源的代码。
-
锁升级:偏向锁->自旋锁(争抢线程数较少,线程执行时间较短适合用这把锁。耗费cpu资源,自旋次数上限默认为10次,自旋线程个数为cpu内核线程的一半。该状态下等待该锁的线程不会进入等待队列,而是在cpu内自旋。)->重量级锁(线程数较多、单个线程执行时间较长时,这把锁比较合适。涉及用户态和内核态切换,占用时间长)
-
锁对象默认不能为字符串常量、Integer类型、Long类型。
-
单例设计模式代码实现:
- 饿汉式:类初始化时为静态变量复制,私有化构造方法,暴露get方法
- 懒汉式:第一次调用式才进行构造,有问题,多线程不安全,直接在方法上加synchronized关键字,但是这样之后第一次初始化之后后面出现大量线程同时请求该单例对象时涉及到锁升级,最终会升级到重量级锁,hotspot不会进行锁降级,获取单例会变慢,所以进行锁细化,对创建单例对象这一段代码使用同步代码块的方式进行加锁,但是出现线程不安全,原因是判断单例为空和加锁这两部操作不具有原子性,所以可以使用双重判断是否为空来避免线程安全问题。即加锁之后紧接着再次判断单例变量是否为空,如果第二次判断为空才会进行初始化。
- volatile:
- 线程可见性:保证变量更改之后其他线程能够立马感知到更改的值。(自我理解:现在cpu为多核,每个核内部有寄存器,多个核跑着不同的线程,所以各个线程运行时是不能够感知的,只能通过硬件来实现,多个核之间也可以共享寄存器。也可以使用jmm内存来解释)
- 禁止指令重排:为了尽可能充分利用cpu资源,将指令划分为不同阶段:取指阶段、分析指令阶段、执行指令阶段。使用流水线的方式来执行指令。编译器会对指令进行重排。在单例模式双重检查实现代码中,对实例应该使用volatile关键字修饰,方式new Object()编译过后的机器码进行重排序。
- volatile不能代替synchronized,因为不能保证原子性。
- 对一个数字进行累加,开启10个线程,每个线程对它循环加10000次,要想最后获得结果为100000,可以使用synchronized(最慢),atomicInteger(使用cas代替,比较好,适用于线程数较少的情形),LongAdder(使用Cas+分段锁的方式来实现,适用于大量线程的情况)。
- ReentrantLock简单使用:
ReentrantLock lock = new ReentrantLock();
try{
lock.lock();
//操作共享资源
}catch(Exception e){}
finally{
lock.unlock();
}
ReentrantLock中的Reentrant是可重入的意思,它和synchronized特别像,只不过后者是手动加锁释放锁。ReentrantLock的优点
- ReentrantLock可以通过tryLock(long timeout, TimeUnit unit)这种方式获取锁,即在指定时间之内获取锁,是否获取到可以通过返回值来确定。释放资源的时候可以通过返回值来确定。
- lockInterruptibly()可以使用这种被打断申请锁过程的方式方式来获取锁资源。synchronized获取不到锁时默认会进入等待状态,ReentrantLock则不同,可以使用thread.interrupt()方法来打断申请锁的过程,然后会抛出异常java.lang.InterruptedException at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898) at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222) at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335),最终执行finally代码块。
- 在创建ReentrantLock锁对象时,可以在构造方法内传入布尔参数,代表是否是公平锁,默认和synchronized一样都是非公平锁,synchronized不可以在公平锁和非公平锁之间进行切换。
- ReentrantLock底层是CAS和AQS队列来实现。
- CountDownLatch使主线程等待多个子线程都执行完之后主线程才继续运行。主要依靠连个方法,在主线程中声明一个countdownlatch,然后在主线程中调用await()方法,此时主线程就处于阻塞状态,再每个子线程中调用cutdown()方法,等到减为0的时候主线程就继续运行。
- LongAdder,对于多个线程对同一个数字进行累加可以有三种实现方式,可以使用synchronized来实现线程安全,也可以使用atomicLong来实现,也可以使用LongAdder来实现。
- LockSupport,这个类是实现锁的工具,主要有两个方法park()和unpark(Thread t),调用park()的线程就会进入等待状态,在其他线程调用unpark(t)可以指定某一个线程状态转换为可运行状态。
- wait()和notify(),这个两个方法是Object类中的方法,主要由被锁定的对象来进行调用,可以实现被调用线程处于等待状态,主要是释放锁资源,进入等待队列中,由其他线程使用notify()唤醒处于等待状态的线程,使其进入就绪状态。