admin 管理员组文章数量: 1086019
JVM和JUC
JUC
进程/线程
进程:比如我们打开我们的word文档,而启动的这个文档就是我们的进程。可以在我们的任务管理器里面看见。
线程:word文档中帮我们纠正拼写的那个功能,会一直帮我们检测着,一直运行着的那个功能。
并发/并行
并发:多个线程抢同一份资源。比如12306当中,每个人是一条线程的话,多条线程抢一张票的过程称为并发。
并行:多种不一样的事情同时运行。
一共有三个包
java.util.concurrent
java.util.concurrent.atomic
java.util.concurrent.locks
SaleTicketDemo01
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @Author 断剑白发* @Date 2020/4/28 20:39* 题目:三个售票员 卖出 30张票* 在高内聚低耦合的前提下* 实现 线程 操作 资源类* 高内聚: 空调提供制冷或者制热的方法。实现原理都在其内部,我们只需要调用他给的接口即可。* 低耦合:比如空调的启用,我们如果要调用制冷或者制热的方法,只需要点一下按钮就可以了。* 所以 资源类 = 实例变量 + 实例方法* 即 空调 = 空调各种内部需要的参数 + 空调实现制冷和制热的方法* synchronized 是众锁 会锁住不必要的部分* lock 是小锁* 直接new 接口 来获取匿名内部类 所以可以不用去获取对象 直接匿名内部使用 可以少继承一个Runnable类* 多线程的调用还有状态都和底层的操作系统和CPU有关 所以每时每刻一共有几个线程都是不一定的* 状态分为NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED*/
class Ticket{public int num = 30;Lock lock=new ReentrantLock();public void sell(){lock.lock();try{if(num>0){System.out.println(Thread.currentThread().getName()+"卖出了第"+num--+"票,还剩下"+num+"张");}} finally {lock.unlock();}}
}public class SaleTicketDemo01 {public static void main(String[] args) {Ticket ticket=new Ticket();new Thread(()->{for(int i =0;i<30;i++) ticket.sell();},"线程A").start();new Thread(()->{for(int i =0;i<30;i++) ticket.sell();},"线程B").start();new Thread(()->{for(int i =0;i<30;i++) ticket.sell();},"线程C").start();}
}
LambdaExpressionDemo2
interface Foo {public int add(int x, int y);public default int mul1(int x, int y) {return x * y;}public default int mul2(int x, int y) {return x * y;}public static int div1(int x, int y) {return x / y;}public static int div2(int x, int y) {return x / y;}
}/*** 写lambda表达式的步骤:拷贝小括号 写死右箭头 落地花括号* 写lambda表达式的条件 该接口必须是函数式接口* 函数式接口:该接口中只有一个方法只有方法头 没有方法体* 为了缓解只能写一个方法的尴尬* 可以通过加上default或者static的方式来写方法 而且必须加上方法体* 并且default 或者 static的方法都可以写多个*/
public class LambdaExpressionDemo2 {public static void main(String[] args) {Foo foo = (int x, int y) -> {return x + y;};System.out.println(foo.add(3, 5));System.out.println(foo.mul1(3, 5) + ":" + foo.mul2(3, 5));System.out.println(Foo.div1(6, 3) + ":" + Foo.div2(6, 3));}
}
NotSafeDemo03
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.CopyOnWriteArraySet;/*** List,Set,HashMap都是线程不安全的* 1 报错:java.util.ConcurrentModificationException* 2 原因:因为多个线程请求同一份资源,在写操作时没有上锁,会导致写资源的时候出现争夺情况然后出错。* 例子:比如大家都要登记,但是笔只有一支。因为没有上锁,所以出现了争夺笔的情况。也就造成了写可能会出现错误。* 3 List线程不安全的解决方法* 1、new Vector();* 2、Collections.synchronizedList(new ArrayList<>());* 3、new CopyOnWriteArrayList<>();* 4 Set线程不安全的解决方法* new CopyOnWriteSet<>();* 5 HashMap线程不安全的解决方法* new ConcurrentHashMap<>();* 写时复制:* CopyOnWrite容器即写时复制的容器。往一个容器添加元素的时候,不直接往当前容器Object[]里面添加,而是先将容器Object[]进行Copy,* 复制出一个新的容器Object[] newElements,然后新的容器Object[] newElements里添加元素,添加元素之后,* 再将原容器的引用指向新的容器setArray(newElements);这样做的好处是可以对CopyOnWrite容器进行并发的读,* 而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite容器也是一种读写分离的思想,读和写不同的容器.* 这样子算是加上了锁,同时避免了因为使用synchronized带来的减弱并发的后果。因为synchronized锁住了所有人,不可读也不可写。* 但是这样子的话大家都可以读,只不过写的话只能一个个写。*/
public class NotSafeDemo03 {public static void main(String[] args) {}private static void NotSafeOfHashMap() {Map<String,Object> map=new ConcurrentHashMap<>();for (int i = 0; i < 30; i++) {new Thread(()->{map.put(Thread.currentThread().getName(), UUID.randomUUID().toString().substring(0,8));System.out.println(map);},String.valueOf(i)).start();}}private static void NotSafeOfSet() {Set<String> set=new CopyOnWriteArraySet<>();for (int i = 0; i < 11; i++) {new Thread(()->{set.add(UUID.randomUUID().toString().substring(0,8));System.out.println(set);},String.valueOf(i)).start();}}public void NotSafeOfList(){List<String> list=new CopyOnWriteArrayList<>();for (int i = 0; i < 11; i++) {new Thread(()->{list.add(UUID.randomUUID().toString().substring(0,8));System.out.println(list);},String.valueOf(i)).start();}}
}
八锁
package com.xjj.entity;import java.util.concurrent.TimeUnit;class Phone {public synchronized void sendSMS() {System.out.println(Thread.currentThread().getName() + "发送了短信");}public synchronized void sendEmail() {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "发送了邮件");}public synchronized void callPhone() {System.out.println(Thread.currentThread().getName() + "发起了电话");}public void charge() {System.out.println(Thread.currentThread().getName() + "给手机插上了充电器");}public static synchronized void sendQQ() {try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "发送QQ信息(静态方法)");}public static synchronized void callQQ() {System.out.println(Thread.currentThread().getName() + "发起了QQ电话(静态方法)");}
}/*** 情况1.使用1部手机,去执行两个同步方法。发送短信和打电话。* 结果:张三先发送短信,艾克后打电话。* 理解:因为synchronized会锁住当前对象中所有的有synchronized的方法,所以必定会等到短信息发完才会拨打电话。* 例子:张三发短信,手机在张三手里,捏得死死的。而艾克拿不到手机,只能等张三操作完先。所以先张三发短信,再是艾克打电话。** 情况2.使用1部手机,去执行两个同步方法。发送4秒钟的邮件和打电话* 结果:张三先发送邮件,艾克后打电话。* 理解:因为synchronized会锁住当前对象中所有的有synchronized的方法,所以必定会等到邮件发完才会拨打电话。* 例子:张三发邮件,手机在张三手里,捏得死死的。而艾克拿不到手机,只能等张三操作完先。所以先张三发邮件,再是艾克打电话。** 情况3.使用1部手机,去执行一个同步方法和一个普通方法。发送4秒钟的邮件和插上充电器.* 结果:艾克先插了充电器,张三再发送邮件。* 理解:因为synchronized只会锁住当前对象里面,有synchronized的方法。其他的方法是不会被锁住的,所以可以任意执行。* 例子:张三想要发送邮件,而艾克只是想给手机充电,两者的工作并不冲突,并不争夺资源。所以艾克先给手机插上充电器,再让张三发送邮件。** 情况4.两部手机,去执行两个同步方法。一个发送邮件,一个打电话。* 结果:艾克先打电话,张三再发送邮件。* 理解:因为synchronized只会锁住当前对象里面有synchronized的方法,所以其他对象中的方法是锁不住的,即无关,可以任意执行。* 例子:张三想要用小米手机发送邮件,艾克想要用苹果手机打电话。但是艾克打电话的速度比张三快,所以先是艾克,再是张三。** 情况5.一个手机,执行两个静态同步方法。一个发送qq消息,一个打qq电话.* 结果:张三先发送qq消息,艾克再拨打了qq电话。* 理解:因为static synchronized标识的是属于class的方法而非对象,所以锁住的是通过该class创建的对象中所有有static synchronized的方法。* 简易理解:每个对象有两部分,一部分是Class中的static标识符标识出来的部分,另一部分是自己new出来的对象,其中的内容都是不含static的。* 不管一个类new了多少个实例。其Class的static部分永远都只有一个。所以假设有两个对象,一个是A,一个是B。* A=静态+A自身内容。* B=静态+B自身内容。* 而其中的静态指向的是同一部分。* 所以,静态同步方法一旦锁住,就会锁住整个静态区域里面的同步方法。** 情况6.两个手机,两个静态同步方法。一个发送qq消息,一个打qq电话* 结果:张三先发送qq消息,艾克再拨打了qq电话。* 理解:class中所有的static synchronized的方法都呆在static区当中,都被锁住了。** 情况7.1个静态同步,一个普通同步。一部手机,一个发送qq消息,一个拨打电话。* 结果:艾克先拨打电话,张三再发送消息。* 理解:static synchronized锁住的只是静态区中的内容,并不会锁住对象区。* * 情况8.一个静态同步,一个普通同步。两部手机,一个发送qq消息,一个拨打电话。* 结果:艾克先拨打电话,张三再发送qq信息。* 理解:static synchronized锁住的只是静态区中的内容,并不会锁住对象区。*/
public class Test {public static void main(String[] args) throws InterruptedException {}private static void Solution1() throws InterruptedException {Phone mi = new Phone();new Thread(() -> {mi.sendSMS();}, "张三").start();Thread.sleep(1000);new Thread(() -> {mi.callPhone();}, "艾克").start();}private static void Solution2() throws InterruptedException {Phone mi = new Phone();new Thread(() -> {mi.sendEmail();}, "张三").start();Thread.sleep(1000);new Thread(() -> {mi.callPhone();}, "艾克").start();}private static void Solution3() throws InterruptedException {Phone mi = new Phone();new Thread(() -> {mi.sendEmail();}, "张三").start();Thread.sleep(1000);new Thread(() -> {mi.charge();}, "艾克").start();}private static void Solution4() throws InterruptedException {Phone mi = new Phone();Phone apple = new Phone();new Thread(() -> {mi.sendEmail();}, "张三").start();Thread.sleep(1000);new Thread(() -> {apple.callPhone();}, "艾克").start();}private static void Solution5() throws InterruptedException {Phone mi = new Phone();new Thread(() -> {mi.sendQQ();}, "张三").start();Thread.sleep(1000);new Thread(() -> {mi.callQQ();}, "艾克").start();}private static void Solution6() throws InterruptedException {Phone mi = new Phone();Phone apple = new Phone();new Thread(() -> {mi.sendQQ();}, "张三").start();Thread.sleep(1000);new Thread(() -> {apple.callQQ();}, "艾克").start();}private static void Solution7() throws InterruptedException {Phone mi = new Phone();new Thread(() -> {mi.sendQQ();}, "张三").start();Thread.sleep(1000);new Thread(() -> {mi.callPhone();}, "艾克").start();}private static void Solution8() throws InterruptedException {Phone mi = new Phone();Phone apple = new Phone();new Thread(() -> {mi.sendQQ();}, "张三").start();Thread.sleep(1000);new Thread(() -> {apple.callPhone();}, "艾克").start();}
}
ProdConsumerDemo04
class air{private int num = 0;public synchronized void increment() throws InterruptedException {while ( num != 0){this.wait();}num++;System.out.println(Thread.currentThread().getName()+"\t"+num);this.notifyAll();}public synchronized void decrement() throws InterruptedException {while ( num == 0 ){this.wait();}num--;System.out.println(Thread.currentThread().getName()+"\t"+num);this.notifyAll();}
}/*** 高内低耦前提下,线程操作资源类* 判断干活+通知。这是三个步骤,创建一个判断,一个执行的语句块,还有一个通知即唤醒线程。* 防止线程假唤醒:使用while进行判断。* 因为当使用if进行判断的时候,存在以下情况* 2个生产者,2个消费者的情况下,一个生产者正在生产,但是另外一个被卡在了if语句块内部,当生产完毕,被卡住的线程就有可能会被唤醒。* 唤醒以后并不会再次通过if进行循环,而是直接执行接下来的语句。所以会多生产一个。而循环往复,就有可能出现多个。* 所以需要改用while进行循环,条件不满足时,就一直进行判断,直到条件满足弹出while的情况才可以离开。*/
public class ProdConsumerDemo04 {public static void main(String[] args) throws InterruptedException {air air = new air();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.increment();}} catch (InterruptedException e) {e.printStackTrace();}},"厨师长").start();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.decrement();}} catch (InterruptedException e) {e.printStackTrace();}},"BigMoMo").start();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.increment();}} catch (InterruptedException e) {e.printStackTrace();}},"山治").start();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.decrement();}} catch (InterruptedException e) {e.printStackTrace();}},"路飞").start();}
}
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class air{private int num = 0;private Lock lock = new ReentrantLock();private Condition condition = lock.newCondition();public void increment() throws InterruptedException {lock.lock();try {while ( num != 0){condition.await();}num++;System.out.println(Thread.currentThread().getName()+"\t"+num);condition.signalAll();} finally {lock.unlock();}}public void decrement() throws InterruptedException {lock.lock();try {while ( num == 0 ){condition.await();}num--;System.out.println(Thread.currentThread().getName()+"\t"+num);condition.signalAll();} finally {lock.unlock();}}
}/*** 采用lock的方式来写这个例子,其中的this.wait和this.notifyAll的写法是传统的写法。* 这儿里面改用Lock生成Condition对象,然后由condition的方法来写。* 传统写法 condition变化* wait await* notifyAll signAll* 其中condition还包含解锁单个线程的方法,在下一个例子中有讲到。*/
public class ProdConsumerDemo04 {public static void main(String[] args) throws InterruptedException {air air = new air();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.increment();}} catch (InterruptedException e) {e.printStackTrace();}},"厨师长").start();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.decrement();}} catch (InterruptedException e) {e.printStackTrace();}},"BigMoMo").start();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.increment();}} catch (InterruptedException e) {e.printStackTrace();}},"山治").start();new Thread(()->{try {for (int i = 0; i < 10; i++) {air.decrement();}} catch (InterruptedException e) {e.printStackTrace();}},"路飞").start();}
}
ConditionDemo
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;class ShareData{private int number = 1;private Lock lock = new ReentrantLock();// 分别代表了三个线程private Condition condition1 = lock.newCondition();private Condition condition2 = lock.newCondition();private Condition condition3 = lock.newCondition();public void print5() throws InterruptedException {lock.lock();try {while( number != 1 ){condition1.await();}for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"打印了"+i);}// 这里的小要求,先改变标志位,再解锁第二个线程// 否则的话,如果先解锁了线程,但是这时候出了bug,number=2没有执行,那么到了第二个线程中间,就会一直卡在number的while判断中number = 2;condition2.signal();} finally {lock.unlock();}}public void print10() throws InterruptedException {lock.lock();try {while(number!=2){condition2.await();}for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName()+"打印了"+i);}number = 3;condition3.signal();} finally {lock.unlock();}}public void print15() throws InterruptedException {lock.lock();try {while(number!=3){condition3.await();}for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName()+"打印了"+i);}number = 1;condition1.signal();} finally {lock.unlock();}}
}/*** 关键点:使用condition中的signal()方法定点解锁某个线程。*/
public class ConditionDemo {public static void main(String[] args) {ShareData shareData = new ShareData();new Thread(()->{for (int i = 0; i < 10; i++) {try {shareData.print5();} catch (InterruptedException e) {e.printStackTrace();}}},"线程A").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {shareData.print10();} catch (InterruptedException e) {e.printStackTrace();}}},"线程B").start();new Thread(()->{for (int i = 0; i < 10; i++) {try {shareData.print15();} catch (InterruptedException e) {e.printStackTrace();}}},"线程C").start();}
}
callable接口实现多线程
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;class MyThread implements Callable<Integer> {@Overridepublic Integer call() throws Exception {System.out.println(Thread.currentThread().getName()+"执行了");return 1024;}
}/*** 1、创建一个实现了Callable的接口* 2、用一个FutureTask类来接收该接口* 3、然后通过FutureTask来创建线程* 4、使用get方法,拿到callable实现接口中的返回值*/
public class CallableDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());new Thread(futureTask,"线程A").start();System.out.println(futureTask.get());}
}
继承Thread接口实现多线程
class MyThread extends Thread {public void run(){System.out.println(Thread.currentThread().getName());}
}public class MyThreadDemo {public static void main(String[] args) {MyThread thread1=new MyThread();MyThread thread2=new MyThread();MyThread thread3=new MyThread();thread1.setName("线程A");thread2.setName("线程B");thread3.setName("线程C");thread1.start();thread2.start();thread3.start();}
}
CountDownLatchDemo
import java.util.concurrent.CountDownLatch;
//题目:要求六个小线程先结束,再去结束main线程
public class CountDownLatchDemo {//CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。//其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞)//当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。public static void main(String[] args) throws InterruptedException {CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 0; i < 6; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+"离开了教室");countDownLatch.countDown();},String.valueOf(i)).start();}countDownLatch.await();System.out.println("主线程结束了,离开了教室");}//由于这样子写,会导致一种情况的发生,就是主线程提前离开了,并不会等6个小线程结束再离开private static void close() {for (int i = 0; i < 6; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+"离开了教室");},String.valueOf(i)).start();}System.out.println("主线程结束了,离开了教室");}
}
CyclicBarrierDemo
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;public class CyclicBarrierDemo {//题目:要求先启动7个小线程,再去启动一个固定的线程。public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("成功启动七个龙珠,召唤了神龙");});for (int i = 0; i < 7; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName()+"收集到了一颗龙珠");try {cyclicBarrier.await();} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},String.valueOf(i)).start();}}
}
SemaphoreDemo
import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;/*** 在信号量上我们定义了两种操作:* acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1)* 要么一直等下去,直到有线程释放信号量,或超时* release (释放) 实际上会将信号量的值加1,然后唤醒等待的线程。* 信号量主要用于两个目的,一个是用于1多个共享资源的互斥使用,另一个用于并发线程数的控制。*/
public class SemaphoreDemo {//题目:六辆车,抢占三个车位,没抢到的就在外面等,抢到了的就在里面停车三秒钟,然后离开。再由下一辆车子补上。public static void main(String[] args) {Semaphore semaphore = new Semaphore(3);for (int i = 0; i < 6; i++) {new Thread(()->{try {semaphore.acquire();System.out.println(Thread.currentThread().getName()+"进入了车位");TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName()+"离开了车位");semaphore.release();} catch (InterruptedException e) {e.printStackTrace();}},String.valueOf(i)).start();}}
}
JVM
结构图(灰色的部分代表的是线程私有,同时内存极小,几乎不存在GC。亮色的代表线程共享)
ClassLoader加载器
class Car{}
/*** java文件编译以后->class文件。class文件经过ClassLoader->Class 模板* 每一个编译通过的class文件,都是通过类加载器来转换为Class。即我们的类模板。每一个对象都是从类模板生成出来的。* 对象.getClass.getClassLoader获取对象的类加载器* 而我们的类加载器一共有四类。java自带三类,还有一个是我们用来自定义的。* Bootstrap(用来加载java天生自带的,比如Object之类)。但是由于是c++编写的,所以是打印不出来的,只能打印出null来。* Extension(用来加载可扩展,比如我们的一些命名为javax开头的包)* AppClassLoader(我们自己手写的类)* 同时加载顺序为Bootstrap到Extension再到Application* 这也就是我们如果写了一个class如果类名和原生的一样不起作用的原因。* 因为先加载了原生的,所以就不会会加载我们手写的部分了。* 这个加载顺序是因为双亲委派机制* 当一个类收到了类加载器请求,他首先不会尝试自己去加载这个类,而是把这个* 请求委派给父类去完成,每一个层次类加载器都是如此,因此所有的加载请求都* 应该传送到启动类加载器中,只有当父类加载器反馈自己无法完成这个请求的时候* (在他的加载路径下没有找到所需加载的class),子类加载器才会尝试自己去加载* 采用双亲委派的一个好处是,比如加载位于rt.jar包中的类java.lang.Object,* 不管是哪个加载器加载这个类,最终都是委托给顶层的启动类加载器进行加载,* 这样就保证了使用不同的类加载器最终得到的都是同样一个Object对象* 即沙箱安全,保证了用户写的代码永远不会污染到java的源代码*/
public class Test {public static void main(String[] args) {Object object = new Object();System.out.println(object.getClass().getClassLoader());Car car = new Car();System.out.println(car.getClass().getClassLoader());System.out.println(car.getClass().getClassLoader()+"的父亲为"+car.getClass().getClassLoader().getParent());System.out.println(car.getClass().getClassLoader()+"的父亲为"+car.getClass().getClassLoader().getParent()+"的父亲为"+car.getClass().getClassLoader().getParent().getParent());}
}
native method stack
如果一个线程使用两次的start,就会报错。
java线程被启动以后,状态码就变成了1,是new的状态
如果再次启动,这时候就会因为线程状态不为0,即已经被开启来了,所以就会报线程状态异常的错误
native标识符代表了需要使用第三方语言或者底层操作系统的方法,不需要我们去操心
而这类方法,就会被直接装到native method stack中
我们自己写的方法放在java stack中
PC寄存器
每个线程中都会有程序的计数器,线程私有,就是一个指针,用来指向方法区中的方法字节码(用来存储指向下一条指令的地址,也即将要执行的指令代码)。因为是方法区的,所以在执行native中的时候,计数器是空的,因为native的底层是c。由执行引擎读取下一条指令,是一个非常小的内存空间,几乎可以忽略不记。
这块内存区域很小,他是当前线程所执行的字节码的行号指令器,字节码解释器通过改变这个计数器的值来选取下一条需要执行的字节码指令。
用以完成分支、循环、跳转、异常处理、线程回复等基础功能。不会发生内存溢出(OutOfMemory=OOM)的错误。
方法区
各个线程共享的运行时内存区域。他存储的是每个类的结构信息,即每个class文件被classLoader编译以后生成的Class模板文件。例如运行时常量池、字段和方法数据、构造方法和普通方法的字节码内容。方法区是规范,在不同虚拟机里头实现方法是不一样的,最典型的就是永久代和元空间。
实例变量存在于堆内存中,和方法区无关。
实例变量指的是new 出来的实例中带着的参数或者方法,这些单个对象的身上属性都是在堆中。
方法区中的是模板。
栈
栈管运行,堆管存储。线程私有,不存在垃圾回收。所以当线程结束的时候,栈内存也就释放了。
栈保存8种基本类型的变量+对象的引用变量+实例方法。
在java中,函数成为方法。在栈中,函数称为栈帧。每一个栈帧中都主要保存3类数据。本地变量:输入参数和输出参数。栈操作:记录出栈和入栈的操作。栈帧数据:包括类文件、方法等等。
相当于栈中有无数的栈帧,每一个栈帧都是一个方法,而这个方法中带着对应的参数。所以相当于栈中保存的是方法。
栈溢出是错误。可以从底层架构中找到。
p指的是栈中保存的指针地址,用来指向堆。
new Person()保存在堆中,用来存放访问类元数据的地址。
类元数据指的就是方法区中的类模板。
Person p = new Person();
//栈溢出的写法。同时栈溢出是错误,不是异常
public class Test {public static void main(String[] args) {m1();}public static void m1(){m1();}
}
堆
栈、堆、方法区
栈中保存的是基础数据类型的对象或者类对象的引用。
堆中保存的是new 出来的对象实例。
方法区保存编译以后的class模板文件
修改jvm中堆参数
//修改VM options内容为:-Xms1024m -Xmx1024m -XX:+PrintGCDetails
//用来查看当前虚拟机的内存
import java.util.Random;public class JVMNote {public static void main(String[] args) {long maxMemory = Runtime.getRuntime().maxMemory();long totalMemory = Runtime.getRuntime().totalMemory();System.out.println("-Xmx:MAX_MEMORY="+maxMemory+"(字节)、"+(maxMemory/(double)1024/1024)+"MB");System.out.println("-Xms:TOTAL_MEMORY="+totalMemory+"(字节)、"+(totalMemory/(double)1024/1024)+"MB");String str = "字符串";while(true){//一直新建对象,会导致堆中对象太多,GC速度不够快,导致堆被挤满了,然后就出现了OOM的错误str += str + new Random().nextInt(88888888)+new Random().nextInt(999999999);}}
}
GC四算法
1、引用计数法
缺点:每次对对象赋值时均要维护引用计数器,且对计数器本身也有一定的消耗。较难处理循环引用。
JVM的实现一般不采用这种方式。
2、复制算法
放在新生代中使用.即Eden还有两个幸存者区域
直接全体复制,只需要一步
缺点:耗费空间
优点:没碎片,非常快
大部分GC使用这种算法。
将Eden区的保留内容放到to中,将from的保留内容放到to中,然后将from和to进行交换。相当于空的那个还是to,有东西的还是from。
3、标记清除法
放在老年代中使用。
先标记,再清除,一共两步。
优点:解决了复制算法的耗费空间大的缺点
缺点:出现大量的碎片,相对复制算法来说需要两步操作,复制和清除,所以扫描时间太长
4、标记压缩
放在老年代中使用
在标记清楚的步骤上加了一步,整理所有的存货对象的引用地址。
缺点:效率更低了,因为又多了一步。
优点:没有了碎片。
建议使用:标记-清除-压缩
多次标记清除以后,在使用标记压缩。这样子不仅速度快了,而且碎片也少了。
具体情况分析:
重视内存效率情况下:复制算法》标记清除算法》标记整理算法(仅仅只是时间复杂度)
内存整齐度:复制算法=标记整理》标记清除
内存利用率:标记整理=标记清除》复制算法
年轻代相对于老年代来说比较小,对象存活率低。这种情况复制算法的回收整理,速度是最快的。复制算法的效率只和当前存活对象大小有关,因而很适用于年轻代的回收。而复制算法内存利用率不高的问题,通过hotspot中的两个servivor的设计得到缓解。
老年代的特点是区域比较大,对象存活率较高。这种情况,存在大量存活率高的对象,复制算法明显变得不合适。一般是由标记清除或者是标记清除与标记整理的混合实现。
JMM
分别有可见性,原子性,有序性。
可见性指的是线程A改完以后,必须让其他线程知道数据被修改了。
原子性,必须一次性完成所有的操作。
有序性:按照代码顺序执行。
由于JVM运行程序的实体是线程,而每个线程创建时JVM都会为其创建一个工作内存(有些地方称为栈内存),工作内存是每个线程的私有数据区域,而Java内存模型中规定所有变量都存储在主内存,主内存是共享内存区域,所有线程都可以访问,但线程对变量的操作(读取赋值等)必须在内存中进行,首先要将变量从主内存拷贝到的线程自己的工作内存空间,然后对变量进行操作完成后再将变量写回主内存,不能直接操作主内存中的变量,各个线程中的工作内存中存储着主内存中的变量副本拷贝,因此不同的线程间无法访问对方的工作内存,线程间的通信(传值)必须通过主内存来完成,其简要访问过程如下图:
//验证可见性的代码
class MyNumber{volatile int number = 10;public void update(){this.number = 1001;}
}public class JMM {public static void main(String[] args) {MyNumber myNumber=new MyNumber();new Thread(()->{try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}myNumber.update();System.out.println("结束");},"线程A").start();while(myNumber.number == 10){//这里其实有个问题,没理解透//如果这里面也加上了打印语句的代码,然后就算不加上volatile,其实主线程也是会被通知到了//但是如果不加,就会在这里一直无限月读}System.out.println("循环结束");}
}
//静态代码块和代码块和构造方法的执行顺序
class Num{static {System.out.println("最先执行,但是只执行一次-------1");}{System.out.println("执行构造方法之前执行,每次都执行--------2");}public Num(){System.out.println("每次都执行,但是是最后的----------3");}
}public class Test {static {System.out.println("最先执行,但是只执行一次-------4");}{System.out.println("执行构造方法之前执行,每次都执行--------5");}public Test(){System.out.println("每次都执行,但是是最后的----------6");}public static void main(String[] args) {//执行这句之前,先执行当前模板的static块System.out.println("我是美丽的分割线---------------------7");new Num();System.out.println("-------------");new Num();System.out.println("-------------");new Test();}
}
线程池
线程池的优势:
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超过数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:线程复用,控制最大并发数;管理线程。
第一:降低资源消耗速度。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
线程池的使用
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ThreadPoolDemo {public static void main(String[] args) {//ExecutorService executorService = Executors.newFixedThreadPool(5); //创建一个线程池,其中包含了五个线程//ExecutorService executorService = Executors.newSingleThreadExecutor(); //创建一个线程池,其中只有一个线程ExecutorService executorService = Executors.newCachedThreadPool(); //创建一个线程池,其中的线程数量是可变的for (int i = 0; i < 10; i++) {//线程池执行操作executorService.execute(() -> {System.out.println(Thread.currentThread().getName() + "执行了操作");});}//关闭线程池executorService.shutdown();}
}
底层实现
public static ExecutorService newFixedThreadPool(int nThreads) {return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());}
public static ExecutorService newSingleThreadExecutor() {return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
//实际上用的都是一个。new ThreadPoolExecutor()
//进去发现是这样的
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue) {//这里的this指的是自己的构造方法,所以再往下深入一层this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), defaultHandler);}
//构造方法
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;}
//其中涉及到了七种参数
//corePoolSize:线程池中的常驻核心线程数
//maximumPoolSize:此线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
//keepAliveTime:多余的线程的存活时间当前池中线程数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止
//unit:keepAliveTime的单位
//workQueue:任务队列,被提交尚未被执行的任务。
//threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认即可。
//handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程数时如何拒绝讲求执行的runnable的策略。
线程池底层工作原理
1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
如果正在运行的线程数量小于corePoolSize,那么线程池会马上创建线程执行这个任务。
如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和策略来执行。
当一个线程完成任务时,它会从队列中取下一个任务来执行。
当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
线程池的拒绝策略
import java.util.concurrent.*;/*** 四大拒绝策略的代码演示* AbortPolicy:直接报错,当需要的线程数量超过了最大线程数+候客区数量的时候* CallerRunPolicy:不报错,而是将多余的任务发配回原来的线程执行。即返回给主线程,让他自己执行。* DiscardPolicy:不报错,直接丢弃* DiscardOldestPolicy:不报错,丢弃队列中等待时间最长的任务。*/
public class Test {public static void main(String[] args) {ExecutorService executorService = new ThreadPoolExecutor(2,5,2L,TimeUnit.SECONDS,new LinkedBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.DiscardOldestPolicy());try{for (int i = 0; i < 9; i++) {executorService.execute(()->{System.out.println(Thread.currentThread().getName()+"执行任务");});}} finally {executorService.shutdown();}}
}
四大函数式接口
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;public class Test {public static void main(String[] args) {Consumer<String> consumer = new Consumer<String>() {@Overridepublic void accept(String s) {System.out.println("消费型接口,有传入值,但是没有传出");}};Consumer<String> lambdaConsumer = (String s)->{System.out.println("消费型接口,有传入值,但是没有传出.表达式的写法");};Supplier<String> supplier = new Supplier<String>() {@Overridepublic String get() {return "供给型接口,没有传入值,有固定的传出类型。";}};Supplier<String> lambdaSupplier = () -> {return "供给型接口,没有传入值,有固定的传出类型.表达式的写法";};Function<String,Integer> function = new Function<String, Integer>() {@Overridepublic Integer apply(String s) {System.out.println("函数型接口,有传入和传出");return 1;}};Function<String,Integer> lambdaFunction = (String s)->{System.out.println("函数型接口,有传入和传出.表达式的写法");return 1;};Predicate<String> predicate = new Predicate<String>() {@Overridepublic boolean test(String s) {System.out.println("判断型接口,有传入和传出,传出为固定的boolean");return false;}};Predicate<String> lambdaPredicate = (String s)->{System.out.println("判断型接口,有传入和传出,传出为固定的boolean.表达式的写法");return false;};consumer.accept("a");lambdaConsumer.accept("a");System.out.println(supplier.get());System.out.println(lambdaSupplier.get());function.apply("a");lambdaFunction.apply("a");predicate.test("a");lambdaPredicate.test("a");}
}
流式计算
package com.xjj.entity;import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;class Person {public Person(Integer personId, String personName, Integer personAge) {this.personId = personId;this.personName = personName;this.personAge = personAge;}@Overridepublic String toString() {return "Person{" +"personId=" + personId +", personName='" + personName + '\'' +", personAge=" + personAge +'}';}Integer personId;String personName;Integer personAge;
}/*** 流式计算的写法* filter:判断用,底层使用的是判断接口。返回的是boolean,即判断当前的对象是否符合条件,不符合条件时剔出stream* map:底层用的是Function接口,有传入和传出。* sorted:倒序的写法* limit用法和sql一样* .collect(Collectors.toList());将stream转化回list* 上一个计算结果都会作为下一个计算的可用参数*/
public class Test {public static void main(String[] args) {Person a = new Person(1, "a", 23);Person b = new Person(2, "b", 26);Person c = new Person(3, "c", 33);Person d = new Person(4, "d", 30);Person e = new Person(5, "e", 29);List<Person> list = Arrays.asList(a, b, c, d, e);list = list.stream().filter((Person p) -> {return p.personId % 2 != 0;}).filter((Person p) -> {return p.personAge > 23;}).map((Person p) -> {p.personName = p.personName.toUpperCase();return p;}).sorted((Person p1, Person p2) -> {return p2.personName.compareTo(p1.personName);}).limit(1).collect(Collectors.toList());System.out.println(list);}
}
分支合并框架
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.RecursiveTask;class MyTask extends RecursiveTask<Integer> {private static final Integer ADJUST_VALUE = 10;private int begin;private int end;private int result;public MyTask(int begin, int end) {this.begin = begin;this.end = end;}@Overrideprotected Integer compute() {if (end - begin <= ADJUST_VALUE) {for (int i = begin; i <= end; i++) {result = result + i;}} else {int middle = (begin + end) / 2;MyTask myTask01 = new MyTask(begin, middle);MyTask myTask02 = new MyTask(middle + 1, end);myTask01.fork();myTask02.fork();result = myTask01.join() + myTask02.join();}return result;}
}public class ForkJoinDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {MyTask myTask = new MyTask(0, 100);ForkJoinPool forkJoinPool = new ForkJoinPool();ForkJoinTask<Integer> forkJoinTask = forkJoinPool.submit(myTask);System.out.println(forkJoinTask.get());forkJoinPool.shutdown();}
}
异步调用
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
// 异步回调completableFuture这个线程。其中又分为有返回值的和没有返回值的。
public class CompletableFuture {public static void main(String[] args) throws ExecutionException, InterruptedException {// 没有返回值的写法CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{System.out.println(Thread.currentThread().getName()+"没有返回");});completableFuture.get();// 有返回值的写法CompletableFuture<Integer> completableFuture1=CompletableFuture.supplyAsync(()->{System.out.println(Thread.currentThread().getName()+"有返回");int age = 10/0;return 1024;});// t和u总有一个为空。// t是执行成功的返回值。u是执行时的报错。// 当报错时,还会执行exceptionally中的代码,并且有返回值completableFuture1.whenComplete((t,u)->{System.out.println("-------t:"+t);System.out.println("-------u:"+u);}).exceptionally(f->{System.out.println("出现了异常"+f.getMessage());return 4444;}).get();}
}
本文标签: JVM和JUC
版权声明:本文标题:JVM和JUC 内容由网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:http://www.roclinux.cn/b/1686729792a30240.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论