《Java多线程编程核心技术》第一章读书笔记
进程:正在执行的程序。进程是OS分配资源的最小单位。
线程:正在执行的程序的子任务。线程是程序执行的最小单位。
使用多线程也就是在使用异步。
同步是什么?同步的概念就是共享
异步是什么?异步的概念就是独立
执行stop()方法,强制停止了某个线程,可能导致某些重要的释放资源代码没有执行。
stop()方法会让线程释放同步锁。
调用线程的suspend方法,线程暂停,但是并不释放同步锁。
suspend方法极其容器造成死锁,因为它不释放同步锁
静态方法Thread#yield()方法,相当于一个不固定时间的sleep(),所以不释放同步锁。
A线程启动了B线程,那么B线程的优先级继承了A线程,所以B线程的优先级与A线程相同。
守护线程的作用是对其它线程提供服务。
如果程序中没有非守护线程了,那么守护线程就自动销毁了。典型的守护线程就是垃圾回收线程。
Java多线程编程核心技术-----第二章读书笔记
方法里面的变量是临时变量,在栈内创建。多次调用则多次创建临时变量,线程安全。
同步方法的同步锁是本对象,静态同步方法的同步锁是当前类的字节码文件,即.class文件。
当一个线程在得到对象锁,再次请求该对象锁是可以的。这就是synchronized锁的可重入性。
线程出现异常会自动释放同步锁。
同步不能被继承。需要为重写的方法加上synchronized修饰符,来保证同步。
即与synchronized(类.class)文件 本质相同。
避免使用字符串作为同步代码块的同步锁,因为字符串共享常量池。
同步代码块中的同步锁引用指向的是一个堆内存中的对象。只要这个对象所在的内存地址不变,也不影响此对象作为锁的使用。
volatile修饰的变量,内存存放在公共区,所有线程都能看到。让JVM禁止指令重排序优化
volatile关键字最致命的缺点是:不支持原子性。所以还需对变量的操作加锁。
synchronized在进入同步代码块之前,更新当前线程的工作内存,保证与公共内存的数据一致。
i++和 ++j 都是对变量进行自增操作,结果是i和j都自增。但是其表达式的值不一样,前者是i,后者是j+1。
Java多线程编程核心技术-----第三章读书笔记
调用wait()或notify()等方法的对象,必须是同步锁对象,否则抛出IllegalMonitorStateException.
wait()使当前执行代码的线程进入等待状态(冷冻暂停),并释放同步锁。
sleep方法不释放同步锁。
Object#wait
Thread#sleep
notify()随机使得一个wait状态的线程恢复执行。
notify()需要在同步代码中执行,此线程执行完才释放同步锁。
wait状态的线程必须要得到notify()或者notifyAll()才能被唤醒,否则一直处于wait状态。
wait()/notify() 要结合 Synchronized 关键字一起使用,因为他们都需要首先获取该对象的对象锁;
线程等待的原因
sleep()方法的睡眠时间到了
调用了wait()方法进入wait状态
线程重新进入可执行状态的情况:
sleep()方法的睡眠时间到了
线程被notify()唤醒了
wait(long)方法功能是:等待某一时间内是否有线程对其唤醒,超过此时间就自动唤醒。释放同步锁
多生产多消费出现假死。假死出现的主要原因是有可能连续唤醒同类,解决办法是将异类也一同唤醒。
伪代码:
生产者 if(有产品){lock.wait} 生产 lock.notify
如果有产品,我就休息。不然进行生产,并通知消费者消费。
消费者 if(没产品){lock.wait} 消费 lock.notify
如果没有产品,我就等待。不然的话就消费,消费完就通知生产。
package com.ssi.domains.Add;
/**
* Created by jay.zhou on 2018/7/24.
*/
public class Product {
public String state = "无货";
public void create() {
state = "有货";
}
public void consume() {
state = "无货";
}
public static void main(String[] args) {
Product product = new Product();
Object lock = new Object();
ThreadA threadA = new ThreadA(product,lock);
ThreadB threadb = new ThreadB(product,lock);
threadA.start();
threadb.start();
}
}
class ThreadA extends Thread {
private Product product;
private Object lock;
public ThreadA(Product product, Object lock) {
this.product = product;
this.lock = lock;
}
//生产者线程
@Override
public void run() {
while (true) {
try {
synchronized (lock) {
if (product.state.equals("有货")) {
lock.wait();
}
product.create();
System.out.println("生产者生产出货物");
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ThreadB extends Thread {
private Product product;
private Object lock;
public ThreadB(Product product, Object lock) {
this.product = product;
this.lock = lock;
}
//消费者线程
@Override
public void run() {
while (true) {
try {
synchronized (lock) {
if (product.state.equals("无货")) {
lock.wait();
}
product.consume();
System.out.println("消费者消费完货物");
lock.notify();
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
A线程的任务中出现 B.join(); 那么A线程需要等待B线程任务执行完毕再继续执行。线程A调用 otherThread.join()方法,那么线程A进入 WAITING 状态,内部使用的是 wait()方法实现,所以会释放同步锁。
ThreadLocal实现了每个线程内部都有自己的共享变量。
SimpleDateFormat是线程不安全的,解决方法是为每个线程创建一个sdf对象,而不是共享。
阿里巴巴:不要把SimpleDateFormat定义为静态变量,如果定义为静态变量,一定要加锁
继承ThreadLocal类,重写initialValue()方法。创建此MyThreadLocal类对象,调用get()返回值就是此键值对的初始值。
Java多线程编程核心技术-----第四章读书笔记
调用ReentrantLock对象的lock()方法获取锁,调用unlock()方法释放锁。
如果不调用lock.unlock()释放锁,那么即使线程任务结束,仍然不会释放锁。抛出异常也不会释放锁。
Condition对象通过ReentrantLock对象生成。Condition condition = lock.newCondition();
condition.await()方法让持有lock锁的对象暂停,并释放同步锁。
condition.signal();一次只唤醒一个暂停的线程。
condtion在调用await()方法和signal()方法之前,需要先调用lock.lock(),保证在同步环境中才可执行。
Lock lock = new ReentrantLock(boolean b);
如果boolean为true,那么此锁就是公平锁。公平体现在:先请求锁的线程优先获取锁。默认是非公平锁,进入可执行状态的线程,进行争夺同步锁
if(lock.tryLock()){ A代码; }else{ B代码; } 如果获取不到同步锁,那么就执行B了,不然干等等到什么时候。
if(lock.tryLock(3,TimeUnit.SECONDS)){ A代码; }else{ B代码; } 如果等了3秒还没有锁,那么执行B代码。
ReentrantReadWriteLock是读写锁。
多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥。
创建读写锁
ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
lock.readLock().lock();
lock.readLock().unlock();
lock.writeLock().lock()
lock.writeLock().unlock();
ReentranceLock 等 同步组件,内部都用了 队列同步器AQS 实现。
队列同步器(简称:同步器)AbstractQueuedSynchronizer(英文简称:AQS)
AQS使用了一个 int 成员变量表示同步状态,标记共享变量的状态(是否已经被其他线程锁住等)
AQS通过内置的 先进先出Node队列来完成资源获取和线程的排队工作。
Java多线程编程核心技术-----第五章读书笔记
JDK自带了一个定时任务类Timer类
Timer类的主要作用就是设置任务,TimerTask类用于封装任务。
Java并发编程的艺术-----第二章读书笔记
并发执行的速度有时不如串行,原因是线程有创建与上下文切换的开销。
如何减少上下文切换?
使用CAS操作进行无锁并发编程。多线程竞争锁的时候,会引起上下文切换。JUC包下的原子操作类,它们都是使用的CAS操作,可以避免使用同步锁。
dump可查看线程信息
编程中避免死锁的办法
避免一个线程同时获得多个锁
使用定时锁。
volatile关键字原理:将最先的变量的值写入到主内存中,使其它线程中缓存了该变量的内存地址失效
锁一共有4中状态,级别从低到高分别是:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态。
原子操作:不可被中断的操作。
Java是如何实现原子操作的:循环执行CAS操作,直到成功为止。
ABA问题:使用带版本号(时间戳)的原子操作类来避免。
for(;;){
int currentValue = getCurrentValue();
boolean succ = automicInteger.compareAndSet(currentValue,++currentValue);
if(succ){
break;
}
}
Java并发编程的艺术-----第三章读书笔记
Java线程之间的通信由Java内存模型(JMM)控制
每个线程都有一个私有的本地内存
image.png
Java并发编程的艺术-----第四章读书笔记
如何查看线程的状态?
在终端输入 jps ,我们运行的Java类的名字是ThreadState。所以JPS可以看到我的Java代码的进程ID为3068,
image.png
再通过 jstack 3068 查看进程的中的所有线程信息。
如下图可以发现,线程名为"BolckedThreadB"的线程的状态为 TIMED_WAITING ,说明此线程调用了 Thread.sleep()方法。
image.png
为什么 stop()、suspend()方法被废弃。因为这两个方法一个立即释放同步锁(导致脏读,资源没清理),一个不释放同步锁(导致死锁)。
关键字synchronized表示:同一时刻,只能有一个线程处于临界区中。它能保证变量的可见性与排他性。
可见性分析:在释放锁的时候,将线程私有变量刷新到主存。获取锁的时候,将私有内存中变量的值废弃,重新从主存中获取最新变量的值。
读写锁通过分离读锁与写锁,使得并发性能比一般其它的排他锁有了很大提升。
网友评论