线程
进程:操作系统进行资源调度和分配的基本单位(例如浏览器,APP,JVM)。
线程:进程中的最小执行单位(可以理解为一个顺序的执行流),同一个进程内的多个线程共享资源。
并行和并发
并发:多线程抢占CPU,可能不同时执行,侧重于多个任务交替执行。
现在的操作系统无论是windows,linux还是macOS等其实都是多用户多任务分时操作系统,使用这些操作系统的的用户可以“同时”干多件事情。但实际上,对于单机CPU的计算机而言,在同一时间只能干一件事,为了看起来像是“同时干多件事”分时操作系统把CPU的时间划分成了长短进本相同的时间区间,即“时间片”,通过操作系统的管理,把时间片依次轮流的分配给各个线程任务使用。我们看似的“同时干多件事”,其实是通过CPU时间片技术并发完成的。例如:多个线程并发使用一个CPU资源并发执行任务的线程时序图。

并行:线程不共享CPU,每个线程一个CPU同时执行多个任务。并行只出现在多CPU或多核CPU中
生命周期
一个线程从创建,运行,到最后销毁的这个过程称之为线程的生命周期,这些状态可归纳为:状态分别为新建状态,就绪状态,运行状态,阻塞状态,死亡状态。如下几个状态:

上下文切换,一个线程得到CPU执行的时间是有限的。当此线程用完为其分配的CPU时间以后,cpu会切换到下一个线程执行。但是在这之前,线程需要将当前的状态进行保存,以便下次再次获得CPU时间片时可以加载对应的状态以继续执行剩下的任务。而这个切换过程是需要耗费时间的,会影响多线程程序的执行效率,所以在在使用多线程时要减少线程的频繁切换。减少多线程上下文切换的方案如下:
无锁并发编程:锁的竞争会带来线程上下文的切换
CAS算法:CAS算法在数据更新方面,可以达到锁的效果
使用最少线程:避免不必要的线程等待
使用协程:单线程完成多任务的调度和切换,避免多线程
线程安全
多个线程并发执行时,仍旧能够保证数据的正确性,这种现象称之为线程安全。
多个线程并发执行时,不能能够保证数据的正确性,这种现象称之为线程不安全。
产生的因素
多个线程并发执行。
多个线程并发执行时存在共享数据集(临界资源)。
多个线程在共享数据集上的操作不是原子操作。
保证线程安全方法
对共享进行限制访问(例如加锁:syncronized,Lock)
基于CAS实现非阻塞同步(基于CPU硬件技术支持)
取消共享,每个线程一个对象实例(例如threadlocal)
锁
JAVA中为了保证多线程并发访问的安全性,提供了基于锁的应用,大体可归纳为两大类,即悲观锁和乐观锁。
悲观锁:假定会发生并发冲突,屏蔽一切可违反数据完整性的操作,例如java中可以基于syncronized,Lock,ReadWriteLock等实现。适合写操作多的场景,先加锁可以保证写操作时数据正确。
class Counter{
private int count;
public synchronized int count() {
count++;
return count;
}
}
乐观锁实现:假设不会发生冲突,只在提交操作时检查是否违反数据完整性,例如java中可借助CAS( Compare And Swap)算法实现(此算法依赖硬件CPU)。适合读操作多的场景,不加锁的特点能够使其读操作的性能大幅提升
class Counter{
private AtomicInteger at=new AtomicInteger();
public int count() {
return at.incrementAndGet();
}
}
多个线程互相等待已经被对方线程正在占用的锁,导致陷入彼此等待对方释放锁的状态,这个过程称之为死锁。避免死锁的思路如下
避免一个线程中同时获取多个锁
避免一个线程在一个锁中获取其他的锁资源
考虑使用定时锁来替换内部锁机制,如lock.tryLock(timeout)。
通讯
线程通讯:java中的多线程通讯主要是共享内存(变量)等方式。
进程通讯:java中进程通讯(IPC)主要是Socket,MQ等。

基于wait/nofity/notifyall实现线程通讯
Wait:阻塞正在使用监视器对象的线程,同时释放监视器对象
notify: 唤醒在监视器对象上等待的单个线程,但不释放监视器对象,此时调用该方法的代码继续执行,直到执行结束才释放对象锁
notifyAll: 唤醒在监视器对象上等待的所有线程,但不释放监视器对象,此时调用该方法的代码继续执行,直到执行结束才释放对象锁
使用wait/notify/notifyAll的作用一般是为了避免轮询带来的性能损失。
基于Condition实现线程通讯
Condition 是一个用于多线程间协同的工具类,基于此类可以方便的对持有锁的线程进行阻塞或唤醒阻塞的线程。它的强大之处在于它可以为多个线程间建立不同的Condition,通过signal()/signalall()方法指定要唤醒的不同线程。
基于Lock对象获取Condition对象
基于Condition对象的await()/signal()/signalall()方法实现线程阻塞或唤醒。
ThreadLocal
线程本地变量,访问这个变量的每个线程都会有这个变量的一个本地拷贝。
作用,通过这种机制,可以保证一个对象在当前线程只有一份,并且取消了线程间的共享,是线程安全的。
可以将某个对象存储到当前线程的ThreadLocalMap中
可以从当前线程的ThreadLocalMap中获取到指定对象
原理

ThreadLocal可能会导致内存泄漏,因为线程中ThreadLocalMap内部对key的引用是弱引用(WeakReference),这个弱引用应用的key可能在GC时,就被移除了.但是value是强引用,key假如被GC了,那value也就不能访问到了,但他还占用着内存,所以它会导致内存泄漏。
线程池
线程池本质上就是一个存储了多个线程的容器,容器中的线程可以实现复用。这种设计一种享元设计(享元模式)。使用线程池的原因
线程对象的创建和销毁非常耗时
复用线程,减少线程创建和销毁次数。
创建
基于Executors类中的方法进行池的创建(不推荐)
基于ThreadPoolExecutor创建(推荐)
进行池设计
核心线程池数
最大线程数
阻塞式任务队列
空闲线程的释放
创建线的工厂
任务拒绝策略
public class ThreadPoolExecutorTests {
/**核心线程数*/
static int corePoolSize=2;
/**最大线程数*/
static int maximumPoolSize=3;
/**空闲线程释放时间*/
static int keepAliveTime=60;
/**阻塞式任务队列*/
static BlockingQueue<Runnable> workQueue=new ArrayBlockingQueue<>(1);
/**创建线程的工厂*/
private static AtomicLong atomicLong=new AtomicLong(1);
static ThreadFactory threadFactory=new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r, "tedu-2207-"+atomicLong.getAndIncrement());
}
};
/**拒绝策略:
* 1)AbortPolicy 直接拒绝,抛出异常
* 2)DiscardPolicy 直接丢弃
* 3)callerRunsPolicy 由调用着线程执行。
* 4)DiscardOldestPolicy 丢弃队列中最早放入的任务,将当前任务交给线程池。
* */
static RejectedExecutionHandler handler=
new ThreadPoolExecutor.CallerRunsPolicy();
public static void main(String[] args) {
ThreadPoolExecutor poolExecutor =
new ThreadPoolExecutor(corePoolSize,//2
maximumPoolSize,
keepAliveTime,
TimeUnit.SECONDS,
workQueue,//核心线程都在忙则任务会存储到这个队列
threadFactory,
handler);
}
}
执行逻辑
