1. 线程基础
1.1 进程、线程
进程是程序运行资源分配的最小单位,资源包括:CPU、内存、IO。
线程是 CPU 调度的最小单位,必须依赖于进程存在,与同进程内的其他线程共享该进程的全部资源。
1.2 CPU 核心数、线程数
本来,CPU 核心数 = 线程数,Intel 使用超线程技术,使线程数翻倍,即下图的逻辑处理器。
1.3 CPU 时间片轮转
把 CPU 时间切片,分配给正在运行的线程,分配时发生上下文切换,时间大约为 20000 个 CPU 周期。
1.4 并行、并发
1.5 高并发好处、注意事项
好处:
- 充分利用 CPU 资源
- 加快用户响应时间
- 便于代码模块化、异步化、简单化
注意事项:
- 线程安全
- 线程死锁
- 线程太多会造成服务器资源耗宕机。Linux 限制一个进程总线程数 1000 个,Windows 限制一个进程总线程数 2000 个。
2. Java 线程基础
2.1 创建线程
有 2 种方式:Thread、Runnable。
Thread 是 Java 中对线程的抽象,Runnable 是对任务的抽象。
public class UserThread extends Thread {
@Override
public void run() {
System.out.println("new Thread");
}
public static void main(String[] args) {
new UserThread().start();
}
}
class UserRunnable implements Runnable {
public void run() {
System.out.println("new Thread");
}
public static void main(String[] args) {
UserRunnable userRunnable = new UserRunnable();
new Thread(userRunnable).start();
}
}
2.2 中止
stop 终结线程时不保证线程的资源正常释放,不建议使用。
interrupt:发起中断信号,即给线程置标志位。
isInterrupted:判断线程是否被中断。
Thread.interrupted:判断线程是否被中断,并清空中断标志位。
此处说明 JDK 中线程是协作式,而非抢占式。
public class EndThread {
private static class UseThread extends Thread {
public UseThread(String name) {
super(name);
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
System.out.println(threadName + " interrupt flag =" + isInterrupted());
while (!isInterrupted()) {
System.out.println(threadName + " is running");
}
System.out.println(threadName + " interrupt flag =" + isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("endThread");
endThread.start();
Thread.sleep(20);
endThread.interrupt();//中断线程,其实设置线程的标识位true
}
}
中断判断 while 循环中如果是 sleep,需要自己重新置中断标志位。
public class HasInterrputException {
private static class UseThread extends Thread {
public UseThread(String name) {
super(name);
}
@Override
public void run() {
while (!isInterrupted()) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + "interrupt flag is " + isInterrupted());
// 此时需要自己置中断标志位,否则不会跳出 while
interrupt();
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " I am extends Thread.");
}
System.out.println(Thread.currentThread().getName() + " interrupt flag is " + isInterrupted());
}
}
public static void main(String[] args) throws InterruptedException {
Thread endThread = new UseThread("HasInterruptEx");
endThread.start();
Thread.sleep(500);
endThread.interrupt();
}
}
不建议使用 cancle 变量的形式。
public class EndThread {
private static class UseThread extends Thread {
public UseThread(String name) {
super(name);
}
private boolean cancle;
@Override
public void run() {
String threadName = Thread.currentThread().getName();
while (isCancle()) {
System.out.println(threadName + " is running");
// wake(); 如果在此处 wake 了,没人唤醒,就不会判断 cancel,进而中断
}
System.out.println(threadName + " interrupt flag =" + isInterrupted());
}
public boolean isCancle() {
return cancle;
}
public void setCancle(boolean cancle) {
this.cancle = cancle;
}
}
public static void main(String[] args) throws InterruptedException {
UseThread endThread = new UseThread("endThread");
endThread.start();
Thread.sleep(20);
endThread.setCancle(true);
}
}
2.3 线程同步
1. synchronized
synchronized 有对象锁、类锁。
2. volatile
最轻量的同步机制:
- 保证可见性
- 不保证原子性
3. ThreadLocal
为每个线程提供一个变量副本,使数据实现了线程的隔离。
Spring 在实现事务时使用了 ThreadLocal。
get、set、remove
public class UseThreadLocal {
private static final ThreadLocal<Integer> intLocal
= new ThreadLocal<Integer>() {
@Override
protected Integer initialValue() {
return 1;
}
};
/**
* 运行 3 个线程
*/
public void StartThreadArray() {
Thread[] runs = new Thread[3];
for (int i = 0; i < runs.length; i++) {
runs[i] = new Thread(new TestThread(i));
}
for (int i = 0; i < runs.length; i++) {
runs[i].start();
}
}
/**
* 线程任务
*/
public static class TestThread implements Runnable {
int id;
public TestThread(int id) {
this.id = id;
}
public void run() {
System.out.println(Thread.currentThread().getName() + ":start");
Integer s = intLocal.get();
s = s + id;
intLocal.set(s);
System.out.println(Thread.currentThread().getName() + ":" + intLocal.get());
//intLocal.remove();
}
}
public static void main(String[] args) {
UseThreadLocal test = new UseThreadLocal();
test.StartThreadArray();
}
}
1. ThreadLocal 原理
ThreadLocal 中定义了静态内部类 ThreadLocalMap,Thread 中有个成员变量,变量类型就是这个 ThreadLocalMap。
ThreadLocalMap 中定义了 Entry 数组,每个 Entry 中保存 key(ThreadLocal)、value。
因为可定义多个 ThreadLocal 变量,也就是线程有多个 ThreadLocal 变量,所以存在 Entry 数组,通过 ThreadLocal 的值拿到 value。
2. ThreadLocal 引发内存泄露
强>软>弱>虚
软引用:如果垃圾回收完,发现内存还是不够用,就回收软引用。
弱引用:发生垃圾回收,就回收弱引用。
ThreadLocal 是弱引用,只要发生垃圾回收就会回收变量。
get、set 会有一定机会清除 ThreadLocal 为 null 的 Value。
public class ThreadLocalOOM {
private static final int TASK_LOOP_SIZE = 500;
final static ThreadPoolExecutor poolExecutor
= new ThreadPoolExecutor(5, 5,
1,
TimeUnit.MINUTES,
new LinkedBlockingQueue<>());
static class LocalVariable {
private byte[] a = new byte[1024 * 1024 * 5];/*5M大小的数组*/
}
final static ThreadLocal<LocalVariable> localVariable
= new ThreadLocal<>();
public static void main(String[] args) throws InterruptedException {
/** 5*5 */
for (int i = 0; i < TASK_LOOP_SIZE; ++i) {
poolExecutor.execute(new Runnable() {
public void run() {
//localVariable.set(new LocalVariable());
new LocalVariable();
System.out.println("use local varaible");
//localVariable.remove();
}
});
Thread.sleep(100);
}
System.out.println("pool execute over");
}
}
3. ThreadLocal 线程不安全
ThreadLocal 线程不安全是因为没使用好。
例如,给 ThreadLocal 设置一个静态的变脸值,也就是共享的,就会线程不安全,如下面的 static Number,并不是每个线程的该值是 1,因为都是共用了一个 number。
public class ThreadLocalUnsafe implements Runnable {
public Number number = new Number(0);
public void run() {
//每个线程计数加一
number.setNum(number.getNum() + 1);
//将其存储到ThreadLocal中
value.set(number);
SleepTools.ms(2);
//输出num值
System.out.println(Thread.currentThread().getName() + "=" + value.get().getNum());
}
public static ThreadLocal<Number> value = new ThreadLocal<Number>() {
};
public static void main(String[] args) {
for (int i = 0; i < 5; i++) {
new Thread(new ThreadLocalUnsafe()).start();
}
}
private static class Number {
public Number(int num) {
this.num = num;
}
private int num;
public int getNum() {
return num;
}
public void setNum(int num) {
this.num = num;
}
@Override
public String toString() {
return "Number [num=" + num + "]";
}
}
}
2.4 线程协作
wait、notify、notifyAll
等待、通知标准范式:wait 会释放锁,并且被唤醒后,wait 会重新去竞争锁。
synchronized (对象){
while (条件不满足){
对象.wait();
}
// 业务逻辑
}
synchronized (对象){
// doSth()
// 改变条件,使 wait 条件满足
对象.notify()/notifyAll();
}
1. 用等待超时模式实现一个连接池
public class DBPool {
private static LinkedList<Connection> pool = new LinkedList<Connection>();
/*初始化,限制连接池大小*/
public DBPool(int initialSize) {
if (initialSize > 0) {
for (int i = 0; i < initialSize; i++) {
pool.addLast(SqlConnectImpl.fetchConnection());
}
}
}
/*释放连接,通知其他的等待连接的线程*/
public void releaseConnection(Connection connection) {
if (connection != null) {
synchronized (pool) {
pool.addLast(connection);
//通知其他等待连接的线程
pool.notifyAll();
}
}
}
public Connection fetchConnection(long mills) throws InterruptedException {
synchronized (pool) {
//永不超时
if (mills <= 0) {
while (pool.isEmpty()) {
pool.wait();
}
return pool.removeFirst();
} else {
/*超时时刻*/
long future = System.currentTimeMillis() + mills;
/*等待时长*/
long remaining = mills;
while (pool.isEmpty() && remaining > 0) {
pool.wait(remaining);
/*唤醒一次,重新计算等待时长*/
remaining = future - System.currentTimeMillis();
}
Connection connection = null;
if (!pool.isEmpty()) {
connection = pool.removeFirst();
}
return connection;
}
}
}
}
2.5 其他
线程状态转换:
start、run:启动线程调用 start,在 start 中调用 start0,连续调用两次 start 会抛出异常。
yield:让线程让出 CPU 使用权,CPU 重新选择线程执行;但是不会让出锁。
join: A 调用 B 的 join,A 暂时挂起。
守护线程:setDaemon,非守护线程结束,守护线程自动结束;与子线程概念不同,子线程不是守护线程。
释放锁与否:
- 释放:wait
- 不释放:yield、sleep、notify