知识点1-线程中断
public class LearnThread {
public static void main(String[] args) {
Runnable task1 = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("task1" + i);
}
};
Runnable task2 = () -> {
for (int i = 0; i < 5; i++) {
System.out.println("task2" + i);
}
};
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
thread1.start();
thread2.start();
// 中断线程
// 注意:当线程设置为中断true,如果线程被一个sleep调用阻塞,则会抛出一个InterruptedException
thread1.interrupt();
// 静态方法,检查当前线程是否中断,并清除当前线程中断状态从true->false
Thread.interrupted();
// 获取当前线程对象并判断状态是否中断
System.out.println(Thread.currentThread().isInterrupted());
// 线程状态
System.out.println(thread1.isInterrupted());
System.out.println(thread2.isInterrupted());
}
}
知识点2-守护进程
守护线程可以通过调用t.setDaemon(true)将一个线程转换为守护线程,守护线程的唯一的用途是为其他线程提供服务。计时器线程就是定时地发送“计时器抵达”信号给其他线程,另外清空过时缓存项的线程也是守护线程。当只剩下守护线程时,虚拟机就会退出,因为只剩下守护线程,就没必要继续运行了;且该方法必须在线程启动前调用;
知识点3-线程组和异常处理器
public class LearnThread {
public static void main(String[] args) {
Runnable task1 = () -> {
for (int i = 0; i < 3; i++) {
System.out.println("task1" + i);
}
};
Runnable task2 = () -> {
List list = new ArrayList();
list.get(1);
for (int i = 0; i < 3; i++) {
System.out.println("task2" + i);
}
};
Thread thread1 = new Thread(task1);
Thread thread2 = new Thread(task2);
// 异常处理器
System.out.println(thread1.getUncaughtExceptionHandler());
thread2.setUncaughtExceptionHandler(new LearnUncaughtExceptionHandle());
System.out.println(thread2.getUncaughtExceptionHandler());
// 线程组
LearnThreadGroup threadGroup = new LearnThreadGroup("test");
threadGroup.uncaughtException(thread2, new RuntimeException());
thread1.start();
thread2.start();
}
}
public class LearnUncaughtExceptionHandle implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("线程异常抛出");
}
}
public class LearnThreadGroup extends ThreadGroup {
public LearnThreadGroup(String name) {
super(name);
}
public LearnThreadGroup(ThreadGroup parent, String name) {
super(parent, name);
}
@Override
public void uncaughtException(Thread t, Throwable e) {
System.out.println("LearnThreadGroup 线程异常抛出");
}
}
知识点4-线程优先级
知识点5-i++
i++ 不是原子操作,无法保证线程安全,包含读取累加再写入等操作;
知识点6-同步机制
修饰词synchronized,重入锁ReentrantLock(),公平锁ReentrantLock(true);公平锁比常规锁慢很多;
知识点7-条件对象
import java.util.Arrays;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* 排他锁,条件对象使用场景
*
* @author zhuangshd
* @date 2022/3/1 22:28
*/
public class Bank {
/**
* 银行账号
*/
private final double[] accounts;
/**
* 唯一锁
*/
private Lock bankLock;
/**
* 条件对象
*/
private Condition sufficientFunds;
public Bank(int n, double initialBalance) {
// 初始化银行账号、存款
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
// 初始化重入锁
bankLock = new ReentrantLock();
// 初始化条件对象
sufficientFunds = bankLock.newCondition();
}
/**
* 转账方法
*/
public void transfer(int from, int to, double amount) throws InterruptedException {
// 加锁
bankLock.lock();
try {
while (accounts[from] < amount)
// 条件对象,当条件不满足线程进入等待集,等待激活校验
sufficientFunds.await();
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf("%10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
// 激活,线程进行条件校验
sufficientFunds.signalAll();
} finally {
// finally 解锁,防止异常不释放
bankLock.unlock();
}
}
/**
* 获取账户总合
*/
public double getTotalBalance() {
// 加锁
bankLock.lock();
try {
double sum = 0;
for (double a : accounts) {
sum += a;
}
return sum;
} finally {
// 解锁
bankLock.unlock();
}
}
public int size() {
return accounts.length;
}
}
/**
* @author zhuangshd
* @date 2022/3/1 22:47
*/
public class LockBankTest {
public static final int ACCOUNTS = 10;
public static final int MAX_AMOUNT = 1000;
public static void main(String[] args) {
Bank bank = new Bank(ACCOUNTS, MAX_AMOUNT);
for (int i = 0; i < ACCOUNTS; i++) {
int fromAccount = i;
Runnable r = () -> {
try {
while (true) {
int toAccount = (int) (bank.size() * Math.random());
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (10 * Math.random()));
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
};
Thread thread = new Thread(r);
thread.start();
}
}
}/**
* @author zhuangshd
* @date 2022/3/1 22:47
*/
public class LockBankTest {
public static final int ACCOUNTS = 10;
public static final int MAX_AMOUNT = 1000;
public static void main(String[] args) {
Bank bank = new Bank(ACCOUNTS, MAX_AMOUNT);
for (int i = 0; i < ACCOUNTS; i++) {
int fromAccount = i;
Runnable r = () -> {
try {
while (true) {
int toAccount = (int) (bank.size() * Math.random());
double amount = MAX_AMOUNT * Math.random();
bank.transfer(fromAccount, toAccount, amount);
Thread.sleep((int) (10 * Math.random()));
}
} catch (InterruptedException ex) {
ex.printStackTrace();
}
};
Thread thread = new Thread(r);
thread.start();
}
}
}
知识点8-synchronized关键字
①每个对象都有一个内部锁,而wait/notifyAll/notify方法是Object类的final方法,Condition方法必须命名为await/signalAll/signal从而不会与那些方法冲突;
②将静态方法修饰为同步也是合法的,它会获得相关类对象的内部锁,会锁定该方法或任何其他同步静态方法;
③内部锁和条件存在一些限制,包括:不能中断一个正在尝试获得锁的线程,不能指定尝试获取锁时的超时时间,每个锁仅有一个条件可能是不够的;
④同步方法推荐使用梯度,首先考虑java.util.concurent包中的某种机制,其次考虑synchronized关键字修饰程序,最后再使用Lock/condition;
/**
* 每个对象都有一个内部锁
*
* @author zhuangshd
* @date 2022/3/1 23:15
*/
public class SyncBank {
/**
* 银行账号
*/
private final double[] accounts;
public SyncBank(int n, double initialBalance) {
// 初始化银行账号、存款
accounts = new double[n];
Arrays.fill(accounts, initialBalance);
}
/**
* 转账方法
*/
public synchronized void transfer(int from, int to, double amount) throws InterruptedException {
while (accounts[from] < amount)
// synchronized 仅有一个条件对象,当条件不满足线程进入等待,等待激活校验
wait();
System.out.print(Thread.currentThread());
accounts[from] -= amount;
System.out.printf("%10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
// 激活,线程进行条件校验
notifyAll();
}
/**
* 获取账户总合
*/
public synchronized double getTotalBalance() {
double sum = 0;
for (double a : accounts) {
sum += a;
}
return sum;
}
public int size() {
return accounts.length;
}
}
知识点9-同步块
使用一个对象的锁来实现额外的原子操作,称为客户端锁定;
synchronized(accounts){
accounts[from] -= amount;
System.out.printf("%10.2f from %d to %d", amount, from, to);
accounts[to] += amount;
System.out.printf(" Total Balance: %10.2f%n", getTotalBalance());
}
知识点10-监视器概念
知识点11-volatile字段
volatile关键字为实例字段的同步访问提供了一个免锁机制,如果声明一个字段为volatile,那么编译器和虚拟机就知道该字段可能被另外一个线程并发更新。编译器会插入适当代码,以确保如果一个线程对变量的修改,对读取这个变量的其他线程都可见。volatile变量不能提供原子性,i++,不能保证读取、翻转、写入不被中断;
知识点12-final变量
用final修饰的变量可作为共享变量,可以保证其他线程看到变量更新后的值,但是映射操作不是线程安全的,如果有多个线程修改或读取这个映射,仍然需要同步;
知识点13-原子性
java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令来保证其他操作的原子性;AtomicInteger/AtomicIntergerArray/LongAddr等等;
知识点14-死锁
知识点15-线程局部变量
SimleDateFormat不是线程安全的,多线程下,内部数据结构(?)可能会被并发破坏,做同步synchronized开销太大,局部变量SimleDateFormat太浪费(内存?);
public class LearnThreadLocal {
// ThreadLoacl辅助类
public static final ThreadLocal<SimpleDateFormat> dateFormat = ThreadLocal.withInitial(() -> new SimpleDateFormat("YYYY-MM-dd"));
public static void main(String[] args) {
// get方法会返回当前线程的那个实例
System.out.println(dateFormat.get().format(new Date()));
}
}
知识点16-线程安全的集合
java.util.concurrent包
①阻塞队列,LinkedBlockingQueue,PriorityBlockingQueue;使用offer/poll/peek方法,队列容量没有上线,在并发操作上可以使用队列来实现线程问题;
②映射,ConcurrentHashMap,ConcurrentSkipListMap;线程安全,16,0.75;批操作search/reduce/forEach
③有序集,ConcurrentSkipListSet
/**
* concurrent包下线程安全集合
*
* @author zhuangshd
* @date 2022/3/3 22:55
*/
public class LearnConcurrent {
public static void main(String[] args) {
// 阻塞队列
LinkedBlockingQueue linkedBlockingQueue = new LinkedBlockingQueue<String>();
System.out.println(linkedBlockingQueue.offer("test"));//插入元素
System.out.println(linkedBlockingQueue.poll());//移除并返回列头元素
System.out.println(linkedBlockingQueue.peek());//返回队列头元素
// 优先队列
PriorityQueue priorityQueue = new PriorityQueue<>();
// 映射
ConcurrentHashMap concurrentHashMap = new ConcurrentHashMap<>();
ConcurrentSkipListMap concurrentSkipListMap = new ConcurrentSkipListMap<>();
// 集合
ConcurrentSkipListSet concurrentSkipListSet = new ConcurrentSkipListSet<>();//底层ConcurrentHashMap
}
}
知识点17-串行数组算法-parallel
知识点18-Callable和Futrue
异步运行的任务但是有返回值;接口Callable是参数化类型的接口,只有方法call,参数类型是返回值的类型,当Callable
public static void main(String[] args) throws Exception {
Callable<Integer> task = () -> {
Thread.sleep(1000L);
return 1;
};
FutureTask<Integer> futureTask = new FutureTask<>(task);
Thread thread = new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
知识点19-执行器
执行器拥有许多静态工程方法用来构造线程池;例如:newCacheThreadPoll/newFixedThreadPoll等,在最优情况下,并发线程数等于处理器内核数;
调用submit提交Runable或Callable对象,会得到一个Future对象,得到结果或者取消线程任务;
对一个任务进行分解,通过执行器创建线程池来完成,使用ExecutorService的等待其中一个任务完成invokeAny()或者全部任务完成invokeAll(),阻塞式完成任务;结果集List
知识点20-异步计算-可完成Futrue
CompletableFuture
知识点21-回调中的长时间运行任务
当应用程序有界面时,需要使用线程来提高程序的响应性能,当遇到耗时工作时,不能在当前页面完成工作,需要启动另外一个工作线程;(下载文件等)