概述
面试中,多线程和并发编程已经是必不可少的了,我经常看到此类问题,当时也简单了解过,什么继承Thread类,实现Runnable接口,这些都被说烂了,知道这些当然是远远不够的,于是这几天搜索相关资料恶补了一下,为了方便后期复习,在此做个总结。
继承Thread类
这个可以说是很原始的方式,就是继承Thread类,重写run方法。
package com.hzy;
public class Main {
public static void main(String[] args) {
new MyThread().start();
}
}
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
由于Java是单继承的,所以这种方法用的比较少,一般都是用Runnable接口
实现Runnable接口
这里给出几个常用的构造方法
先给出一个传统的方法实现
package com.hzy;
public class Main {
public static void main(String[] args) {
new Thread(new MyThread()).start();
new Thread(new MyThread(),"贺志营").start();
}
}
class MyThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
我们也可以通过匿名内部类实现
package com.hzy;
public class Main {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}).start();
}
}
还可以通过Lamata表达式实现
package com.hzy;
public class Main {
public static void main(String[] args) {
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}).start();
}
}
实现Callable接口
Callable接口可以接收返回值,可以抛出异常,重写的是call方法
package com.hzy;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建执行服务
ExecutorService service = Executors.newFixedThreadPool(3);
// 提交执行
Future<Boolean> future1 = service.submit(new MyThread());
Future<Boolean> future2 = service.submit(new MyThread());
Future<Boolean> future3 = service.submit(new MyThread());
// 获取返回值
Boolean b1 = future1.get();
Boolean b2 = future2.get();
Boolean b3 = future3.get();
System.out.println(b1);
System.out.println(b2);
System.out.println(b3);
// 关闭服务
service.shutdown();
}
}
class MyThread implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return true;
}
}
另外还可以通过FutureTask适配器创建
package com.hzy;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args) throws ExecutionException, InterruptedException {
// 创建一个适配器
FutureTask futureTask = new FutureTask(new MyThread());
new Thread(futureTask,"A").start();
Boolean o = (Boolean) futureTask.get();
System.out.println(o);
}
}
class MyThread implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
return true;
}
}
线程的五大状态
创建状态
所谓的创建状态,也就是我们的new Thread();
就绪状态
所谓的就绪状态,就是我们myThread.start();
运行状态
运行状态就是我们的代码执行。
阻塞状态
当线程等待(wait)、同步(synchronized)、sleep和join的时候
死亡状态
run()结束、main()结束
线程常用方法
setPriority(int new Priority)
更改线程的优先级sleep(long millis)
让当前线程进入休眠join()
相当于插队,插入的线程执行结束后,被插队线程继续执行。yield()
线程礼让,暂停当前的线程,并把该线程状态转化为就绪状态interrupt()
中断线程(不建议用)isAlive()
判断线程是否处于存活状态
多线程买票案例
当多个线程操作同一个资源的时候,就会出现线程安全问题。假设我们有100张票,在三个窗口同时卖,也就是我们有三个线程去买票。
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
new Thread(myThread,"窗口4").start();
new Thread(myThread,"窗口5").start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
private boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
public void buy() {
if (ticket <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);// 模拟买票延迟100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
}
}
可以看出,出现了线程安全问题,原因是在对ticket变为0之前,有多个线程同时进来。
解决办法,可以通过synchronized线程同步,可以使用同步方法和同步代码块进行解决。同步方法,也就是在方法上加一个synchronized关键字。
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
private boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
public synchronized void buy() {
if (ticket <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);// 模拟买票延迟100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"+ ticket--);
}
}
同步代码块锁的是一个对象,即是需要变化的量
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread,"窗口1").start();
new Thread(myThread,"窗口2").start();
new Thread(myThread,"窗口3").start();
}
}
class MyTicket {
int ticket;
public MyTicket(int ticket) {
this.ticket = ticket;
}
}
class MyThread implements Runnable {
MyTicket myTicket = new MyTicket(100);
private boolean flag = true;
@Override
public void run() {
while (flag) {
buy();
}
}
public void buy() {
synchronized (myTicket) {
if (myTicket.ticket <= 0) {
flag = false;
return;
}
try {
Thread.sleep(100);// 模拟买票延迟100ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":"+ myTicket.ticket--);
}
}
}
死锁
所谓死锁,也就是A拥有A资源的同时想要B的资源,B拥有B资源的同时想要A的资源。
package com.hzy;
public class Main {
public static void main(String[] args){
A a = new A();
B b = new B();
new Thread(new MyThread(a,b,0)).start();
new Thread(new MyThread(a,b,1)).start();
}
}
class A {
}
class B {
}
class MyThread implements Runnable {
private A a;
private B b;
private int choice;
public MyThread(A a,B b,int choice) {
this.a = a;
this.b = b;
this.choice = choice;
}
@Override
public void run() {
if (choice == 0) {
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "获得" + a);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "获得" + b);
}
}
}else {
synchronized (b) {
System.out.println(Thread.currentThread().getName() + "获得" + b);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (a) {
System.out.println(Thread.currentThread().getName() + "获得" + a);
}
}
}
}
}
可以看出,A需要B资源,B需要A资源,出现了死锁的状态。
Lock锁
Lock是一个接口,而不是一个关键字,他是一个显示锁,只能锁同步代码块,不能锁方法,可以显示加锁,释放锁,可以指定唤醒某一个线程,Lock锁有一个实现类是ReentrantLock,可重入锁(递归锁),在这里说一下,所有的锁都是可重入锁,也就是如果我们获得了外面的锁之后,会自动获取里面的锁。
正常的,如果我们不加锁,会出现线程安全问题
package com.hzy;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket --);
}
}
}
可以通过RenntrantLock进行加锁,显示锁,记得解锁。
package com.hzy;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args){
MyThread myThread = new MyThread();
new Thread(myThread).start();
new Thread(myThread).start();
new Thread(myThread).start();
}
}
class MyThread implements Runnable {
private int ticket = 100;
// 定义lock锁
ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while (true) {
lock.lock();
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(ticket --);
}else {
break;
}
lock.unlock();
}
}
}
比ReentrantLock更细的锁是,ReentrantReadWriteLock,这里有一个读锁,一个写锁,
package com.hzy;
import java.util.HashMap;
import java.util.Map;
public class Main {
public static void main(String[] args) {
MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.put(temp + "",temp + "");
},temp + "").start();
}
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.get(temp + "");
},temp + "").start();
}
}
}
class MyReadWriteLock {
private volatile Map<String,String> map = new HashMap<>();
public void put(String key,String value) {
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入完成");
}
public void get(String key) {
System.out.println(Thread.currentThread().getName() + "读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
}
}
如果不加锁的话,在写入完成之前会被其他线程插入
而我们想要的是,在写入的时候,只能是一个线程,而读取的时候,可以是多个线程。
package com.hzy;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Main {
public static void main(String[] args) {
MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.put(temp + "",temp + "");
},temp + "").start();
}
for (int i = 0; i < 5; i++) {
int temp = i;
new Thread(()->{
myReadWriteLock.get(temp + "");
},temp + "").start();
}
}
}
class MyReadWriteLock {
private volatile Map<String,String> map = new HashMap<>();
ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key,String value) {
readWriteLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + "写入" + key);
map.put(key,value);
System.out.println(Thread.currentThread().getName() + "写入完成");
readWriteLock.writeLock().unlock();
}
public void get(String key) {
readWriteLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + "读取" + key);
map.get(key);
System.out.println(Thread.currentThread().getName() + "读取完成");
readWriteLock.readLock().unlock();
}
}
可以看出,在写入的时候,是一个写入,一个写入完成,在读取的时候,可能会有多个读取。
synchronized和Lock的区别
- synchronized是关键字,Lock是类
- synchronized无法获取锁的状态,Lock可以
- synchronized会自动释放锁,Lock需要手动
- synchronized没有Lock锁灵活(Lock锁可以自己定制)
生产者消费者问题
这是一个经典的线程通信问题,也就是不同线程之间有联系,生产者生产的东西放到缓冲区,如果缓冲区满了,生产者进入堵塞,缓冲区空了,消费者堵塞。
常用的方法有wait(),notify(),notifyAll()
package com.hzy;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args){
Buffer buffer = new Buffer();
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("生产者生产了" + i);
buffer.put();
}
},"生产者").start();
new Thread(()->{
for (int i = 0; i < 100; i++) {
System.out.println("消费者消费了" + i);
buffer.get();
}
},"消费者").start();
}
}
// 缓冲区
class Buffer {
private int len = 0;
public synchronized void put() {
if (len < 10) {
len ++;
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notifyAll();
}
public synchronized void get() {
if (len > 0) {
len --;
} else {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
notifyAll();
}
}
学习了Lock锁,这里的生产者消费者问题,可以通过Lock锁实现,可以指定唤醒某个线程,常用的方法是await,signal。
这里给出缓冲区
// 缓冲区
class Buffer {
private int len = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();// 该对象可以设置一些条件
public void put() {
lock.lock();
if (len < 10) {
len ++;
} else {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
condition.signalAll();
lock.unlock();
}
public void get() {
lock.lock();
if (len > 0) {
len --;
} else {
try {
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
condition.signalAll();
lock.unlock();
}
}
其中这里引入了Condition,他可以指定唤醒某个线程,这里我们演示三个线程ABC。
package com.hzy;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Main {
public static void main(String[] args){
Buffer buffer = new Buffer();
new Thread(()->{
for (int i = 0; i < 10; i++) {
buffer.a();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
buffer.b();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
buffer.c();
}
},"C").start();
}
}
// 缓冲区
class Buffer {
private int flag = 1;// 1执行A,2执行B,3执行C
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();// 该对象可以设置一些条件
Condition condition2 = lock.newCondition();// 该对象可以设置一些条件
Condition condition3 = lock.newCondition();// 该对象可以设置一些条件
public void a() {
lock.lock();
try {
while (flag != 1) {
condition1.await();
}
System.out.println("A");
flag = 2;
condition2.signal();// 指定唤醒B
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void b() {
lock.lock();
try {
while (flag != 2) {
condition2.await();
}
System.out.println("B");
flag = 3;
condition3.signal();// 指定唤醒C
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void c() {
lock.lock();
try {
while (flag != 3) {
condition3.await();
}
System.out.println("C");
flag = 1;
condition1.signal();// 指定唤醒A
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
结果都是ABC、ABC…
八锁问题
一
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
先输出sendMsg,不要理解为是先调用了sendMsg,而是因为A线程先获得了锁。
二
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
这个例子,再次解释了是因为sendMsg先获得的锁,这里的synchronized锁的对象是调用者,这两个方法用的是同一把锁,谁先拿到,谁先执行
三
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
// public synchronized void call() {
// System.out.println("call");
// }
public void call() {
System.out.println("call");
}
}
这里把call方法的synchronized去掉了,会先输出哪个呢,答案是先输出call,因为他不去获取锁资源,所以直接输出了
四
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}, "B").start();
}
}
class Phone {
public synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
这里用两个phone对象,分别调用sendMsg和call方法,会先执行哪个,答案是先执行call,因为这里的锁锁的是对象,而他们不是同一个对象,所以资源不受影响。
五
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public static synchronized void call() {
System.out.println("call");
}
}
这里是通过一个对象,去调用静态的同步方法,看看是先sendMsg还是call,答案是sentMsg,因为这里锁的是Class对象(只有一个)
六
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public static synchronized void call() {
System.out.println("call");
}
}
这里是通过两个对象去调用sentMsg和call,结果是什么呢,答案还是先执行sentMsg,因为锁的是Class对象,所以不管是phone1还是phone2,都是属于Phone的Class对象。
七
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone = new Phone();
new Thread(()->{
phone.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
这里锁的一个是静态同步方法,一个是普通同步方法,用一个对象对调用,结果是什么呢,答案是先call,因为静态同步方法锁的是Class对象,而普通同步方法锁的是调用者,锁的不是同一个东西。
八
package com.hzy;
import java.util.concurrent.TimeUnit;
public class Main {
public static void main(String[] args){
Phone phone1 = new Phone();
Phone phone2 = new Phone();
new Thread(()->{
phone1.sendMsg();
}, "A").start();
try {
TimeUnit.SECONDS.sleep(1);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{
phone2.call();
}, "B").start();
}
}
class Phone {
public static synchronized void sendMsg() {
try {
TimeUnit.SECONDS.sleep(3);// 休息一秒
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("sendMsg");
}
public synchronized void call() {
System.out.println("call");
}
}
这里用两个对象去调用静态同步方法sendMsg和普通同步方法call,会先输出哪个,答案是call,因为sendMsg锁的是Class,而call锁的是调用者,不是同一个一个东西
线程池
线程的创建跟Callable差不多,也是用ExecutorService
package com.hzy;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Main {
public static void main(String[] args){
ExecutorService service = Executors.newFixedThreadPool(10);// 创建固定个数线程
// ExecutorService service2 = Executors.newCachedThreadPool();// 创建动态线程(自适应大小)
// ExecutorService service1 = Executors.newSingleThreadExecutor();// 创建单个线程
// 执行
service.execute(new MyThread());
service.execute(new MyThread());
service.execute(new MyThread());
// 关闭连接
service.shutdown();
}
}
class MyThread implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
可以看出,在线程池中拿了三个线程
在阿里巴巴开发手册中有规定,创建线程池要用ThreadPoolExecutor
对于ThreadPoolExecutor的学习,就从七大参数,四种拒绝策略
int corePoolSize// 核心线程池大小
int maximumPoolSize// 最大核心线程池大小
long keepAliveTime// 超时存活时间
TimeUnit unit// 超时单位
BlockingQueue<Runnable> workQueue// 阻塞队列
ThreadFactory threadFactory// 线程工厂,用于创建线程
RejectedExecutionHandler handler// 拒绝策略
AbortPolicy());// 银行满了还有人进来,不处理,抛出异常(默认)
CallerRunsPolicy();// 银行满了,不处理,哪里来的去哪里,一般抛给main线程
DiscardPolicy();// 银行满了,把该线程丢掉,不抛异常
DiscardOldestPolicy();// 银行满了,会和先来的线程竞争,不抛异常
package com.hzy;
import java.util.concurrent.*;
public class Main {
public static void main(String[] args){
/**
* 用一个银行的例子进行讲解这七大参数
*/
ExecutorService threadPool = new ThreadPoolExecutor(
2,// 两个常开营业窗口
5,// 五个窗口,其中三个应急用
3,// 超时存货时间
TimeUnit.SECONDS,// 超时时间单位
new LinkedBlockingDeque<>(3),// 银行候客区大小
Executors.defaultThreadFactory(),// 默认线程池工厂
new ThreadPoolExecutor.AbortPolicy());// 银行满了还有人进来,不处理,抛出异常(默认)
// new ThreadPoolExecutor.CallerRunsPolicy();// 银行满了,不处理,哪里来的去哪里,一般抛给main线程
// new ThreadPoolExecutor.DiscardPolicy();// 银行满了,把该线程丢掉,不抛异常
// new ThreadPoolExecutor.DiscardOldestPolicy();// 银行满了,会和先来的线程竞争,不抛异常
for (int i = 0; i < 8; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName());
});
}
// 关闭连接
threadPool.shutdown();
}
}
在定义线程池最大大小的时候,一般有两种策略CPU密集型和IO密集型,所谓CPU密集型,也就是,几核的CPU就定义为几,我的是八核,所以定义为8,Runtime.getRuntime().availableProcessors();// 获取CPU的核数
,IO密集型,就是判断程序中有多少个非常耗IO线程的程序,最大线程池的大小要大于这个值即可。
volatile
说起volatile,不难想出三大特性保证可见性,不保证原子性,禁止指令重排。
在讲解volatile之前,需要讲解一个东西,Java内存模型(Java Memory Model,JMM),当线程读取内存中的一个变量时,会先把这个变量拷贝到CPU的高速缓存区,然后对其进行操作,操作完成后,会把该变量写入到内存中。在单线程中是不会出现任何问题的,但是在多线程中就会有问题,当线程1读取了该变量a=1到缓存区进行了加1操作,还没写到内存中,线程2读取了内存中的变量a=1也进行加1操作,然后线程1写入内存a=2,线程2也写入a=2到内存,那么最后,该变量的值是2,而不是3(出现了线程安全问题)。我们想要当线程1进行了加1操作之后,让线程2知道,这就是volatile的作用了,可以保证可见性,也就是,当线程1对a变量进行了加1操作,会直接写入到内存中(立即马上),并且通知线程2,变量被修改了,要求线程2缓冲区的值去内存中重新读取。但是,加1操作不是原子性的(三步,首先读取a变量的值,然后对其进行加1操作,然后赋值给a),也就是说,当线程1读取a变量到缓冲区后,还没有修改a的值,此时线程2进来了,读取了a的值,并且对其进行了加1操作,由于可见性,会把线程1缓冲区的值进行修改,但是,线程1中的CPU已经读取了缓冲区的值,而且是更新前的值,所以出现了线程安全问题,也是volatile不保证原子性的问题。于是就需要加1操作是原子性操作,于是就有了一个automic包,通过该包下的方法,可以实现加1的原子性操作(还有其他原子性操作)。
在原子操作里的自增,其实用的是自旋锁,也就是一直进行比较并交换。
说起原子性操作,其实得聊聊CAS(Compare And Swap,比较并交换),如果我们想要修改某个值num,那么我们可以通过一个方法compareAndSet(5,6)
意思是,我们期望num的值是5,如果是,就修改为6。但是这就会有一个问题,我们期望的num是5,如果有其他线程把5修改为了8,然后又修改为了5,最终是5,但是已经被修改过一次了,这就是ABA问题。我们可以通过AtomicStampedReference,也就是原子引用,在创建的时候,有一个印记,相当于版本号,每被修改一次,版本号都被更新,所以,当出现ABA问题的时候,我们就可以清楚的知道,被修改了多少次。
还有一个特性就是禁止指令重排,既然要禁止他,那么就得知道什么是指令重排,所谓指令重排,也就是,为了提高程序执行效率,寄存器对代码的优化,如果我们有一段代码如下
a = 1;// 语句1
b = 2;// 语句2
a = a + 1;// 语句3
b = a + 1;// 语句4
所谓指令重排,也就是在不影响单线程程序程序结果的情况下进行最优执行排序,可以看出,语句1和语句2的执行顺序并不会影响程序的结果,最终a的值为2,b的值为3。下面看这个代码Instance instance = new Instance();
从微观的角度看,这条语句可以分解为三步,一是分配内存空间,二是初始化对象三是指向该内存空间,其中二和三会发生指令重排,如果在多线程的情况下,线程A分配了内存空间,并且执行了该内存空间(没有初始化对象),线程B进行了访问该内存空间,这就会出错了。而volatile就是禁止指令重排的,也并非绝对的禁止,假设我们有1、2、3、4、5条语句
a = 1;
b = 2;
c = 3;
d = 4;
e = 5;
如果我们对c操作进行了volatile修饰,那么a和b依然可以指令重排,d和e也可以指令重排,ab和de不能指令重排了,c把他们分开了(专业术语是给c加了个内存屏障)。
转载:https://blog.csdn.net/HeZhiYing_/article/details/105943962