文章目录
1.实现线程的几种方式
1.1.继承Thread类
- 子类继承Thread类具备多线程能力
- 启动线程:子类对象.start()
- 不建议使用:避免OOP单继承局限性
public class Test1 extends Thread{
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
System.out.println("线程"+i);
}
}
public static void main(String[] args) {
Test1 test1 =new Test1();
test1.start();
for (int i = 0; i <20 ; i++) {
System.out.println("主线程"+i);
}
}
}
1.2.实现Runnable接口
- 实现接口Runnable具有多线程能力
- 启动线程:传入目标对象+Thread对象.start()
- 推荐使用:避免单继承局限性,灵活方便,方便同一个对象被多个线程使用
public class Test2 implements Runnable{
@Override
public void run() {
for (int i = 0; i <20 ; i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
public static void main(String[] args) {
Test2 test2 = new Test2();
new Thread(test2,"线程1").start();
new Thread(test2,"线程2").start();
}
}
1.3.实现Callable接口
1.实现Callable接口
2.重写call方法,需要返回值类型
3.创建目标对象
4.创建执行任务:ExecutorService executorService = Executors.newFixedThreadPool(3);
5.提交执行:Future result1 = ser.submit(1)
6.获取结果:boolean r1= result1.get()
7.关闭服务:ser.shutdownNow()
public class Test3 implements Callable<Boolean> {
@Override
public Boolean call() throws Exception {
for (int i = 0; i <20 ; i++) {
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
return true;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
Test3 t1 = new Test3();
Test3 t2 = new Test3();
Test3 t3 = new Test3();
//创建执行任务:
ExecutorService executorService = Executors.newFixedThreadPool(3);
//提交执行
Future<Boolean> submit1 = executorService.submit(t1);
Future<Boolean> submit2 = executorService.submit(t2);
Future<Boolean> submit3 = executorService.submit(t3);
// 获取结果
boolean r1= submit1.get();
boolean r2= submit2.get();
boolean r3= submit3.get();
//关闭服务:
executorService.shutdownNow();
}
}
2.Lambda表达式
public class Test4 {
public static void main(String[] args) {
Lambda lambda ;
//lambda表达式实现接口
lambda = (a,b) -> {
System.out.println((a+b));
};
lambda.print(1,1);
}
}
//函数式接口
interface Lambda{
void print(int a,int b);
}
总结:
- lambda表达式只有一行代码的情况下才能简化称为一行,如果有多行,那么就用代码块包裹。
- 使用lambda表达式的前提必须是函数式接口(只有一个方法)
- 多个参数也可以去掉参数类型,要去掉就都去掉,必须加上括号
3.静态代理模式
- 真实对象和代理对象都要实现同一个接口
- 代理对象要代理真实角色
好处:
- 代理对象可以做很多真实对象做不了的事情
- 真实对象专注做自己的事情
静态代理模式,婚庆公司举例:
public class Test5 {
public static void main(String[] args) {
new WeddingCompany(new You()).happyMarry();
}
}
interface Marry{
void happyMarry();
}
//角色,你去结婚
class You implements Marry{
@Override
public void happyMarry() {
System.out.println("你在结婚中");
}
}
//代理角色,婚庆公司,帮助你结婚
class WeddingCompany implements Marry{
private Marry target;
public WeddingCompany(Marry target) {
this.target = target;
}
@Override
public void happyMarry() {
before();
target.happyMarry();
after();
}
private void after() {
System.out.println("结婚后收钱");
}
private void before() {
System.out.println("结婚前准备");
}
}
线程的实现原理就是基于静态代理模式。
new WeddingCompany(new You()).happyMarry();
new Thread(()-> System.out.println("我爱你"));
Thread也实现了Runnable接口。
Thread就是一个代理对象,代理中间的真实对象Runnable接口。
4.synchronized锁
synchronized方法:锁的是当前类的class。
public class Test6 {
public static void main(String[] args) {
BuyTickets b1= new BuyTickets();
new Thread(b1,"张三").start();
new Thread(b1,"李四").start();
new Thread(b1,"赵五").start();
}
}
class BuyTickets implements Runnable{
private int ticketsNum = 10;
private boolean flag = true;//外部停止方式
@Override
public void run() {
while (flag){
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
buy();
}
}
private synchronized void buy(){
if (ticketsNum<=0){
flag=false;
return;
}
System.out.println(Thread.currentThread().getName()+"拿到了"+ticketsNum--);
}
}
synchronized代码块:synchronized(锁的对象){
要加锁的内容
}
private void buy(){
synchronized (ticketsNum) {
if (ticketsNum <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "拿到了" + ticketsNum--);
}
}
锁的对象是进行增删改的对象。
5.死锁
当某个同步块同时拥有“两个以上对象的锁时”,就可能会放生死锁问题
两个对象互相占着对方的资源,产生僵持。
产生死锁的四个必要条件
- 互斥条件:一个资源每次只能被一个进程使用
- 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放
- 不剥夺条件:进程已获得的资源,在未使用完前,不能强行剥夺
- 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
6.Lock锁
- 从JDK5.0开始, java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用Lock充当。
- java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
- ReentrantLock类实现了Lock,他拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock可重用锁,可以显式加锁,释放锁。
//new一个锁对象
ReentrantLock lock = new ReentrantLock();
private void buy(){
//加锁
try {
lock.lock();
if (ticketsNum <= 0) {
flag = false;
return;
}
System.out.println(Thread.currentThread().getName() + "拿到了" + ticketsNum--);
}finally {
//释放锁
lock.unlock();
}
}
7.synchronized与Lock区别
- synchronized 内置的java关键字,lock是一个java类
- synchronized无法判断获取锁的状态,lock可以判断是否获得了锁
- synchronized会自动释放锁,lock必须手动释放锁,如果不释放就会死锁
- synchronized造成的阻塞线程会一直等待下去,而lock不一定会等
- synchronized可重入锁,不可以中断,非公平,lock可重入锁,可以判断锁,默认非公平(可以自己设置)
- synchronized适合少量的代码同步问题,lock适合大量的同步代码
8.线程池
- JDK5.0起提供了线程池相关API:ExecutorService和Excutors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExcutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
- Future submit(Callable task) :执行任务,有返回值,一般用来执行Callable
- Excutors:工具类,线程池的工厂类,用于创建并返回不同类型的线程池
9.线程状态
public enum State {
/**
* 新生
*/
NEW,
/**
运行
*/
RUNNABLE,
/**
阻塞
*/
BLOCKED,
/*
等待
*/
WAITING,
/**
超时等待
*/
TIMED_WAITING,
/**
死亡
*/
TERMINATED;
}
10. wait和sleep的区别
都是线程休眠
1.来自不同的类
wait是Object类中的
sleep是Thread类中的
2.关于锁的释放
wait会释放锁
sleep不会释放锁
3.使用范围不同
sleep在任何地方都可以使用
wait只有在同步代码块中可以使用
4.是否需捕获异常
wait不需要捕获异常
sleep需要捕获异常
11.生产者与消费者问题
/*
生产者消费者问题
*/
public class Test7 {
public static void main(String[] args) {
Data data = new Data();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
data.decrement();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
try {
data.increment();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
},"D").start();
}
}
class Data{
private static Integer num = 0;
public synchronized void increment() throws InterruptedException {
//注意这里要用while来判断,不能用if,if只判断一次会引起虚假唤醒问题
while (num!=0){
//等待
this.wait();
}
//唤醒其他线程
this.notifyAll();
num++;
System.out.println(Thread.currentThread().getName()+"=>"+num);
}
public synchronized void decrement() throws InterruptedException {
while (num==0){
this.wait();
}
this.notifyAll();
num--;
System.out.println(Thread.currentThread().getName()+"=>"+num);
}
}
线程也可以唤醒,而不会被通知,中断或超时,即所谓的虚假唤醒。
等待应该发生在循环中,不应用if判断。
12.什么是JUC
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-O4X3h0YS-1602919849079)(C:\Users\Administrator\AppData\Roaming\Typora\typora-user-images\image-20201015102153149.png)]
juc就是,java.util.cocurrent 包
13.JUC版的生产者与消费者问题
Lock
替换synchronized
方法和语句的使用, Condition
取代了对象监视器方法的使用。 await,signal。
一个Condition
实例本质上绑定到一个锁。 要获得特定Condition
实例的Condition实例,请使用其newCondition()
方法。
class Data{
private static Integer num = 0;
//创建锁对象
ReentrantLock lock = new ReentrantLock();
//创建Condition对象,用它其中的方法await 和 signal 来代替wait 和notify
Condition condition = lock.newCondition();
public void increment() throws InterruptedException {
try{
//上锁
lock.lock();
//注意这里要用while来判断,不能用if,if只判断一次会引起虚假唤醒问题
while (num!=0){
//等待
condition.await();
}
num++;
condition.signal();
System.out.println(Thread.currentThread().getName()+"=>"+num);
}finally {
//释放锁
lock.unlock();
}
}
public void decrement() throws InterruptedException {
try {
lock.lock();
while (num==0){
condition.await();
}
num--;
condition.signal();
System.out.println(Thread.currentThread().getName()+"=>"+num);
}finally {
lock.unlock();
}
}
}
对比wait和notify,condition可以精准通知和唤醒线程
/**
* 顺序通知线程执行,condition精准唤醒线程
* ABCD
*/
public class Test2 {
public static void main(String[] args) {
Data3 data3 = new Data3();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data3.printA();
}
},"A").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data3.printB();
}
},"B").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data3.printC();
}
},"C").start();
new Thread(()->{
for (int i = 0; i <10 ; i++) {
data3.printD();
}
},"D").start();
}
}
class Data3{
private ReentrantLock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
Condition condition4 = lock.newCondition();
private int num = 1;
public void printA(){
lock.lock();
try {
while (num!=1){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"->"+num);
num = 2;
//唤醒B线程
condition2.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printB(){
lock.lock();
try {
while (num!=2){
condition2.await();
}
System.out.println(Thread.currentThread().getName()+"->"+num);
num = 3;
//唤醒C线程
condition3.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printC(){
lock.lock();
try {
while (num!=3){
condition3.await();
}
System.out.println(Thread.currentThread().getName()+"->"+num);
num = 4;
//唤醒C
condition4.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void printD(){
lock.lock();
try {
while (num!=4){
condition4.await();
}
System.out.println(Thread.currentThread().getName()+"->"+num);
num = 1;
//唤醒A
condition1.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
A->1
B->2
C->3
D->4
A->1
B->2
C->3
D->4
...
14.8锁问题
关于锁的8个问题
例子
public class Test3 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sendSms();
}).start();
new Thread(()->{
phone.call();
}).start();
}
}
class Phone{
public synchronized void sendSms(){
System.out.println("发短信");
}
public synchronized void call(){
System.out.println("打电话");
}
}
1.标准情况下,两个线程先打印发短信还是打电话?
先打印发短信,两个线程共用phone对象同一个锁,先调用发短信则打电话必须等发短信执行完之后才能执行。
2.sedSms延迟4秒,两个线程先打印发短信还是打电话?
先打印发短信,和上题情况相同。
3.增加了一个普通方法,先执行发短信还是普通方法?
先执行普通方法,由于普通方法没有上锁,所以不用等待发短信执行完毕就可先执行。
4.两个对象,两个同步方法,第一个对象执行发短信和打电话,第二个对象执行打电话,先执行发短信还是打电话?
先执行打电话,两个对象对应了两个锁,打电话没有休眠时间,所以比发短信先执行。
5.将两个同步方法都改为静态同步方法,只有一个对象。先执行发短信还是打电话?
先执行发短信, 静态方法在类初始化的时候就会执行,这时候锁的就不是对象了,而是Class,按顺序执行发短信在前同时拥有锁,打电话就得等待。
6.5条件中改为两个对象,先执行发短信还是打电话?
发短信,还是和上题答案一样,锁的是Class而不是对象,就和对象无关了。
7.发短信静态的同步方法,打电话普通的同步方法,先执行发短信还是打电话?
先执行打电话,这时候就有两个锁了,发短信占用的是Class的锁,而打电话占用的是对象的锁,两者互不影响
8.上述条件,两个对象,先执行发短信还是打电话?
还是先执行打电话,和上题解释相同。
15.集合的安全问题
15.1.List不安全
public class Test4 {
public static void main(String[] args) {
List<String> list = new ArrayList<>();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(list);
},String.valueOf(i)).start();
}
}
}
执行报错:java.util.ConcurrentModificationException
并发下的ArrayList不安全,看add源码会发现没有同步锁
解决方案:
1**.使用vector集合**
vector集合是线程安全的,底层的add方法使用synchronized加上了同步锁
但是效率较低
2.使用Collection.synchronizedList(new ArrayList<>())来创建使集合线程安全
3.使用new CopyOnwriteArrayList<>()来创建
在写入时复制,避免覆盖造成数据问题。
底层add方法采用lock锁,更高效
15.2.Set不安全
上面例子换为Set试一试
public class Test5 {
public static void main(String[] args) {
Set<String> set = new HashSet<String>();
for (int i = 0; i <10 ; i++) {
new Thread(()->{
set.add(UUID.randomUUID().toString().substring(0,5));
System.out.println(set);
},String.valueOf(i)).start();
}
}
}
同样报java.util.ConcurrentModificationException异常,没有多试几次或者增加循环次数。
解决方法:
1.Collections.synchronizedCollection(set)解决
2.new CopyOnWriteArraySet<>()解决。
接着问:HashSet的底层是什么?
HashSet底层就是HashMap
源码:
public HashSet() {
map = new HashMap<>();
}
public boolean add(E e) {
return map.put(e, PRESENT)==null;
}
private static final Object PRESENT = new Object();//常量
HashSet的值不重复利用的就是HashMap的键不重复。
15.3.map不安全
public class Test6 {
public static void main(String[] args) {
Map<String,String> map = new HashMap<>();
for (int i = 0; i <30 ; i++) {
new Thread(()->{
map.put(Thread.currentThread().getName(),UUID.randomUUID().toString().substring(0,5));
System.out.println(map);
},String.valueOf(i)).start();
}
}
}
同样报错ConcurrentModificationException,
解决:
1.Collections.synchronizedMap(map)
2.使用ConcurrentHashMap
16.Callable
Callable
接口类似于Runnable
,因为它们都是为其实例可能由另一个线程执行的类设计的。然而,ARunnable
不返回结果,也不能抛出被检查的异常。
public class CallableTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
//FutureTask实现了RunnableFuture接口,RunnableFuture继承了Runnable
FutureTask futureTask = new FutureTask<>(myThread);//适配类
new Thread(futureTask).start();//结果会被缓存,效率高
try {
String o = (String) futureTask.get();//可能会产生阻塞,把他放到最后
System.out.println(o);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyThread implements Callable<String>{
@Override
public String call() throws Exception {
System.out.println("call()");
return "1024";
}
}
细节:
1.有缓存
2.结果可能会需要等待,会阻塞。
17.常用的辅助类
17.1.CountDownLatch
减法计数器
public class CountDownLatchTest {
public static void main(String[] args) throws InterruptedException {
//总数是6
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"出去了");
countDownLatch.countDown();//数量-1
},String.valueOf(i)).start();
}
//等待总数为0时,再执行下面的任务,即所有人出去后再关门。
countDownLatch.await();
System.out.println("所有人都出去了,关门");
//2出去了
//1出去了
//3出去了
//4出去了
//5出去了
//6出去了
//所有人都出去了,关门
}
}
17.2.CyclicBarrier
加法计数器
public class CyclicBarrierTest {
public static void main(String[] args) {
//加法计数器,当计数到7时,输出
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{
System.out.println("召唤神龙");
});
for (int i = 1; i <=7 ; i++) {
final int temp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"拿到了"+temp+"个龙珠");
try {
cyclicBarrier.await();//等待到收集完7颗龙珠
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
//1拿到了1个龙珠
//2拿到了2个龙珠
//3拿到了3个龙珠
//4拿到了4个龙珠
//5拿到了5个龙珠
//6拿到了6个龙珠
//7拿到了7个龙珠
//召唤神龙
}
17.3.Semaphore
信号量
多个线程共享资源,同一时间只允许一定数量的线程占用资源。
模拟抢车位
public class SemaphoreTest {
public static void main(String[] args) {
//模拟抢车位,共3个车位
Semaphore semaphore = new Semaphore(3);
//6量车在抢
for (int i = 1; i <=6 ; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+"抢到了车位");
TimeUnit.SECONDS.sleep(2);
semaphore.release();
System.out.println(Thread.currentThread().getName()+"离开了车位");
}catch (Exception e){
e.printStackTrace();
}
},String.valueOf(i)).start();
}
}
//1抢到了车位
//3抢到了车位
//2抢到了车位
//1离开了车位
//6抢到了车位
//2离开了车位
//3离开了车位
//5抢到了车位
//4抢到了车位
//6离开了车位
//4离开了车位
//5离开了车位
}
18.读写锁
ReadWriteLock读写锁
读取锁(共享锁):允许多个线程进行读取操作。
写入锁(排他锁):只允许一个线程进行写入操作。
public class ReadWriteLockTest {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 1; i <=3 ; i++) {
final int temp = i;
new Thread(()->{
myCache.write(temp+"",temp+"");
},String.valueOf(i)).start();
}
for (int i = 1; i <=3 ; i++) {
final int temp = i;
new Thread(()->{
myCache.read(temp+"");
},String.valueOf(i)).start();
}
}
}
class MyCache{
private volatile Map<String,String> map = new HashMap<>();
//读写锁
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
//读取
public void read(String key){
System.out.println(Thread.currentThread().getName()+"读取"+key);
map.get(key);
System.out.println(Thread.currentThread().getName()+"读取ok");
}
//写入
public void write(String key,String value){
System.out.println(Thread.currentThread().getName()+"写入"+key);
map.put(key,value);
System.out.println(Thread.currentThread().getName()+"写入ok");
}
//2写入2
//2写入ok
//1写入1
//1写入ok
//3写入3
//3写入ok
//1读取1
//1读取ok
//2读取2
//2读取ok
//3读取3
//3读取ok
}
19.阻塞队列
BlockingQueue阻塞队列
在多线程并发处理和线程池情况下会使用阻塞队列
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(,) |
移除 | remove | poll | take | poll(,) |
查看队首元素 | element | peek | - | - |
public static void main(String[] args) {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.add("a"));
System.out.println(arrayBlockingQueue.add("b"));
System.out.println(arrayBlockingQueue.add("c"));
System.out.println(arrayBlockingQueue.add("d"));//抛出异常
System.out.println("===============");
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove());
System.out.println(arrayBlockingQueue.remove()); //抛出异常
}
public static void main(String[] args) {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("a"));
System.out.println(arrayBlockingQueue.offer("b"));
System.out.println(arrayBlockingQueue.offer("c"));
System.out.println(arrayBlockingQueue.offer("d"));//返回false
System.out.println("===============");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll()); //返回null
}
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.put("a");
arrayBlockingQueue.put("b");
arrayBlockingQueue.put("c");
arrayBlockingQueue.put("d");//队列没有位置了,一直阻塞
System.out.println("===============");
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take());
System.out.println(arrayBlockingQueue.take()); //没有这个元素,一直阻塞
}
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
arrayBlockingQueue.offer("a");
arrayBlockingQueue.offer("b");
arrayBlockingQueue.offer("c");
arrayBlockingQueue.offer("d",1,TimeUnit.SECONDS);//等待1s后就退出
System.out.println("===============");
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll());
System.out.println(arrayBlockingQueue.poll(1,TimeUnit.SECONDS)); //等待1s后就退出
}
同步队列SynchronousQueue
没有容量,一个元素进来之后,必须等他出来才能进入下一个元素
public static void main(String[] args) {
//没有容量,一个元素进来之后,必须等他出来才能进入下一个元素
SynchronousQueue<String> queue = new SynchronousQueue<>();
new Thread(()->{
try {
System.out.println(Thread.currentThread().getName()+" put 1");
queue.put("1");
System.out.println(Thread.currentThread().getName()+" put 2");
queue.put("2");
System.out.println(Thread.currentThread().getName()+" put 3");
queue.put("3");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t1").start();
new Thread(()->{
try {
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+queue.take());
TimeUnit.SECONDS.sleep(2);
System.out.println(Thread.currentThread().getName()+" take "+queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"t2").start();
}
//t1 put 1
//t2 take 1
//t1 put 2
//t2 take 2
//t1 put 3
//t2 take 3
20.线程池
三大方法,7大参数,4种拒绝策略。
线程池的好处
1.降低资源的消耗
2.提高响应的速度
3.方便管理
线程复用,可以控制最大并发数,管理线程
20.1.三大方法
newSingleThreadExecutor()
newFixedThreadPool(3)
newCachedThreadPool()
public static void main(String[] args) {
//ExecutorService executorService = Executors.newSingleThreadExecutor();//单个线程池
// ExecutorService executorService = Executors.newFixedThreadPool(3);//指定线程池大小
ExecutorService executorService = Executors.newCachedThreadPool();//遇强则强,遇弱则弱,最大可达到21亿
try {
for (int i = 0; i <10 ; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行");
});
}
} finally {
executorService.shutdown();
}
}
20.2.七大参数
看一下三大方法的底层源码
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
可以看到这三个方法最后都反返回了一个叫ThreadPoolExecutor方法
进去看一看
7大参数就是说的ThreadPoolExecutor中的7大参数
- int corePoolSize,//核心线程数
- int maximumPoolSize,//最大线程数
- long keepAliveTime,//超时等待时间
- TimeUnit unit,//超时单位
- BlockingQueue workQueue,//阻塞队列
- ThreadFactory threadFactory,//线程工厂,一般不用动
- RejectedExecutionHandler handler//拒绝策略
public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//超时等待时间
TimeUnit unit,//超时单位
BlockingQueue<Runnable> workQueue,//阻塞队列
ThreadFactory threadFactory,//线程工厂,一般不用动
RejectedExecutionHandler handler//拒绝策略) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
举一个银行排队的例子来说明创建线程池的七大参数问题。
说明:
-
正常情况下银行柜台只开1号和2号窗口(corePoolSize核心线程数),最多有5个柜台同事开放(maximumPoolSize最大线程数)
-
在1号和2号窗口都有人的时候,剩下的人进入等候区(BlockingQueue阻塞队列),在等候区满的时候开启3,4,5号柜台。
-
在3,4,5窗口连续1小时没有人进入的 时候关闭3,4,5号窗口(keepAliveTime超时时间,TimeUnit超时单位)
-
在5个柜台全部开放且等候区满的情况下依然有人进入,这时候选择拒绝此人,拒绝此人的方法就是RejectedExecutionHandler拒绝策略
在阿里巴巴开发手册中明确要求不允许使用Excutors创建线程。而是通过ThreadPoolExecutor来创建,参数自己设置。
手动创建一个线程池案例
public static void main(String[] args) {
ExecutorService executorService= new ThreadPoolExecutor(
2,
5,
3,
TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy()
);
try {
for (int i = 0; i <5 ; i++) {
executorService.execute(()->{
System.out.println(Thread.currentThread().getName()+"执行");
});
}
} finally {
executorService.shutdown();
}
}
当i的最大值为5时会有2个线程在执行
当i的最大值为6时会有3个线程在执行
当i的最大值为7时会有4个线程在执行
当i的最大值为8时会有5个线程在执行
当i的最大值为9时会抛出异常
所以由此可知线程池最大允许线程执行个数为阻塞队列大小+最大线程数
20.2.1.CPU密集型和IO密集型
面试题:最大线程到底该如何定义?
1.cpu密集型:cpu是几核就定义最大线程数是几。
2.IO密集型:判断你程序中十分耗IO的线程,假设有10个,那就可以设置2倍于他的最大线程数。
20.3.四种拒绝策略
在上述例子中,使用了默认的拒绝策略AbortPolicy,还有三种拒绝策略
AbortPolicy:
- 阻塞队列满了,不处理,并抛出异常。
CallerRunsPolicy:
- 阻塞队列满了,哪里来的去哪里,从main线程来的就交个main线程执行
DisCardOldesPolicy:
- 阻塞队列满了,丢掉任务不管他并且不会抛出异常
DiscardPolicy:
- 阻塞队列满了,尝试和最早进入线程池的线程竞争,不会抛出异常
21.四大函数式接口
函数式接口:只有一个方法的接口。
Function函数式接口:有一个输入参数,有一个输出参数
Function<String,String> function = (str)->{return str;};
System.out.println(function.apply("123"));
断定型函数式接口:有一个输入参数,返回值只能是布尔值
Predicate<String> predicate = (str)->{return false;};
System.out.println(predicate.test("123"));
消费型接口:只有输入参数,没有返回值
Consumer<String> consumer = (str)->{
System.out.println(str);
};
consumer.accept("123");
供给型接口:没有参数,只有返回值
Supplier<String> supplier = ()->{return "123";};
System.out.println(supplier.get());
22.Stream流
什么是stream流?
存储应交给集合和数据库来做
计算交给流来做
案例
题目要求:
1分钟内完成此题,只能用1行代码实现!
现在有5个用户!筛选:
1、ID 必须是偶数
2、年龄必须大于23岁
3、用户名转为大写字母
4、用户名字母倒着排序
5、只输出1个用户!
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",26);
User u3 = new User(3,"c",24);
User u4 = new User(4,"d",25);
User u5 = new User(5,"e",26);
List<User> list = Arrays.asList(u1,u2,u3,u4,u5);
list.stream()
.filter(user->{return user.getId()%2==0;})//id为偶数
.filter(user->{return user.getAge()>23;})//年龄大于23岁
.map(user ->{return user.getName().toUpperCase();})//字母转换为大写
.sorted((user1,user2)->{return user2.compareTo(user1);})//倒序排序
.limit(1)//只允许一个输出
.forEach(System.out::println);//遍历
}
23.JMM
请你谈谈对Volatile的理解
Volatile是Java虚拟机提供轻量级的同步机制
1.保证可见性
2.不能保证原子性
3.禁止指令重排
什么是JMM
Java内存模型,不存在的东西,是一种概念,约定。
关于JMM一些同步的约定:
1.线程解锁前,必须把共享变量立刻刷回主存
2.线程加锁前,必须读取主存中的最新值到工作内存中!
3.加锁和解锁是同一把锁
8种操作:
问题:程序不知道主内存里的值已经修改过了
24.Volatile
1.保证可见性
private volatile static int num = 0;
public static void main(String[] args) throws InterruptedException {
new Thread(()->{
while (num==0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
}
num加了volatile保证可见性,如果不加volatile线程就监测不到主内存中的num的值已经修改了,就会陷入死循环。
2.不保证原子性
private volatile static int num;
public static void add(){
num++;
}
public static void main(String[] args) {
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main,gc
Thread.yield();//线程礼让
}
System.out.println(num);
}
理论上上述代码输出结果应为20000,实际上
这是由于volatile不能保证原子性。
那么如何保证原子性?
在java.util.concurrent.atomic下提供了原子类,可以保证原子性。
改造上述代码
private volatile static AtomicInteger num=new AtomicInteger();
public static void add(){
// num++;//不是一个原子性操作
num.getAndIncrement();//+1方法
}
public static void main(String[] args) {
for (int i = 0; i <20 ; i++) {
new Thread(()->{
for (int j = 0; j <1000 ; j++) {
add();
}
}).start();
}
while (Thread.activeCount()>2){//main,gc
Thread.yield();//线程礼让
}
System.out.println(num);
}
结果:
3.禁止指令重排
指令重排:程序并不会按照写的顺序来执行,计算机会重排
源代码–>编译器优化的重排–>指令并行也可能重排–>内存系统也会重排—>执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
线程A | 线程B |
---|---|
x=a | y=b |
b=1 | a=2 |
正常的结果:x=0,y=0;但是可能由于指令重排
线程A | 线程B |
---|---|
b=1 | a=2 |
x=a | y=b |
结果为:x=2,y=1.
volatile可以避免指令重排:
内存屏障。cpu指令。作用:
1.保证特定的操作的执行顺序
2.可以保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
25.CAS
CAS是原子类保证其原子性的原理。
CompareAndSet
源码:
如果期望的值符合则更新并且返回true,反之返回false。
代码举例:
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(1);
//参数1:期望值,参数2:更新的值
System.out.println(atomicInteger.compareAndSet(1, 2));
System.out.println(atomicInteger);
atomicInteger.getAndIncrement();
System.out.println(atomicInteger.compareAndSet(1, 2));
System.out.println(atomicInteger);
}
Unsafe类
Java无法操作内存
Java可以通过native调用C++操作内存。
Unsafe是java提供的操作内存的后门。
**CAS:**比较当前工作中内存的值和主内存中的值,如果这个值是期望的,那么就执行操作,如果不是就一直循环。
缺点:
1.循环会耗时。
2.一次性只能保证一个共享变量的原子性。
3.ABA问题。
ABA问题
26.原子引用
解决ABA问题,引入原子引用,对应的思想:乐观锁
原子引用在CAS基础上添加了版本号,记录对对象的修改操作。
即使内存中的值相同,版本号不同则会返回false
public static void main(String[] args) {
//参数1:初始值,参数2:初始版本号
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(1,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
//参数:期望值,更新值,期望版本号,更新版本号
atomicStampedReference.compareAndSet(1,2,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("a2=>"+atomicStampedReference.getStamp());
atomicStampedReference.compareAndSet(2,1,
atomicStampedReference.getStamp(),atomicStampedReference.getStamp()+1);
System.out.println("a3=>"+atomicStampedReference.getStamp());
},"a").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();//获得版本号
System.out.println("b1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicStampedReference.compareAndSet(1, 6, stamp, stamp + 1));
System.out.println("b2=>"+atomicStampedReference.getStamp());
},"b1").start();
}
27.各种锁
27.1.公平锁,非公平锁
公平锁:不允许插队,先来后到
非公平锁:允许插队。
非公平锁比公平锁效率更高,例如两个人上厕所,一个大便一个小便,虽然大便的先来,但是小便的先上厕所效率更高。
synchronized 是非公平锁
lock默认非公平锁,但可以手动设置
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
27.2.可重入锁
拿到了外面的锁,就可以拿到里面的锁,自动获得。
synchronized
public class Demo01 {
public static void main(String[] args) {
Phone1 phone1 = new Phone1();
new Thread(()->{
phone1.sms();
}).start();
new Thread(()->{
phone1.sms();
}).start();
}
}
class Phone1 {
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call();//这里也有一把锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
lock
class Phone1 {
private ReentrantLock lock = new ReentrantLock();
public void sms(){
lock.lock();
System.out.println(Thread.currentThread().getName()+"sms");
call();//这里也有一把锁
lock.unlock();
}
public void call(){
lock.lock();
System.out.println(Thread.currentThread().getName()+"call");
lock.unlock();
}
}
结果一样。
27.3.自旋锁
spinLock
当一个线程在获取锁的时候,如果锁已经被其它线程获取,那么该线程将循环等待,然后不断的判断锁是否能够被成功获取,直到获取到锁才会退出循环
自定义一个锁测试
public class SpinLockDemo {
//int 0, Thread null
AtomicReference<Thread> atomicReference = new AtomicReference<>();
//加锁
public void myLock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==》myLock");
//自旋锁,如果获取不到锁就一直循环等待
while (!atomicReference.compareAndSet(null,thread)){
}
}
public void myUnlock(){
Thread thread = Thread.currentThread();
System.out.println(Thread.currentThread().getName()+"==》myUnlock");
atomicReference.compareAndSet(thread,null);
}
}
public static void main(String[] args) throws InterruptedException {
SpinLockDemo spinLockDemo = new SpinLockDemo();
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
},"t1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
spinLockDemo.myLock();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
spinLockDemo.myUnlock();
},"t2").start();
}
t2必须等待t1将锁释放后才能执行解锁。