文章目录
1. Thread
1.1 创建线程
继承Thread,重写run方法
@Test
public void helloThreadTest() {
Thread thread = new HelloThread();
thread.start();
}
public class HelloThread extends Thread {
@Override
public void run() {
System.out.println("HelloThread");
}
}
实现Runnable接口
@Test
public void helloRunnableTest() {
Thread thread = new Thread(new HelloRunnable());
thread.start();
}
public class HelloRunnable implements Runnable {
@Override
public void run() {
System.out.println("HelloRunnable");
}
}
Lambda语法简写
@Test
public void lambdaTest() {
Thread thread = new Thread(() -> {
System.out.println("lambdaTest");
});
thread.start();
}
1.2 join
join()方法使主线程会等待线程完成后继续执行。
没有join()方法,只能保证主线程先打印main start,然后main end;线程先打印thread start,然后thread end
main end具体打印什么位置未知,由操作系统调度
但是添加join,就可以保证main end在线程执行结束后打印
@Test
public void mainThreadTest() throws InterruptedException {
System.out.println("main start");
Thread thread = new Thread(() -> {
System.out.println("thread start");
System.out.println("thread end");
});
thread.start();
//thread.join();
System.out.println("main end");
}
1.3 interrupt
Thread.sleep(1);需要使用main方法,单元测试会不可用。
线程t中断,主线程等待线程t中断后,继续执行
public static void main(String[] args) throws InterruptedException {
Thread t = new InterruptThread();
t.start();
// 暂停1毫秒
Thread.sleep(1);
// 中断t线程
t.interrupt();
// 等待t线程结束
t.join();
System.out.println("end");
}
public class InterruptThread extends Thread {
public void run() {
int n = 0;
while (!isInterrupted()) {
n ++;
System.out.println(n + " hello!");
}
}
}
线程t启动后,线程t内部会启动线程2,并等待线程2结束
当线程t中断后,主线程等待线程t结束
如果不把线程2中断,则线程2会继续执行
public static void main(String[] args) throws InterruptedException {
Thread t = new InterruptThread1();
t.start();
Thread.sleep(1000);
// 中断t线程
t.interrupt();
// 等待t线程结束
t.join();
System.out.println("end");
}
public class InterruptThread1 extends Thread {
public void run() {
Thread thread2 = new InterruptThread2();
// 启动hello线程
thread2.start();
try {
// 等待hello线程结束,如果中断会抛异常
thread2.join();
} catch (InterruptedException e) {
System.out.println("interrupted!" + new Date());
}
// 不将thread2中断,还会继续
thread2.interrupt();
}
}
public class InterruptThread2 extends Thread {
public void run() {
int n = 0;
while (!isInterrupted()) {
n++;
System.out.println(n + " InterruptThread2!" + new Date());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
break;
}
}
}
}
中断也可通过设置标志位来实现
通过关键词volatile将其标记为线程间共享的变量
public static void main(String[] args) throws InterruptedException {
InterruptThread3 thread = new InterruptThread3();
thread.start();
Thread.sleep(1);
thread.running = false;
}
public class InterruptThread3 extends Thread {
public volatile boolean running = true;
@Override
public void run() {
int n = 0;
while (running) {
n ++;
System.out.println(n + " hello!");
}
System.out.println("end!");
}
}
2. wait notify
wait和notify用于多线程协调运行。
在synchronized内部可以调用wait()使线程进入等待状态,调用notify()或notifyAll()唤醒其他等待线程;
必须在已获得的锁对象上调用wait()方法,调用notify()或notifyAll()方法;
已唤醒的线程还需要重新获得锁后才能继续执行。
启动两个线程,一个输出 1,3,5,7…99,另一个输出 2,4,6,8…100,最后按序输出 1,2,3,4,5…100
private static final Object lock = new Object();
private volatile int i = 1;
@Test
public void test1() {
Thread thread1 = new Thread(() -> {
while (i <= 100) {
synchronized (lock) {
// 偶数则等待
if (i % 2 == 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 奇数打印并判断
} else {
System.out.print(i + " ");
i++;
lock.notifyAll();
}
}
}
});
Thread thread2 = new Thread(() -> {
while (i <= 100) {
synchronized (lock) {
// 奇数则等待
if (i % 2 != 0) {
try {
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 偶数打印并判断
} else {
System.out.print(i + " ");
i++;
lock.notifyAll();
}
}
}
});
thread1.start();
thread2.start();
}
也可以这样:
@Test
public void test2() {
Thread t1 = new Thread(() -> {
while(i <= 100) {
synchronized (lock) {
if (i % 2 == 0) {
System.out.print(i++);
System.out.print(" ");
} else {
// 奇数,就wait,释放锁
try{
lock.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
});
Thread t2 = new Thread(() -> {
while(i <= 100) {
synchronized (lock) {
if (i % 2 != 0) {
System.out.print(i++);
System.out.print(" ");
} else {
// 偶数,就notify,会唤醒上方锁等待的线程
lock.notifyAll();
}
}
}
});
t1.start();
t2.start();
}
两个线程,一个线程输出奇数,一个线程输出偶数,保证输出顺序是:2、1、4、3、……、50、49、52、51、54、53、……、100、99
private static final Object lock = new Object();
private volatile int count = 2;
@Test
public void test3() {
Thread t1 = new Thread(() -> {
while(count <= 100) {
synchronized (lock){
// 偶数则打印,并减1
if (count % 2 == 0) {
System.out.print(count + " ");
count--;
// 需要notify,否则两个线程执行一次后都进行等待
lock.notifyAll();
if (count <= 100) {
try{
lock.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
});
Thread t2 = new Thread(() -> {
while(count <= 100) {
synchronized (lock){
// 奇数则打印,并加3
if (count % 2 != 0) {
System.out.print(count + " ");
count += 3;
// 需要notify,否则两个线程执行一次后都进行等待
lock.notifyAll();
if (count <= 100) {
try{
lock.wait();
}catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
});
t1.start();
t2.start();
}
3. ReentrantLock Condition
ReentrantLock可以替代synchronized进行同步,并且获取锁更安全;必须先获取到锁,再进入try {…}代码块,最后使用finally保证释放锁;可以使用tryLock()尝试获取锁。
用ReentrantLock,可以使用Condition对象来实现,等同于synchronized,wait和notify的功能
启动两个线程,一个输出 1,3,5,7…99,另一个输出 2,4,6,8…100,最后按序输出 1,2,3,4,5…100
使用ReentrantLock和Condition:
private static final Lock reentrantLock = new ReentrantLock();
private static final Condition condition = reentrantLock.newCondition();
private volatile int i = 1;
@Test
public void reentrantLockTest() {
Thread thread1 = new Thread(() -> {
while (i <= 100) {
reentrantLock.lock();
try {
// 偶数则等待
if (i % 2 == 0) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 奇数打印并判断
} else {
System.out.print(i + " ");
i++;
condition.signalAll();
}
} finally {
reentrantLock.unlock();
}
}
});
Thread thread2 = new Thread(() -> {
while (i <= 100) {
reentrantLock.lock();
try {
// 齐数则等待
if (i % 2 != 0) {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 偶数打印并判断
} else {
System.out.print(i + " ");
i++;
condition.signalAll();
}
} finally {
reentrantLock.unlock();
}
}
});
thread1.start();
thread2.start();
}
4. ReadWriteLock
使用ReadWriteLock可以保证:只允许一个线程写入(其他线程既不能写入也不能读取);没有写入时,多个线程允许同时读(提高性能)。
如果有线程正在读,写线程需要等待读线程释放锁后才能获取写锁,即读的过程中不允许写,这是一种悲观的读锁。
private static final ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private static final Lock readLock = readWriteLock.readLock();
private static final Lock writeLock = readWriteLock.writeLock();
/**
* 获取写锁
* @param i
*/
private void setI (Integer i) {
writeLock.lock();
try {
this.i = i;
} finally {
writeLock.unlock();
}
}
/**
* 获取读锁
* @return
*/
private Integer getI () {
readLock.lock();
try {
return i;
} finally {
readLock.unlock();
}
}
5. StampedLock
StampedLock和ReadWriteLock相比,改进之处在于:读的过程中也允许获取写锁后写入!这样一来,我们读的数据就可能不一致,所以,需要一点额外的代码来判断读的过程中是否有写入,这种读锁是一种乐观锁。
乐观锁的意思就是乐观地估计读的过程中大概率不会有写入,因此被称为乐观锁。反过来,悲观锁则是读的过程中拒绝有写入,也就是写入必须等待。显然乐观锁的并发效率更高,但一旦有小概率的写入导致读取的数据不一致,需要能检测出来,再读一遍就行。
StampedLock提供了乐观读锁,可取代ReadWriteLock以进一步提升并发性能;StampedLock是不可重入锁。
private static final StampedLock stampedLock = new StampedLock();
private Integer x;
private Integer y;
private void set(Integer x , Integer y) {
long stamp = stampedLock.writeLock();
try {
x += x;
y += y;
} finally {
stampedLock.unlockWrite(stamp);
}
}
private Integer get() {
// 先获取乐观读锁
long stamp = stampedLock.tryOptimisticRead();
Integer newX = x;
Integer newY = y;
// 判断获取乐观读锁后是否有获取其他写锁
if (!stampedLock.validate(stamp)) {
// 有获取其他写锁,则获取悲观读锁。写锁需要等悲观读锁释放后,才能写
stamp = stampedLock.readLock();
try {
// 重新赋值
newX = x;
newY = y;
} finally {
stampedLock.unlockRead(stamp);
}
}
return newX * newY;
}
4. ExecutorService
ExecutorService接口表示线程池,Java标准库提供的几个常用实现类有:
FixedThreadPool:线程数固定的线程池,任务超过则等待;
CachedThreadPool:线程数根据任务动态调整的线程池;
SingleThreadExecutor:仅单线程执行的线程池,定期反复执行。
@Test
public void executorServiceTest() {
ExecutorService executorService = Executors.newFixedThreadPool(3);
for (int i = 1; i <= 3; i++) {
executorService.submit(new Task(i + ""));
}
executorService.shutdown();
}
public class Task implements Runnable {
private final String name;
public Task(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(new Date() + " start " + name);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(new Date() + " end " + name);
}
}
5. Future
一个Future接口表示一个未来可能会返回的结果,可以用来获取线程返回值。
当我们提交一个Callable任务后,我们会同时获得一个Future对象,然后,我们在主线程某个时刻调用Future对象的get()方法,就可以获得异步执行的结果。
在调用get()时,如果异步任务已经完成,我们就直接获得结果。如果异步任务还没有完成,那么get()会阻塞,直到任务完成后才返回结果。
@Test
public void callableTest() {
ExecutorService executorService = Executors.newFixedThreadPool(3);
Callable<String> callable = new TaskCallable();
Future<String> future = executorService.submit(callable);
String returnStr = "";
try {
returnStr = future.get();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(returnStr);
}
public class TaskCallable implements Callable<String> {
@Override
public String call() throws Exception {
return "TaskCallable";
}
}
6. CompletableFuture
CompletableFuture针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。
CompletableFuture可多个串行或并行执行
@Test
public void CompletableFutureTest() {
while (true) {
CompletableFuture<Double> completableFuture = CompletableFuture.supplyAsync(ThreadTest::fetchPrice);
// 成功
completableFuture.thenAccept((result) -> {
System.out.println(result);
});
// 失败
completableFuture.exceptionally((e) -> {
e.printStackTrace();
return null;
});
}
}
static Double fetchPrice() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
if (Math.random() < 0.3) {
throw new RuntimeException("fetch price failed!");
}
return 5 + Math.random() * 20;
}
参考:
Java 多线程
编程题:两个线程,一个线程输出奇数,一个线程输出偶数,保证输出顺序是:2、1、4、3、……、50、49、52、51、54、53、……、100、99
你真的懂wait、notify和notifyAll吗