MyThread01 线程创建
package com.yenroc.ho.多线程;
public class MyThread01 {
public static void main(String[] args) throws InterruptedException {
System.out.print(Thread.currentThread().getName() + "\tmain running..\n");
// 线程类start 执行线程类的run 方法
Thread t = new MyThread();
// t.setDaemon(true); // 把该线程标记为守护线程
t.start();// 启动一个线程
// java 8 lambda 表达式简写
Thread t2 = new Thread(()->{
System.out.print(Thread.currentThread().getName() + "\tlambda Thread running..\n");
try {
Thread.sleep(100L);
} catch (java.lang.InterruptedException e){
}
System.out.print(Thread.currentThread().getName() + "\tlambda Thread end..\n");
});
t2.start();
// 执行实现Runnable类。
Thread t3 = new Thread(new MyRunnable());
t3.start();
t2.join();// 只有当t2 线程执行完,main 线程才会继续
// t.interrupt(); // 中断t线程
System.out.print(Thread.currentThread().getName() + "\tmain end..\n");
}
/**
* 线程的状态:
* New: 新建
* Runnable : 运行中的线程
* Blocked: 运行中的线程,因为某些操作被阻塞挂起
* waiting: 运行中的线程,因为某写操作在等待中
* Timed Waiting:运行中的线程,因为执行sleep()方法正在计时等待
* Terminated: 终止的线程
*/
private void threadStatus() {}
}
// 1.继承Thread 类,重写run方法
class MyThread extends Thread {
@Override
public void run() {
System.out.print(Thread.currentThread().getName() + "\tMyThread class is running..\n");
try {
Thread.sleep(500L);
} catch (java.lang.InterruptedException e){
}
System.out.print(Thread.currentThread().getName() + "\tMyThread class is end..\n");
}
}
// 2. 实现Runnable接口,
class MyRunnable implements Runnable {
@Override
public void run() {
System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is running..\n");
try {
Thread.sleep(500L);
} catch (java.lang.InterruptedException e){
}
System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is end..\n");
}
}
创建线程
Thread t = new MyThread();// 编写继承Thread类的实现类MyThread,实现run方法
Thread t2 = new Thread(()->{方法体});// 根据lambda 表达式简写
Thread t3 = new Thread(new MyRunnable());// 执行实现Runnable的类
t2.join();// 只有当t2 线程执行完,主线程才会继续
中断线程运行
t.interrupt(); // 中断t线程的执行
守护线程
t.setDaemon(true); // 把该线程标记为守护线程,需要注意守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失
volatile
volatile关键字解决了共享变量在线程间的可见性问题:当一个线程修改了某个共享变量的值,其他线程能够立刻看到修改后的值。
在x86的架构下,JVM回写主内存的速度非常快,但是,换成ARM的架构,就会有显著的延迟
MyThread02 线程同步
package com.yenroc.ho.多线程;
/**
* synchronized 关键字保证一段代码的原子性
*/
public class MyThread02 {
public static int count = 0;
public static void main(String[] args) throws InterruptedException {
Thread addThread = new Thread(()->{
for (int i=0; i<10000; i++) {
synchronized (MyThread02.class) {
count += 1;
}
}
});
Thread decThread = new Thread(()->{
for (int i=0; i<10000; i++) {
synchronized (MyThread02.class) {
count -= 1;
}
}
});
addThread.start();
decThread.start();
addThread.join();
decThread.join();
System.out.print(Thread.currentThread().getName() + "\t count=" + count);
}
}
synchronized
使用synchronized 关键字保证一段代码的原子性。
可重入锁
JVM允许同一个线程重复获取同一个锁,这种能被同一个线程反复获取的锁,就叫做可重入锁
由于Java的线程锁是可重入锁,所以,获取锁的时候,不但要判断是否是第一次获取,还要记录这是第几次获取。每获取一次锁,记录+1,每退出synchronized块,记录-1,减到0的时候,才会真正释放锁
死锁的产生
线程1 获取A锁之后,继续获取B锁。。
线程2 获取B锁之后,继续获取A锁。。
当线程1 获取A锁后,线程2同时获取到B锁,此时线程1需要获取B锁,线程2需要获取A锁。
此时,两个线程各自持有不同的锁,然后各自试图获取对方手里的锁,造成了双方无限等待下去,这就是死锁。
避免死锁: 线程2的逻辑优化成先获取A锁再获取B锁。。
MyThread03 多线程协调wait和notify
package com.yenroc.ho.多线程;
import java.util.LinkedList;
import java.util.Queue;
/**
* 多线程间的协调:wait().notify()
*/
public class MyThread03 {
public static void main(String[] args) {
QueueList<String> queueList = new QueueList<>();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(()->{
String s = queueList.get();
System.out.print(Thread.currentThread().getName() + "\tget s="+s+" \n");
});
t.start();
}
for (int i = 0; i < 10; i++) {
Thread t = new Thread(()->{
queueList.add(Thread.currentThread().getName());
System.out.print(Thread.currentThread().getName() + "\t add s="+Thread.currentThread().getName()+" \n");
});
t.start();
}
}
}
class QueueList<T> {
private Queue<T> queue = new LinkedList();
public synchronized void add(T t){
queue.add(t);
// 使用notifyAll()将唤醒所有当前正在this锁等待的线程,而notify()只会唤醒其中一个
this.notifyAll();// 这个方法会唤醒一个正在this锁等待的线程
}
public synchronized T get() {
while (queue.isEmpty()) {
try {
this.wait();//当一个线程在this.wait()等待时,它就会释放this锁,从而使得其他线程能够在add()方法获得this锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
return queue.remove();
}
}
wait和notify用于多线程协调运行
当一个线程在this.wait()等待时,它就会释放this锁,从而使得其他线程能够在add()方法获得this锁
这个方法会唤醒一个正在this锁等待的线程
在synchronized内部可以调用wait()使线程进入等待状态;
必须在已获得的锁对象上调用wait()方法;
在synchronized内部可以调用notify()或notifyAll()唤醒其他等待线程;
必须在已获得的锁对象上调用notify()或notifyAll()方法;
已唤醒的线程还需要重新获得锁后才能继续执行
java.util.concurrent
接口 | 线程不安全 | 线程安全 |
---|---|---|
List | ArrayList | CopyOnWriteArrayList |
Map | HashMap | ConcurrentHashMap |
Set | HashSet / TreeSet | CopyOnWriteArraySet |
Queue | ArrayDeque / LinkedList | ArrayBlockingQueue / LinkedBlockingQueue |
Deque | ArrayDeque / LinkedList | LinkedBlockingDeque |
Atomic
Atomic类是通过无锁(lock-free)的方式实现的线程安全(thread-safe)访问。
它的主要原理是利用了CAS.
Atomic的类在 java.util.concurrent.atomic 包下面,如:AtomicBoolean,AtomicInteger,AtomicLong...
CAS(compare-and-swap)
乐观锁主要采用CAS算法,
思想:获取当前变量最新值A(预期值),然后进行CAS操作。
此时如果内存中变量的值V(内存值V)等于预期值A,说明没有被其他线程修改过,
我就把变量值改成B(更新值);如果不是A,便不再修改。
CAS的缺点:
1. ABA问题:可以参考数据库乐观锁的处理,加版本号
2. 自旋时间长带来性能消耗: (自旋)重试策略,
代码简单实现说明:
// ++i 的实现
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
// 其中 compareAndSet方法:
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
// 其中unsafe.compareAndSwapInt 方法类似:
if (this == expect) {
this = update
return true;
} else {
return false;
}
使用java.util.concurrent.atomic提供的原子操作可以简化多线程编程
原子操作实现了无锁的线程安全;
适用于计数器,累加器等
MyThread04 线程池
package com.yenroc.ho.多线程;
import java.util.Date;
import java.util.concurrent.*;
/**
* 线程池
*/
public class MyThread04 {
public static void main(String[] args) {
// 创建线程池:创建这些线程池的方法都被封装到Executors这个类中
// 固定大小的线程池
ExecutorService executors = Executors.newFixedThreadPool(3);
// 动态线程池 ExecutorService executors = Executors.newCachedThreadPool();
// ExecutorService executors = new ThreadPoolExecutor(4,64,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
// 反复执行的线程池(定时执行,线程池不要shutdown)
ScheduledExecutorService sExecutors = Executors.newScheduledThreadPool(2);
// 延迟1s后只执行一次
sExecutors.schedule(new MyTaskThread(),1, TimeUnit.SECONDS);
// 延迟2秒后开始执行定时任务,每3秒执行(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,那么则每次执行间隔是5s)
sExecutors.scheduleAtFixedRate(new MyTaskThread(),2, 3, TimeUnit.SECONDS);
// 2秒后开始执行定时任务,以3秒为间隔执行:(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,则需要再间隔3s,5+3s后执行)
sExecutors.scheduleWithFixedDelay(new MyTaskThread(),2, 3, TimeUnit.SECONDS);
for (int i = 0; i < 10; i++) {
Thread t = new MyTaskThread();
executors.submit(t);
}
System.out.println("main Thread end.. ");
// 关闭线程池:
executors.shutdown();
}
}
class MyTaskThread extends Thread {
@Override
public void run() {
System.out.printf("线程[%s]\t 当前时间=[%s] \n",Thread.currentThread().getName(), new Date());
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
Executors 类进行创建线程池
线程池在程序结束的时候要关闭。使用shutdown()方法关闭线程池的时候,它会等待正在执行的任务先完成,然后再关闭
FixedThreadPool:线程数固定的线程池;
CachedThreadPool: 会根据任务数量动态调整线程池的大小
可以通过ExecutorService executors = new ThreadPoolExecutor(4,64,60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>())
ScheduledThreadPool: 反复执行的线程池
// 延迟1s后只执行一次
executors.schedule(new MyTaskThread(),1, TimeUnit.SECONDS);
// 延迟2秒后开始执行定时任务,每3秒执行(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,那么则每次执行间隔是5s)
executors.scheduleAtFixedRate(new MyTaskThread(),2, 3, TimeUnit.SECONDS);
// 延迟2秒后开始执行定时任务,以3秒为间隔执行:(当上一个线程执行完成才会继续下一个执行,如果线程执行5s结束,则需要再间隔3s,5+3s后执行)
// 上一次任务执行完毕后,等待固定的时间间隔,再执行下一次任务
executors.scheduleWithFixedDelay(new MyTaskThread(),2, 3, TimeUnit.SECONDS);
MyThread05 返回结果的线程池
package com.yenroc.ho.多线程;
import java.util.concurrent.*;
/**
* 带返回结果的线程,Future,CompletableFuture
*/
public class MyThread05 {
public static void main(String[] args) throws Exception {
// 1. Future
ExecutorService executors = Executors.newFixedThreadPool(3);
Future<String> myTaskThreadCallableFuture = executors.submit(new MyTaskThreadCallable());
executors.submit(new My05Runnable());
// 执行get 会阻塞线程,需要放到下面执行
System.out.println(myTaskThreadCallableFuture.get(550L,TimeUnit.MILLISECONDS));
executors.shutdown();
// 2. CompletableFuture
CompletableFuture<String> stringCompletableFuture = CompletableFuture.supplyAsync(() -> { // Supplier 接口 T get();
System.out.println("CompletableFuture 执行逻辑。。。");
try {
Thread.sleep(500L);
} catch (InterruptedException e) {
e.printStackTrace();
}
// int i = 1/0; // 进入exceptionally 打印CompletableFuture 执行逻辑异常,异常信息=[java.lang.ArithmeticException: / by zero]
return "success";
});
stringCompletableFuture.thenAccept((result)->{// Consumer 接口 void accept(T t);
System.out.printf("stringCompletableFuture 执行逻辑成功,返回结果=[%s] \n", result);
});
stringCompletableFuture.exceptionally(e->{// Function 接口 R apply(T t);
System.out.printf("stringCompletableFuture 执行逻辑异常,异常信息=[%s] \n", e.getMessage());
return null;
});
// anyOf()可以实现“任意个CompletableFuture只要一个成功”,allOf()可以实现“所有CompletableFuture都必须成功”,这些组合操作可以实现非常复杂的异步流程控制
CompletableFuture<String> stringCompletableFuture2 = CompletableFuture.supplyAsync(() -> {
try {
Thread.sleep(800L);
} catch (InterruptedException e) {
e.printStackTrace();
}
return "stringCompletableFuture2 success";
});
CompletableFuture<Object> objectCompletableFuture = CompletableFuture.anyOf(stringCompletableFuture, stringCompletableFuture2);
objectCompletableFuture.thenAccept((result)->{// Consumer 接口 void accept(T t);
System.out.printf("stringCompletableFuture,stringCompletableFuture2 执行逻辑成功,返回结果=[%s] \n", result);
});
// 主线程不能直接结束
Thread.sleep(1000L);
}
}
/**
* 实现Runnable的没有办法获取返回值
*/
class My05Runnable implements Runnable {
@Override
public void run() {
System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is running..\n");
try {
Thread.sleep(500L);
} catch (java.lang.InterruptedException e){
e.printStackTrace();
}
System.out.print(Thread.currentThread().getName() + "\tMyRunnable class is end..\n");
}
}
/**
* 实现Callable的支持 获取返回值
*/
class MyTaskThreadCallable implements Callable<String> {
@Override
public String call() throws Exception {
System.out.print(Thread.currentThread().getName() + "\tMyTaskThreadCallable class is running..\n");
try {
Thread.sleep(500L);
} catch (java.lang.InterruptedException e){
e.printStackTrace();
}
System.out.print(Thread.currentThread().getName() + "\tMyTaskThreadCallable class is end..\n");
return "MyTaskThreadCallable run is success.";
}
}
Future
当我们提交一个Callable任务后,可以获取返回的Future类型的对象,Future类型的实例代表一个未来能获取结果的对象。
在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果。
Future接口表示一个未来可能会返回的结果,它定义的方法有
get():获取结果(可能会等待)
get(long timeout, TimeUnit unit):获取结果,但只等待指定的时间;
cancel(boolean mayInterruptIfRunning):取消当前任务;
isDone():判断任务是否已完成。
CompletableFuture
CompletableFuture它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法
创建一个CompletableFuture是通过CompletableFuture.supplyAsync()实现的,它需要一个实现了Supplier接口的对象.
线程执行成功回调的实例:completableFuture.thenAccept(),完成时,CompletableFuture会调用Consumer对象
线程执行异常回调的实例:completableFuture.exceptionally(), 异常时,CompletableFuture会调用 Function对象
anyOf()可以实现“任意个CompletableFuture只要一个成功”,
allOf()可以实现“所有CompletableFuture都必须成功”
CompletableFuture的优点
异步任务结束时,会自动回调某个对象的方法;
异步任务出错时,会自动回调某个对象的方法;
主线程设置好回调后,不再关心异步任务的执行
CompletableFuture的命名规则
xxx():表示该方法将继续在已有的线程中执行;
xxxAsync():表示将异步在线程池中执行。
MyThread06 线程变量ThreadLocal
package com.yenroc.ho.多线程;
import java.util.Date;
/**
* ThreadLocal
*/
public class MyThread06 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new MyThreadLocal()).start();
}
}
}
class MyThreadLocal implements Runnable {
// 每个线程获取ThreadLocal变量时,总是使用Thread自身作为key
// 因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰
protected static ThreadLocal<String> name = new ThreadLocal<>();
@Override
public void run() {
try {
initName();
printName();
Thread.sleep(1000);
printName2();
} catch (Exception e){
e.printStackTrace();
} finally {
clearName();// 线程结束要销毁线程变量
}
}
private void initName(){
name.set(Thread.currentThread().getName() + new Date().toString());
}
private void clearName(){
name.remove();
}
private void printName(){
System.out.println("printName="+ name.get());
}
private void printName2(){
System.out.println("printName2="+ name.get());
}
}
每个线程获取ThreadLocal变量时,总是使用Thread自身作为key
因此,ThreadLocal相当于给每个线程都开辟了一个独立的存储空间,各个线程的ThreadLocal关联的实例互不干扰