JUC(java多线程)
本文章学习狂神说JUC并发编程所做笔记~2020-9-15(点击跳到相应视频)
什么是JUC:
- java.util.concurrent;
- java.util.concurrent.atomic;
- java.util.concurrent.locks
什么是java多线程?
进程与线程
进程
- 当一个程序被运行,就开启了一个进程, 比如启动了qq,word
- 程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备
线程
- 一个进程内可分为多个线程
- 一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令
并行与并发
并发:单核cpu运行多线程时,时间片进行很快的切换。线程轮流执行cpu
并行:多核cpu运行 多线程时,真正的在同一时刻运行
java提供了丰富的api来支持多线程。
问:java真的开启线程吗?
不能,private native void start0();
,调用的本地方法;
获取CPU核数:Runtime.getRuntime().availableProcessors()
;
为什么用多线程?
多线程能实现的都可以用单线程来完成,那单线程运行的好好的,为什么java要引入多线程的概念呢?
多线程的好处:
- 程序运行的更快!快!快!
- 充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力
线程有几个状态
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
- 新生状态:创建线程对象时的状态
- 运行状态:线程获取到cpu的时间片,执行run()方法的逻辑
- 阻塞状态: 线程被阻塞,放弃cpu的时间片,等待解除阻塞重新回到就绪状态争抢时间片
- 等待:死死的等;
- 超时等待:等待/通知机制上增加了超时控制,即使方法执行时间过长,也不会永久阻塞调用者而是会按照调用者的要求按时返回
- 终止状态: 线程执行完成或抛出异常后的状态
wait/sleep区别
wait | sleep | |
---|---|---|
来自类 | Object | Thread |
关于锁的释放 | 会释放锁 | 不会释放锁 |
使用的范围 | 必须在同步代码块 | 可以在任何地方 |
是否需要捕获异常 | 不需要 | 必须 |
线程的相关方法总结
主要总结Thread类中的核心方法
方法名称 | 是否static | 方法说明 |
---|---|---|
start() | 否 | 让线程启动,进入就绪状态,等待cpu分配时间片 |
run() | 否 | 重写Runnable接口的方法,线程获取到cpu时间片时执行的具体逻辑 |
yield() | 是 | 线程的礼让,使得获取到cpu时间片的线程进入就绪状态,重新争抢时间片 |
sleep(time) | 是 | 线程休眠固定时间,进入阻塞状态,休眠时间完成后重新争抢时间片,休眠可被打断 |
join()/join(time) | 否 | 调用线程对象的join方法,调用者线程进入阻塞,等待线程对象执行完或者到达指定时间才恢复,重新争抢时间片 |
isInterrupted() | 否 | 获取线程的打断标记,true:被打断,false:没有被打断。调用后不会修改打断标记 |
interrupt() | 否 | 打断线程,抛出InterruptedException异常的方法均可被打断,但是打断后不会修改打断标记,正常执行的线程被打断后会修改打断标记 |
interrupted() | 否 | 获取线程的打断标记。调用后会清空打断标记 |
stop() | 否 | 停止线程运行 不推荐 |
suspend() | 否 | 挂起线程 不推荐 |
resume() | 否 | 恢复线程运行 不推荐 |
currentThread() | 是 | 获取当前线程 |
Object中与线程相关方法
方法名称 | 方法说明 |
---|---|
wait()/wait(long timeout) | 获取到锁的线程进入阻塞状态 |
notify() | 随机唤醒被wait()的一个线程 |
notifyAll(); | 唤醒被wait()的所有线程,重新争抢时间片 |
synchronized版
同步锁也叫对象锁,是锁在对象上的,不同的对象就是不同的锁,也可以锁方法;
该关键字是用于保证线程安全的,是阻塞式的解决方案。
让同一个时刻最多只有一个线程能持有对象锁,其他线程在想获取这个对象锁就会被阻塞,不用担心上下文切换的问题。
注意: 不要理解为一个线程加了锁 ,进入 synchronized代码块中就会一直执行下去。如果时间片切换了,也会执行其他线程,再切换回来会紧接着执行,只是不会执行到有竞争锁的资源,因为当前线程还未释放锁。
当一个线程执行完synchronized的代码块后 会唤醒正在等待的线程
synchronized实际上使用对象锁保证临界区的原子性 临界区的代码是不可分割的 不会因为线程切换所打断
//编程尽量使用OOPsixiang
public class SynchronizedDemo {
public static void main(String[] args) {
Ticket ticket = new Ticket();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"A").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"B").start();
new Thread(()->{
for (int i = 0; i < 20; i++) {
ticket.sell();
}
},"C").start();
}
}
class Ticket{
private int number = 50;
synchronized void sell(){
if(number>0){
System.out.println(Thread.currentThread().getName()+"买了第--------------->"+number);
number--;
}
}
}
lock版
所有已知实现类:
- ReentrantLock(可重入锁常用)
- ReentrantReadWriteLock.ReadLock(读锁)
- ReentrantReadWriteLock.WriteLock(写锁)
Lock lock = new ReentrantLock();//ReentrantLock的源码如下:
public ReentrantLock() {
sync = new NonfairSync();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
公平锁:十分公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)
jdk文档的Lock的使用说明:
没有块结构化锁定会删除使用synchronized
方法和语句发生的锁的自动释放。 在大多数情况下,应使用以下惯用语:
Lock l = ...; l.lock(); try { // access the resource protected by this lock } finally { l.unlock(); }
当在不同范围内发生锁定和解锁时,必须注意确保在锁定时执行的所有代码由try-finally或try-catch保护,以确保在必要时释放锁定。
使用:
//1.new一个Lock,2.加锁,3.解锁
public class LockDemo {
public static void main(String[] args) {
Ticket2 ticket = new Ticket2();
new Thread(()->{ for (int i = 0; i < 20; i++) { ticket.sell(); } },"A").start();
new Thread(()->{ for (int i = 0; i < 20; i++) { ticket.sell(); } },"B").start();
new Thread(()->{ for (int i = 0; i < 20; i++) { ticket.sell(); } },"C").start();
}
}
class Ticket2{
private int number = 50;
Lock lock = new ReentrantLock();
synchronized void sell(){
lock.lock();
try {
//业务代码
if(number>0){
System.out.println(Thread.currentThread().getName()+"买了第--------------->"+number);
number--;
}
}catch (Exception e){
e.printStackTrace();
}
finally {
lock.unlock();
}
}
}
synchronized和Lock的区别
synchronized | Lock |
---|---|
内置java关键字 | 一个java类 |
无法判断获取锁的状态 | 可以判断是否获取到了锁 |
会自动释放锁 | 手动释放锁,不释放会形成死锁 |
获得锁之外的线程会傻傻的等 | 此类情况,不一定会傻傻的等–>lock.tryLock() |
可重入锁,不可以中断,非公平 | 可重入的,可以判断锁,可以自己设置 |
适合锁少量的的代码同步问题 | 适合锁大量的同步代码 |
生产者和消费者
synchronized版(wait和notifyAll)
public class SynchronizedPC {
public static void main(String[] args) {
Demo demo = new Demo();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.jia();
};
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.jian();
};
},"B").start();
}
}
class Demo{
private int number = 0;
synchronized void jia(){
if(number!=0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"--------->"+number);
number++;
this.notifyAll();
}
synchronized void jian(){
if(number==0){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"--------->"+number);
number--;
this.notifyAll();
}
}
不足:线程比较多会产生虚假唤醒
结果如下:
解决办法:官方文档
-
-
-
线程也可以唤醒,而不会被通知,中断或超时,即所谓的***虚假唤醒*** 。 虽然这在实践中很少会发生,但应用程序必须通过测试应该使线程被唤醒的条件来防范,并且如果条件不满足则继续等待。 换句话说,等待应该总是出现在循环中,就像这样:
synchronized (obj) { while (<condition does not hold>) obj.wait(timeout); ... // Perform action appropriate to condition }
-
-
将if改成while即可
lock版(await和signal)
public class LockPC {
public static void main(String[] args) {
Demo1 demo = new Demo1();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.jia();
};
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.jian();
};
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.jia();
};
},"C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.jian();
};
},"D").start();
}
}
class Demo1{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
void jia(){
lock.lock();
try {
while(number!=0){
condition.await();
}
System.out.println(Thread.currentThread().getName()+"--------->"+number);
number++;
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void jian() {
lock.lock();
try {
while(number==0){
condition.await();
}
System.out.println(Thread.currentThread().getName() + "--------->" + number);
number--;
condition.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
Condition实现精准唤醒
public class ConditionPC {
public static void main(String[] args) {
Demo2 demo = new Demo2();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.one();
};
},"A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.two();
};
},"B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
demo.three();
};
},"C").start();
}
}
class Demo2{
private int number = 0;
Lock lock = new ReentrantLock();
Condition condition1 = lock.newCondition();
Condition condition2 = lock.newCondition();
Condition condition3 = lock.newCondition();
void one(){
lock.lock();
try {
while(number!=0){
condition1.await();
}
System.out.println(Thread.currentThread().getName()+"--------->"+number);
number=1;
condition2.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void two() {
lock.lock();
try {
while(number!=1){
condition2.await();
}
System.out.println(Thread.currentThread().getName() + "--------->" + number);
number=2;
condition3.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
void three() {
lock.lock();
try {
while(number!=2){
condition3.await();
}
System.out.println(Thread.currentThread().getName() + "--------->" + number);
number=0;
condition1.signalAll();
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
8锁现象
目的:深刻理解我们的锁。锁的是谁
synchronized:锁的是方法的调用者
1:
public class One {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{phone.A();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone.B();}).start();
}
}
class Phone {
synchronized void A() {
System.out.println("我正在吃饭");
}
synchronized void B() {
System.out.println("我正在睡觉");
}
}
方法A锁Phone,方法B锁Phone,所以输出A再B;
public class Two {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{phone.A();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone.B();}).start();
}
}
class Phone2 {
synchronized void A() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我正在吃饭");
}
//这里没有锁,不是同步方法,不受锁的影响
public void B() {
System.out.println("我正在睡觉");
}
}
方法A锁Phone2,方法B没有锁,A中有休眠,所有先输出B再A
public class Three {
public static void main(String[] args) {
Phone3 a = new Phone3();
Phone3 b = new Phone3();
new Thread(()->{a.A();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{b.B();}).start();
}
}
class Phone3 {
synchronized void A() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我正在吃饭");
}
synchronized void B() {
System.out.println("我正在睡觉");
}
}
(两把锁)方法A锁的是调用对象a,方法B锁的是调用对象b,A中有休眠,所有先输出B再A
public class Four {
public static void main(String[] args) {
Phone4 phone4 = new Phone4();
new Thread(()->{phone4.A();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{phone4.B();}).start();
}
}
class Phone4 {
static synchronized void A() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我正在吃饭");
}
static synchronized void B() {
System.out.println("我正在睡觉");
}
}
static方法,是类类一加载就有了,方法A锁class 模板,方法B锁的也是class模板,锁的相同Phone4.class,所以输出A再B;
public class Five {
public static void main(String[] args) {
Phone5 a = new Phone5();
Phone5 b = new Phone5();
new Thread(()->{a.A();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{b.B();}).start();
}
}
class Phone5 {
static synchronized void A() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我正在吃饭");
}
static synchronized void B() {
System.out.println("我正在睡觉");
}
}
static方法,是类类一加载就有了,方法A锁class 模板,方法B锁的也是class模板,锁的相同Phone5.class,所以输出A再B;
public class Six {
public static void main(String[] args) {
Phone6 a = new Phone6();
Phone6 b = new Phone6();
new Thread(()->{a.A();}).start();
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
new Thread(()->{b.B();}).start();
}
}
class Phone6 {
static synchronized void A() {
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我正在吃饭");
}
synchronized void B() {
System.out.println("我正在睡觉");
}
}
static方法,是类类一加载就有了,方法A锁class 模板,方法B锁的Phone6,锁的不相同,所以输出B再A;
总结:
锁的无非是:1.一个new出来的, this具体的一个手机2.static的是 Class的模板唯一的;
集合类不安全
并发下list的不安全;
List:Exception in thread “1” java.util.ConcurrentModificationException
public class ListDemo {
public static void main(String[] args) {
List<Integer> list = new ArrayList<>();
//List<Integer> list = new Vector<>();
//List<Integer> list = Collections.synchronizedList(new ArrayList<>());
//List<Integer> list = new CopyOnWriteArrayList<>();
for (int i = 1; i <= 20; i++) {
int tmp = i;
new Thread(()->{
list.add(tmp);
System.out.println(list);
},String.valueOf(tmp)).start();
}
}
}
解决方案:
1.List<Integer> list = new Vector<>();
2.List<Integer> list = Collections.synchronizedList(new ArrayList<>());
3.JUC版:List<Integer> list = new CopyOnWriteArrayList<>();
为什么CopyOnWriteArrayList能解决并发:源码
private transient volatile Object[] array;
CopyOnWrite机制:和单词描述的一样,他的实现就是写时复制, 在往集合中添加数据的时候,先拷贝存储的数组,然后添加元素到拷贝好的数组中,然后用现在的数组去替换成员变量的数组(就是get等读取操作读取的数组)。这个机制和读写锁是一样的,但是比读写锁有改进的地方,那就是读取的时候可以写入的 ,这样省去了读写之间的竞争,看了这个过程,你也发现了问题,同时写入的时候怎么办呢,当然果断还是加锁。
Vector的add方法源码:有synchronized的效率就会相对较低
public synchronized void addElement(E obj) {
modCount++;
ensureCapacityHelper(elementCount + 1);
elementData[elementCount++] = obj;
}
CopyOnWriteArrayList的add方法源码:
public boolean add(E e) {
final ReentrantLock lock = this.lock;
lock.lock();
try {
Object[] elements = getArray();
int len = elements.length;
Object[] newElements = Arrays.copyOf(elements, len + 1);
newElements[len] = e;
setArray(newElements);
return true;
} finally {
lock.unlock();
}
}
Set不安全
List:Exception in thread “1” java.util.ConcurrentModificationException
public class SetDemo {
public static void main(String[] args) {
Set<Integer> set = new HashSet<>();
//Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
//Set<Integer> set = new CopyOnWriteArraySet<>();
for (int i = 1; i <= 20; i++) {
int tmp = i;
new Thread(()->{
set.add(tmp);
System.out.println(set);
},String.valueOf(tmp)).start();
}
}
}
解决方案同理:
1.Set<Integer> set = new CopyOnWriteArraySet<>();
2.Set<Integer> set = Collections.synchronizedSet(new HashSet<>());
扩展:HashSet底层是HashMap
public HashSet() {
map = new HashMap<>();
}
Map不安全
public class MapDemo {
public static void main(String[] args) {
Map<Integer,Integer> map = new HashMap<>();
//Map<Integer,Integer> map = Collections.synchronizedMap(new HashMap<>());
//Map<Integer,Integer> map = new ConcurrentHashMap<>();
for (int i = 1; i <= 20; i++) {
int tmp = i;
new Thread(()->{
map.put(tmp,tmp);
System.out.println(map);
},String.valueOf(tmp)).start();
}
}
}
解决方案:
1.Map<Integer,Integer> map = Collections.synchronizedMap(new HashMap<>());
2.Map<Integer,Integer> map = new ConcurrentHashMap<>();
Callable
-
@FunctionalInterface public interface Callable<V>
返回结果并可能引发异常的任务。实现者定义一个没有参数的单一方法,称为
call
。Callable
接口类似于Runnable
,因为它们都是为其实例可能由另一个线程执行的类设计的。 然而,ARunnable
不返回结果,也不能抛出被检查的异常。该
Executors
类包含的实用方法,从其他普通形式转换为Callable
类。
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyThread myThread = new MyThread();
FutureTask futureTask = new FutureTask(myThread);
new Thread(futureTask,"A").start();
new Thread(futureTask,"B").start();
String s = (String)futureTask.get();
System.out.println(s);
}
}
class MyThread implements Callable<String>{
@Override
public String call() {
System.out.println("学习callable");
return "1024";
}
}
两条线程,执行一次call方法-----原因:结果会被缓存
常用辅助类
CountDownLatch
减法计数器
//计数器----》倒计时
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);
for (int i = 0; i < 6; i++) {
int tmp = i;
new Thread(()->{
System.out.println(Thread.currentThread().getName()+tmp);
},String.valueOf(i)).start();
countDownLatch.countDown();
}
//等待计数器归零
countDownLatch.await();
System.out.println("计数器归零了");
}
}
CyclicBarrier
加法计数器
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{ System.out.println("召唤神龙成功"); });
for (int i = 0; i < 7; i++) {
int tmp = i;
new Thread(()->{
System.out.println(tmp);
try {
cyclicBarrier.await();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (BrokenBarrierException e) {
e.printStackTrace();
}
},String.valueOf(tmp)).start();
}
}
}
Semaphore
-
一个计数信号量。 在概念上,信号量维持一组许可证。 如果有必要,每个
acquire()
都会阻塞,直到许可证可用,然后才能使用它。 每个release()
添加许可证,潜在地释放阻塞获取方。和抢车位差不多;限流也能用等等
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(4);
for (int i = 0; i < 7; i++) {
int tmp = i;
new Thread(()->{
try {
semaphore.acquire();
System.out.println("抢到了车位"+tmp);
TimeUnit.SECONDS.sleep(3);
System.out.println("离开了车位"+tmp);
} catch (Exception e) {
e.printStackTrace();
} finally {
semaphore.release();
}
},String.valueOf(tmp)).start();
}
}
}
semaphore.acquire();
获得,假如已经满了,需要等待被释放为止;
semaphore.release();
释放;
作用:多个共享资源互斥的使用;控制最大的线程数~安全;
读写锁(ReadWriteLock)
- A
ReadWriteLock
维护一对关联的locks
,一个用于只读操作,一个用于写入。read lock
可以由多个阅读器线程同时进行,只要没有作者。write lock
是独家的
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
for (int i = 0; i < 10; i++) {
int tmp =i;
new Thread(()->{
myCache.xie(String.valueOf(tmp),tmp);
},String.valueOf(i)).start();
}
for (int i = 0; i < 10; i++) {
int tmp = i;
new Thread(()->{
myCache.du(String.valueOf(tmp));
},String.valueOf(i)).start();
}
}
}
class MyCache{
private Map<String,Object> map =new HashMap<>();
private ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
void xie(String s,Object o){
reentrantReadWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"正在写"+s);
map.put(s,o);
System.out.println(Thread.currentThread().getName()+"写完毕"+s);
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.writeLock().unlock();
}
}
void du(String s){
reentrantReadWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()+"正在读"+s);
Object o = map.get(s);
System.out.println(Thread.currentThread().getName()+"读完毕"+s);
} catch (Exception e) {
e.printStackTrace();
} finally {
reentrantReadWriteLock.readLock().unlock();
}
}
}
独占锁(写锁)—>一次只能被一个线程占有;
共享锁(读锁)---->多个线程可以同时占有;
BlockingQueue(阻塞队列)
BlockingQueue
方法有四种形式,具有不同的操作方式,不能立即满足,但可能在将来的某个时间点满足:
- 一个抛出异常;
- 第二个返回一个特殊值(
null
或false
,具体取决于操作)不会抛出异常 - 第三个程序将无限期地阻止当前线程,直到操作成功为止;阻塞等待
- 而第四个程序块在放弃之前只有给定的最大时限;超时等待
方式 | 抛出异常 | 有返回值,不抛出异常 | 阻塞等待 | 超时等待 |
---|---|---|---|---|
添加 | add | offer | put | offer(“A”,2, TimeUnit.SECONDS) |
移除 | remove | poll | tack | poll(“A”,2, TimeUnit.SECONDS) |
检查队首元素 | eolement | peek |
public class BlockingQueueDemo {
public static void main(String[] args) {
//test1();
//test2();
//test3();
test4();
}
//抛出异常 存add,取remove
static void test1(){
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"));
}
//不抛出异常 存offer,取poll
static void test2(){
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"));
}
//不抛出异常,一直等待 存put,取tack
static void test3(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
try {
arrayBlockingQueue.put("A");
arrayBlockingQueue.put("B");
arrayBlockingQueue.put("C");
arrayBlockingQueue.put("D");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//不抛出异常 存offer,取poll,添加时间单位,超时等待
static void test4(){
ArrayBlockingQueue arrayBlockingQueue = new ArrayBlockingQueue<>(3);
System.out.println(arrayBlockingQueue.offer("B"));
System.out.println(arrayBlockingQueue.offer("C"));
System.out.println(arrayBlockingQueue.offer("D"));
try {
arrayBlockingQueue.offer("A",2, TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
同步队列(SynchronousQueue)
没有容量,进去一个元素,必须等待出来之后,才能再往里面放一个元素!只存储一个元素,放入put,取出take;
public class SynchronousQueueDemo {
public static void main(String[] args) {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();//同步队列
new Thread(()->{
try {
System.out.println("放入了A");
blockingQueue.put("A");
System.out.println("放入了B");
blockingQueue.put("B");
System.out.println("放入了C");
blockingQueue.put("C");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
new Thread(()->{
try {
System.out.println("取出了A");
blockingQueue.take();
System.out.println("取出了B");
blockingQueue.take();
System.out.println("取出了C");
blockingQueue.take();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
线程池
程序的运行,本质:占用系统的资源!优化资源的使用;产生池化技术:事先准备好一些资源,有人用,就来我这里拿,用完之后还给我;
池化技术:线程复用,可以控制最大并发数,管理线程
- 降低资源的消耗
- 提高响应的速度
- 方便管理
三大方法:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool:
允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool:
允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
//Executors 工具类,三大方法
public class Demo1 {
public static void main(String[] args) {
//单个线程
Executors.newSingleThreadExecutor();
//固定大小的线程池
Executors.newFixedThreadPool(5);
//可扩展的线程池,可大可小,有个最大限度
Executors.newCachedThreadPool();
}
}
ps:切记线程池用完要记得关闭;
七大参数
//newSingleThreadExecutor()源码:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//newFixedThreadPool(5);
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
//newCachedThreadPool();
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
本质:hreadPoolExecutor:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
- corePoolSize:核心线程池大小
- maximumPoolSize:最大核心线程池大小
- long keepAliveTime:超时没人调用就会释放
- TimeUnit unit:超时单位
- BlockingQueue workQueue:阻塞队列
- ThreadFactory threadFactory:线程工厂
- RejectedExecutionHandler handler:拒绝策略
四种拒绝策略
- AbortPolicy:队列满了,还有线程A进来,不处理A,并抛出异常(默认)
- CallerRunsPolicy:队列满了,还有线程A进来,不处理A,原路返回,不报异常(哪来的去哪里)
- DiscardPolicy:队列满了,还有线程A进来,不处理A,不抛出异常(丢掉任务)
- DiscardOldestPolicy:队列满了,还有线程A进来,会尝试和最先进来的线程竞争,不抛出异常
自定义线程池
public class Demo2 {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(
3,//int corePoolSize,
6,//int maximumPoolSize,
3,//long keepAliveTime,
TimeUnit.SECONDS,//TimeUnit unit,
new ArrayBlockingQueue<>(3),//BlockingQueue<Runnable> workQueue,
Executors.defaultThreadFactory(),//ThreadFactory threadFactory,
new ThreadPoolExecutor.AbortPolicy());//RejectedExecutionHandler handler);
}
}
小结且拓展
最大线程该如何定义:
CPU密集型:几核就是几,可以保持CPU的高效率:
int availableProcessors = Runtime.getRuntime().availableProcessors();
IO密集型:IO十分占用资源,判断任务十分耗IO的线程,一般设置2倍;
四大函数式接口
Package java.util.function
功能界面提供了lambda表达式和方法引用的目标类型。
函数型接口function:只有一个方法的接口
public interface Function<T, R> {
R apply(T t);
}
public class One {
public static void main(String[] args) {
// Function function = new Function<String,Integer>() {
// @Override
// public Integer apply(String o) {
// System.out.println("传入了字符串"+o+"--->输出了整数");
// return 1024;
// }
// };
Function<String,Integer> function = (o)->{
System.out.println("传入了字符串"+o+"--->输出了整数");
return 1024;
};
System.out.println(function.apply("wzf"));
}
}
断定型接口Predicate:
public interface Predicate<T> {
boolean test(T t);}
public class Two {
public static void main(String[] args) {
Predicate<String> predicate = (s)->{
System.out.println("断定型接口,输入一个参数,返回布尔值"+s);
return true;
};
System.out.println(predicate.test("ABC"));
}
}
供给型接口Supplier
public interface Supplier<T> {
T get();
}
public class Three {
public static void main(String[] args) {
Supplier<String> supplier =()->{
String s = "这是一个生产的借口";
return s;
};
System.out.println(supplier.get());
}
}
消费型接口Consumer
public interface Consumer<T> {
void accept(T t);
}
public class Four {
public static void main(String[] args) {
Consumer<String> Consumer = (s)->{
System.out.println(s);
};
String s = "这是一个消费型接口";
Consumer.accept(s);
}
}
Stream流式计算
/**
* 题目要求:一分钟内完成此题,只能用一行代码实现!
* 现在有5个用户!筛选:
* 1、ID 必须是偶数
* 2、年龄必须大于23岁
* 3、用户名转为大写字母
* 4、用户名字母倒着排序
* 5、只输出一个用户!
* */
public class Demo {
public static void main(String[] args) {
User u1 = new User(1,"a",21);
User u2 = new User(2,"b",22);
User u3 = new User(3,"c",23);
User u4 = new User(4,"d",24);
User u5 = new User(6,"e",25);
List<User> list = Arrays.asList(u1, u2, u3, u4, u5);
// 计算交给Stream流
// lambda表达式、链式编程、函数式接口、Stream流式计算
list.stream()
.filter(u->{return u.getId()%2==0;})
.filter(u->{return u.getAge()>23;})
.map(u->{return u.getName().toUpperCase();})
.sorted((a,b)->{return b.compareTo(a);})
.limit(1)
.forEach(System.out::println);
}
}
ForkJoin分之合并
特点:工作窃取,原因:维护的是双端队列
/**
* 如何使用forkjoin
* 1.forkjoinpool通过它来执行
* 2.计算任务forkjoinpool.execute(ForkJoinTask task)
* 3.ForkJoinTask
*/
public class ForkJoinDemo extends RecursiveTask<Long> {
private Long start;
private Long end;
private Long temp = 1000_0000L;
public ForkJoinDemo(Long start, Long end) {
this.start = start;
this.end = end;
}
@Override
protected Long compute() {
if(end - start < temp){
Long sum = 0L;
for (Long i =start;i<=end;i++ ){
sum+=i;
}
return sum;
}else {
Long middle = (start+end)/2;
ForkJoinDemo task1 = new ForkJoinDemo(start,middle);
task1.fork();
ForkJoinDemo task2 = new ForkJoinDemo(middle+1,end);
task2.fork();
return task1.join()+task2.join();
}
}
}
public class Demo {
public static void main(String[] args) {
Demo demo = new Demo();
//demo.Test1();//500000000500000000用时:6629
//demo.Test2();//500000000500000000用时:5807
demo.Test3();//500000000500000000用时:238
}
void Test1(){
Long start = System.currentTimeMillis();
Long sum = 0L;
for (Long i = 1L;i<=10_0000_0000;i++){
sum+=i;
}
Long end = System.currentTimeMillis();
System.out.println(sum+"用时:"+(end-start));
}
void Test2(){
Long start = System.currentTimeMillis();
ForkJoinPool forkJoinPool = new ForkJoinPool();
ForkJoinDemo task = new ForkJoinDemo(0L, 10_0000_0000L);
ForkJoinTask<Long> submit = forkJoinPool.submit(task);
try {
Long sum = submit.get();
Long end = System.currentTimeMillis();
System.out.println(sum+"用时:"+(end-start));
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
void Test3(){
Long start = System.currentTimeMillis();
//stream
long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum);
Long end = System.currentTimeMillis();
System.out.println(sum+"用时:"+(end-start));
}
}
异步回调(Future)
CompletableFuture
无返回值:
public class FutureDemo {
public static void main(String[] args) {
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("使用CompletableFuture,异步回调");
});
System.out.println("现在执行的是主线程,3秒后异步任务执行");
try {
completableFuture.get();
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
有返回值有成功和失败的返回:
public class FutureDemo2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
CompletableFuture<Integer> completableFuture =CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"有返回值");
//int i = 10/0;
return 1024;
});
System.out.println(completableFuture.whenComplete((t, u) -> {
System.out.println("t----->"+t);
System.out.println("u------->"+u);
}).exceptionally((e) -> {
System.out.println(e.getMessage());
return 404;
}).get());
}
}
JMM
JMM:java内存模型
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量立刻刷回主存。
- 线程加锁前,必须读取主存中的新值到工作内存中!
- 加锁和解锁是同一把锁
原理图:
Volatile
谈谈Volatile
Volatile 是 Java 虚拟机提供轻量级的同步机制
- 保证可见性
- 不保证原子性
- 禁止指令重排
保证可见性:
public class VolastileDemo {
volatile static int number = 0;
public static void main(String[] args) {
new Thread(()->{
while (number==0){
}
}).start();
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
number=1;
System.out.println(number);
}
}
不保证原子性
public class VolastileDemo2 {
volatile static int number = 0;
static void add(){
number++;
}
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 500; i1++) {
add();
}
}).start();
}
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(number);
}
}
除了synchronized和lock还可以使用原子类:java.util.concurrent.atomic
public class AcomicDemo {
volatile static AtomicInteger number = new AtomicInteger();
static void add(){
number.getAndIncrement();
}
public static void main(String[] args) {
for (int i = 0; i < 30; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 500; i1++) {
add();
}
}).start();
}
try {
TimeUnit.SECONDS.sleep(6);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(number);
}
}
Unsafe这些类的底层都是直接和操作系统挂钩的!在内存中修改值
禁止指令重排(不好演示)
源代码–》编译器优化的重排–》指令并行也可能会重排–》内存系统也会重排–》执行
内存屏障,CPU指令,作用:
保证特定得操作的执行顺序
可以保证某些变量的内存可见性
单例模式
构造器私有
反射不能破坏枚举的单例;
恶汉式;
public class HungryDemo {
private HungryDemo() {
}
private final static HungryDemo HUNGRY = new HungryDemo();
public static HungryDemo getInstance(){
return HUNGRY;
}
}
懒汉式
public class LazyDemo {
private LazyDemo() {
}
private static LazyDemo lazyDemo;
public static LazyDemo getInstance(){
if(lazyDemo==null){
lazyDemo = new LazyDemo();
}
return lazyDemo;
}
}
CAS
cas:compareAndSet比较并交换,底层是自旋锁,不是就会一直循环
public class CASDemo {
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子 性;
- ABA问题
ABA问题
//类似乐观锁
public class ABADemo {
public static void main(String[] args) {
//AtomicInteger atomicInteger = new AtomicInteger(2020);
AtomicStampedReference<Integer> atomicStampedReference = new AtomicStampedReference<>(10,1);
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("A1版本号为:"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(atomicStampedReference.compareAndSet(10, 6, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("A2版本号为:"+atomicStampedReference.getStamp());
System.out.println(atomicStampedReference.compareAndSet(6, 10, atomicStampedReference.getStamp(), atomicStampedReference.getStamp() + 1));
System.out.println("A3版本号为:"+atomicStampedReference.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"A").start();
new Thread(()->{
int stamp = atomicStampedReference.getStamp();
System.out.println("B1版本号为:"+stamp);
try {
TimeUnit.SECONDS.sleep(3);
System.out.println(atomicStampedReference.compareAndSet(10, 66, stamp, stamp + 1));
System.out.println("B2版本号为:"+atomicStampedReference.getStamp());
} catch (InterruptedException e) {
e.printStackTrace();
}
},"B").start();
}
}
ps:有个坑:如果设置的数过大,就不会缓存产生,而是new 出来;
锁的种类与理解
ReentrantLock(可重入锁常用):可重入锁又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。
ReentrantReadWriteLock.ReadLock(读锁):共享锁(读锁)---->多个线程可以同时占有;
ReentrantReadWriteLock.WriteLock(写锁):独占锁(写锁)—>一次只能被一个线程占有;
公平锁:十分公平,可以先来后到
非公平锁:十分不公平,可以插队(默认)
死锁:死锁是指两个或两个以上的进程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。
自旋锁:自旋锁是专为防止多处理器并发而引入的一种锁,它在内核中大量应用于中断处理等部分(对于单处理器来说,防止中断处理中的并发可简单采用关闭中断的方式,即在标志寄存器中关闭/打开中断标志位,不需要自旋锁)
等等~
ps:还有些内容没有写全,后面的有些理解不是很好,就没写入,谅解~
如何不足之处,请大神指教
记录生活 2020.9.15 早上吃了两个包子拿了快递便开始学习了,下午依然再学习,边看边做笔记,历时3天,到晚上9点,终于写完了笔记,以便以后不会的时候可以查阅。挺充实的,下一步学习一下JVM吧;一起加油~记录一句话:每一个不曾起舞的日子,都是对生命的辜负;