线程学习个人总结
1. 实现多线程的方式
- 实现Runnable接口,重写run方法
- 继承Thread类,重写run方法
2. synchronized关键字
- synchronized 修饰非静态方法时,取得是对象锁,因此不同线程访问,获取到的是不同的锁,数据互不影响。如需共享资源,需要定义该方法为静态方法。
- 当一个线程运行时,已进入一个被synchronized修饰的方法中时,调用另一个被sychoronized修饰的方法,叫做 锁重入
- 被修饰方法出现异常时,该线程锁持有的锁会自动释放
- 使用同步方法块方式(sychoronized(){})可以监视任意对象
- 懒汉式双重锁模式
- Java运行时,JVM会进行指令重排,提高程序运行效率,在单线程程序运行的时候,尽可能提高并行度,但多线程时,指令重排可能会导致 线程间变更通知未及时传递,对象未加载完成已传递、调用导致系统报错。该问题可通过在共享资源声明中加入volatile解决。
3. ThreadLocal
总结:
(1)ThreadLocal只是操作Thread中的ThreadLocalMap对象的集合;
(2)ThreadLocalMap变量属于线程的内部属性,不同的线程拥有完全不同的ThreadLocalMap变量;
(3)线程中的ThreadLocalMap变量的值是在ThreadLocal对象进行set或者get操作时创建的;
(4)使用当前线程的ThreadLocalMap的关键在于使用当前的ThreadLocal的实例作为key来存储value值;
(5) ThreadLocal模式至少从两个方面完成了数据访问隔离,即纵向隔离(线程与线程之间的ThreadLocalMap不同)和横向隔离(不同的ThreadLocal实例之间的互相隔离);
(6)一个线程中的所有的局部变量其实存储在该线程自己的同一个map属性中;
(7)线程死亡时,线程局部变量会自动回收内存;
(8)线程局部变量时通过一个 Entry 保存在map中,该Entry 的key是一个 WeakReference包装的ThreadLocal, value为线程局部变量,key 到 value 的映射是通过:ThreadLocal.threadLocalHashCode & (INITIAL_CAPACITY - 1) 来完成的;
(9)当线程拥有的局部变量超过了容量的2/3(没有扩大容量时是10个),会涉及到ThreadLocalMap中Entry的回收;
个人总结:
- ThreadLocal在添加的时候,实际上是将当前ThreadLocal对象作为Key,将数据作为Value存入一个Entry(ThreadLocal.ThreadLocalMap的内部类,Key、value结构)
- 多个线程间的ThreadLocal互不影响(纵向隔离),单个线程间不同的ThreadLocal互不影响(横向隔离)
- ThreadLocal.set()时,通过自己实现的算法(key.threadLocalHashCode & (len - 1))计算出存储位置
4. 线程通讯
- wait()
- 线程调用wait方法后,立即进入阻塞状态,代码停止在wait()处
- wait()的调用,必须在同步代码块中才能使用
- wait()被唤醒后,重新竞争锁,竞争到锁后才会执行wait()后面的代码
- notify()
- notify() 必须在同步方法或同步方法块中执行
- notify()方法是执行完方法后才会释放锁,被notify()唤醒的线程也是在执行后才竞争
- 如果有多个线程在等待阻塞状态,系统会随机挑选一个唤醒、竞争资源
- notifyAll()通知所以后等待线程开始竞争
5. Lock
5.1. 基本使用
Lock lock = new ReentrantLock();
lock.lock(); // 使用ReentrantLock加锁
…… 逻辑代码
lock.unlock(); // 释放锁
5.2 使用Lock对象实现线程通信
public class LockConditionDemo {
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
public static void main(String[] args) throws InterruptedException {
LockConditionDemo demo = new LockConditionDemo();
new Thread(() -> demo.await(demo.conditionA), "thread1_conditionA").start();
new Thread(() -> demo.await(demo.conditionB), "thread2_conditionB").start();
new Thread(() -> demo.signal(demo.conditionA), "thread3_conditionA").start();
System.out.println("稍等5秒再通知其他的线程!");
Thread.sleep(5000);
new Thread(() -> demo.signal(demo.conditionB), "thread4_conditionB").start();
}
private void await(Condition condition) {
try {
lock.lock();
System.out.println("开始等待await! ThreadName:" + Thread.currentThread().getName());
condition.await();
System.out.println("等待await结束! ThreadName:" + Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
private void signal(Condition condition) {
lock.lock();
System.out.println("发送通知signal! ThreadName:" + Thread.currentThread().getName());
condition.signal();
lock.unlock();
}
}
5.3 ReentrantReadWriteLock
5.3.1 特性
(1)读读共享;
(2)写写互斥;
(3)读写互斥;
(4)写读互斥;
5.3.2 代码演示
public class ReentrantReadWriteLockDemo {
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public static void main(String[] args) throws InterruptedException {
ReentrantReadWriteLockDemo demo = new ReentrantReadWriteLockDemo();
new Thread(() -> demo.read(), "ThreadA").start();
Thread.sleep(1000);
new Thread(() -> demo.write(), "ThreadB").start();
}
private void read() {
try {
try {
lock.readLock().lock();
System.out.println("获得读锁" + Thread.currentThread().getName()
+ " 时间:" + System.currentTimeMillis());
Thread.sleep(3000);
} finally {
lock.readLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
private void write() {
try {
try {
lock.writeLock().lock();
System.out.println("获得写锁" + Thread.currentThread().getName()
+ " 时间:" + System.currentTimeMillis());
Thread.sleep(3000);
} finally {
lock.writeLock().unlock();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
6. CyclicBarrier(循环屏障)
- 构造方法参数(int 需要多少线程等待,Runnable实现类(线程数满足后执行的代码))
- CyclicBarrier callMasterBarrier = new CyclicBarrier(num,() -> {})
- 每个线程运行完逻辑后加上以下代码,表示该线程已到屏障等待,callMasterBarrier.await();
- 相较CountDownLatch的优势:
- CountDownLatch只能使用一次,而CyclicBarrier可以使用reset()方法重置int
- CountDownLatch会阻塞主线程,而CyclicBarrier只会阻塞子线程
- CyclicBarrier的Api相对灵活(待自学)
7. 线程池
7.1. ThreadPoolExecutor 参数说明
-
acc : 获取调用上下文
-
corePoolSize: 核心线程数量,可以类比正式员工数量,常驻线程数量。
-
maximumPoolSize: 最大的线程数量,公司最多雇佣员工数量。常驻+临时线程数量。
-
workQueue:多余任务等待队列,再多的人都处理不过来了,需要等着,在这个地方等。
-
keepAliveTime:非核心线程空闲时间,就是外包人员等了多久,如果还没有活干,解雇了。
-
threadFactory: 创建线程的工厂,在这个地方可以统一处理创建的线程的属性。每个公司对员工的要求不一样,恩,在这里设置员工的属性。
-
handler:线程池拒绝策略,什么意思呢?就是当任务实在是太多,人也不够,需求池也排满了,还有任务咋办?默认是不处理,抛出异常告诉任务提交者,我这忙不过来了。
-
创建实例:
ExecutorService executorService = new ThreadPoolExecutor(5, 5,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(10),
® -> new Thread(r, r.getClass().getName()),
new ThreadPoolExecutor.AbortPolicy());for (int i = 0; i < 5; i++) { int index = i; executorService.submit(() -> System.out.println(index);); } executorService.shutdown(); // 注意!!!submit在报错时不会输出报错信息,有两种解决方案: // 1. 改用execute方法,但无返回值 // 2. 获取submit的返回值.var = Future对象,在调用Future对象的get()方法获取返回值
7.2. Execute源码分析:
-
比较线程池当前的数量和创建时设置的核心线程数(见上文参数说明),如果未达到设定值,会创建一个线程,并以传进来的Runable线程对象作为第一个执行任务执行;
-
如果线程正在运行,并且任务队列有能力接收任务,则插入线程,并重新监测线程池是否在运行,不在运行的话,将删除任务、执行拒绝策略;
-
如果添加临时线程失败,则执行拒绝策略
7.3. addWorker源码分析(循环执行)
——参数列表:1. Runnable对象 2. 是否核心线程
-
判断当前线程池状态,大等于SHUTDOWN状态,则不处理任务,返回False
-
通过传参判断添加核心还是临时线程,添加
-
判断执行到此处线程的状态,如线程状态与之前不一直,则跳过当前循环
-
用传递过来的Runable,作为参数创建Worker对象,获取线程、锁对象,通过ReentrantLock保证接下来将线程添加至线程池的过程的线程安全。
-
添加线程(本质是添加到HashSet)、关闭同步锁、执行线程
7.4. worker线程处理队列任务
-
如果当前传递进来的worker中第一次执行任务对象或者从队列中获取任务队伍不为空,利用Worker将执行过程加锁同步
-
执行前置钩子函数(允许用户继承线程池,重写钩子函数)
-
执行任务
-
执行后置钩子函数(允许用户继承线程池,重写钩子函数)