如何定义线程
1.继承Thread类
2.实现Runnable接口
3.实现Callable(利用futureTask执行任务)
上下文切换
多核cpu下,多线程是并行工作的,如果线程数多,单个核又会并发的调度线程,运行时会有上下文切换的概念,cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。
1.线程的cpu时间片用完
2.垃圾回收
3.线程自己调用了 sleep、yield、wait、join等方法
当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。
sleep() 使线程休眠,会将运行中的线程进入阻塞状态。当休眠时间结束后,重新争抢cpu的时间片继续运行,不会释放锁
yield()方法会让运行中的线程切换到就绪状态,重新争抢cpu的时间片,争抢时是否获取到时间片看cpu的分配(礼让)
join()是指调用该方法的线程进入阻塞状态,等待某线程执行完成后恢复运行
wait()并不是线程对象的方法,而是Object的,即所有对象都有wait和notify、notifyAll方法,必须在获取到锁的时候使用,使得获取到锁的线程进入阻塞状态,即释放锁
interrupt()方法用于中断线程;主要用于打断处于sleep/join/wait的线程,使得该线程抛出InterruptedException异常
Synchronized
同步锁也叫对象锁,是锁在对象上的,不同的对象就是不同的锁。
该关键字是用于保证线程安全的,是阻塞式的解决方案。
让同一个时刻最多只有一个线程能持有对象锁,其他线程在想获取这个对象锁就会被阻塞,不用担心上下文切换的问题。
注意: 不要理解为一个线程加了锁 ,进入 synchronized代码块中就会一直执行下去。如果时间片切换了,也会执行其他线程,再切换回来会紧接着执行,只是不会执行到有竞争锁的资源,因为当前线程还未释放锁。
当一个线程执行完synchronized的代码块后 会唤醒正在等待的线程
synchronized实际上使用对象锁保证临界区的原子性 临界区的代码是不可分割的 不会因为线程切换所打断
可以满足原子性、可见性、有序性
volatile
该关键字解决了可见性和有序性,volatile通过内存屏障来实现的
写屏障:会在对象写操作之后加写屏障,会对写屏障的之前的数据都同步到主存
读屏障:会在对象读操作之前加读屏障,会在读屏障之后的语句都从主存读,并保证读屏障之后的代码执行在读屏障之后
ReentrantLock(可重入锁)
synchronize 也是可重入锁
synchronized 也是可重入锁,ReentrantLock有以下优点
1.支持获取锁的超时时间
2.获取锁时可被打断
3.可设为公平锁
4.可以有不同的条件变量,即有多个waitSet,可以指定唤醒
公平锁:多个线程按照申请锁的顺序去获得锁,线程会直接进入队列去排队,永远都是队列的第一位才能得到锁。
优点:所有的线程都能得到资源,不会饿死在队列中。
缺点:吞吐量会下降很多,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销会很大。非公平锁:多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:你们可能也发现了,这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁,导致饿死。
线程通信
wait+notify

当多线程竞争访问对象的同步方法时,锁对象会关联一个底层的Monitor对象(重量级锁的实现)
1)Thread-0先获取到对象的锁,关联到monitor的owner,同步代码块内调用了锁对象的wait()方法,调用后会进入waitSet等待,Thread-1同样如此,此时Thread-0的状态为Waitting
2)Thread2、3、4、5同时竞争,2获取到锁后,关联了monitor的owner,3、4、5只能进入EntryList中等待,此时2线程状态为 Runnable,3、4、5状态为Blocked
3)2执行后,唤醒entryList中的线程,3、4、5进行竞争锁,获取到的线程即会关联monitor的owner
4)3、4、5线程在执行过程中,调用了锁对象的notify()或notifyAll()时,会唤醒waitSet的线程,唤醒的线程进入entryList等待重新竞争锁
注意!
1.Blocked线程会在owner线程释放锁时唤醒,即处于Blocked的线程均可以竞争锁
2.Blocked状态和Waitting状态都是阻塞状态
3.wait和notify使用场景是必须要有同步,且必须获得对象的锁才能调用,使用锁对象去调用,否则会抛异常
park&unpark
LockSupport是juc下的工具类,提供了park和unpark方法,可以实现线程通信,与wait和notity相比的不同点如下
1.wait 和notify需要获取对象锁 park unpark不要
2.unpark 可以指定唤醒线程 notify随机唤醒
3.park和unpark的顺序可以先unpark wait和notify的顺序不能颠倒
线程池
优点
1.降低资源消耗,通过池化思想,减少创建线程和销毁线程的消耗,控制资源
2.提高响应速度,任务到达时,无需创建线程即可运行
3.提供更多更强大的功能,可扩展性高
构造器参数的意义

拒绝策略
1.调用者抛出RejectedExecutionException (默认策略)
2.让调用者运行任务
3.丢弃此次任务
4.丢弃阻塞队列中最早的任务,加入该任务
如何创建
1.newFixThreadPool
核心线程数 = 最大线程数;阻塞队列无界,存在OOM问题
2.newCacheThreadPool
核心线程数为0;最大线程数可以无限大;可能导致线程数过多,cpu负担太大
3.newSingleThreadPool
核心线程数 = 最大线程数 =1,阻塞队列无界,存在OOM问题
4.newScheduledThreadPool
任务调度的线程池 可以指定延迟时间调用,可以指定隔一段时间调用
以上默认实现的线程池都禁止使用
如何关闭
1.shutdown():会让线程池状态为shutdown,不能接收任务,但是会将工作线程和阻塞队列里的任务执行完 相当于优雅关闭
2.shutdownNow():直接中断所有的线程,并返回处于阻塞队列的线程
阻塞队列
阻塞队列是生产者消费者的实现具体组件之一。当阻塞队列为空时,从队列中获取元素的操作将会被阻塞,当阻塞队列满了,往队列添加元素的操作将会被阻塞。具体实现有:
ArrayBlockingQueue:底层是由数组组成的有界阻塞队列。
LinkedBlockingQueue:底层是由链表组成的有界阻塞队列。
PriorityBlockingQueue:阻塞优先队列。
DelayQueue:创建元素时可以指定多久才能从队列中获取当前元素
SynchronousQueue:不存储元素的阻塞队列,每一个存储必须等待一个取出操作
LinkedTransferQueue:与LinkedBlockingQueue相比多一个transfer方法,即如果当前有消费者正等待接收元素,可以把生产者传入的元素立刻传输给消费者。
LinkedBlockingDeque:双向阻塞队列。
ThreadLocal
ThreadLocal 是线程共享变量。ThreadLoacl 有一个静态内部类 ThreadLocalMap,其 Key 是 ThreadLocal 对象,值是 Entry 对象,ThreadLocalMap是每个线程私有的。
set 给ThreadLocalMap设置值。
get 获取ThreadLocalMap。
remove 删除ThreadLocalMap类型的对象。
存在的问题
对于线程池,由于线程池会重用 Thread 对象,因此与 Thread 绑定的 ThreadLocal 也会被重用,造成一系列问题。
内存泄漏。由于 ThreadLocal 是弱引用,但 Entry 的 value 是强引用,因此当 ThreadLocal 被垃圾回收后,value 依旧不会被释放,产生内存泄漏。