java基础部分
一.jvm内存划分
堆内存
堆存是线程共享的,所以堆是java虚拟机管理内存最大的一块内存区域,堆里一般存放一些对象信息,例如对象的实例变量。
方法区
方法区是线程共享的,用于存储已被虚拟机加载的类信息、常量、静态变量,如static修饰的变量加载类的时候就被加载到方法区中。
方法区还包含一个:运行时常量池,用来存放用于存放编译期间生成的各种字面量和符号引用。
Java6和6之前,常量池是存放在方法区中的。
Java7,将常量池是存放到了堆中,常量池就相当于是在永久代中,所以永久代存放在堆中。
Java8之后,取消了整个永久代区域,取而代之的是元空间。没有再对常量池进行调整。
虚拟机栈
线程私有,就是我们平时说的栈,栈描述的是Java方法执行的内存模型。
每个方法被执行的时候都会创建一个栈帧用于存储局部变量表,操作栈,动态链接,方法出口等信息。每一个方法被调用的过程就对应一个栈帧在虚拟机栈中从入栈到出栈的过程。
局部变量表:一片连续的内存空间,用来存放方法参数,以及方法内定义的局部变量,存放着编译期间已知的数据类型(八大基本类型)和对象引用(reference类型),returnAddress类型。它的最小的局部变量表空间单位为Slot,虚拟机没有指明Slot的大小,但在jvm中,long和double类型数据明确规定为64位,这两个类型占2个Slot,其它基本类型固定占用1个Slot。
本地方法栈
本地方法栈是与虚拟机栈发挥的作用十分相似,区别是虚拟机栈执行的是Java方法(也就是字节码)服务,而本地方法栈则为虚拟机使用到的native方法服务,可能底层调用的c或者c++,我们打开jdk安装目录可以看到也有很多用c编写的文件,可能就是native方法所调用的c代码。
程序计数器
程序计数器是一块很小的内存空间,它是线程私有的,可以认作为当前线程的行号指示器。
我们知道对于一个处理器(如果是多核cpu那就是一核),在一个确定的时刻都只会执行一条线程中的指令,一条线程中有多个指令,为了线程切换可以恢复到正确执行位置,每个线程都需有独立的一个程序计数器,不同线程之间的程序计数器互不影响,独立存储。
注意:如果线程执行的是个java方法,那么计数器记录虚拟机字节码指令的地址。如果为native【底层方法】,那么计数器为空。这块内存区域是虚拟机规范中唯一没有OutOfMemoryError的区域。
JMM内存模型
java内存模型是一组规则或者规范,它规定java中的各个变量(实例字段,静态字段构成数组元素的对象)的访问方式必须满足以下三个条件
- 可见性
- 原子性
- 有序性
java内存模型描述了在多线程代码中,哪些行为是正确的、合法的,以及多线程之间如何进行通信,代码中变量的读写行为如何反应到内存、CPU缓存的底层细节。
一句话概括:保证多线程之间操作共享变量的正确性。
JMM关于同步的规定
- 线程解锁前必须把共享变量的值刷新到主内存
- 线程加锁前必须读取主内存最新的值到自己的工作内存
- 加锁解锁是同一把锁
二.java基本数据结构
List
ArrayList 数组
LinkedList 链表
Vector 线程安全的ArrayList
Set
HashSet 无需的对象集合,不允许重复,底层使用map数据结构,实际上是value为null的HashMap,构造方法里new的是HashMap
LinkedHashSet 链表,有序 底层使用LinkedHashMap
TreeSet SortedSet接口的实现类,可实现默认排序和自定义排序 对应TreeMap 底层是红黑树,提高查找效率
Map
HashMap
LinkedHashMap
TreeMap 实现SortedMap接口,底层是红黑树实现
HashTable
ConCurrentHashMap
三.多线程
多线程的本质:
线程操作资源类,判断执行等待唤醒,严防多线程下的虚假唤醒
1.volatile关键字
volatile是java虚拟机提供的轻量级的同步机制,它有三个特点:
- 保证资源多线程直接的可见性
- 不保证原子性
- 禁止指令重排 (volatile,final,sychronize都可以禁止指令重排)
volatile适用于一个线程写,多个线程读的场景
常见面试题
1.volatile和sychronized的区别
2.lock和sychronized的区别
- sychronized(底层是monitor对象,通过monitorentry加锁和monitorexit释放锁,wait 和notify也依赖monitor对象)属于jvm层面的,是java的关键字,而lock是api层面的,是从jdk1.5之后引入的
- sychronized不要编码者去手动释放锁,当sychronized代码块执行完 ,系统会自动让线程释放对锁的占用,ReentrantLock则需要编码者手动通过unlock释放锁,否则可能造成死锁
- sychronized加锁后 不可被终端,除非抛异常或者正常完成.而lock可以中断,通过tryLock(long timeout,TimeUnit unit)方法,或者调用interrupt()方法
- sychronized是非公平锁,Lock可以通过构造方法来实现公平锁或者非公平锁
- sychronized锁不可以绑定条件,要么唤醒一个要么唤醒所有,而lock可以通过绑定conditon精确唤醒指定的线程
2.锁
公平锁非公平锁
公平锁:多个线程按照申请锁的顺序来获得锁。原理:使用队列,先进先出,来保证顺序
非公平锁:多个线程不是按照申请锁的顺序来获得锁,可能后来的线程申请锁时会直接被放入请求队列中
所以,它们的差别在于非公平锁会有更多的机会去抢占锁。
优缺点:非公平锁性能高于公平锁性能。非公平锁能更充分的利用cpu的时间片,尽量的减少cpu空闲的状态时间。但是非公平锁可能会造成优先级反转或者饥饿现象(即某个线程一直得不到锁,一直处于饥饿等待状态)
可重入锁(又叫递归锁)和不可重入锁
一个线程可以进入任何一个 该线程 已经拥有的锁所同步着的代码块
不可重入锁:只判断这个锁有没有被锁上,只要被锁上申请锁的线程都会被要求等待
可重入锁:不仅判断锁有没有被锁上,还会判断锁是谁锁上的,当就是自己锁上的时候,那么他依旧可以再次访问临界资源,并把加锁次数加一。
sychronized和ReentrantLock都是可重入锁
独占锁和共享锁(又叫读锁和写锁)
package com.shemuel.读写锁; import java.util.HashMap; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.ReentrantReadWriteLock; /** * 测试读写锁 */ public class ReentrantReadWriteLockDemo { public static void main(String[] args) { MyResource myResource = new MyResource(); for (int i = 1;i<6;i++){ final int temp = i ; new Thread(()-> myResource.put(temp+"",temp+""),"线程"+i).start(); } for (int i = 1;i<6;i++){ final int temp = i ; new Thread(()-> myResource.get(temp+""),"线程"+i).start(); } } } // 资源类 class MyResource{ // 缓存map private volatile Map<String,String> map = new HashMap<>(); private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 缓存写入 public void put(String key, String value){ readWriteLock.writeLock().lock(); try{ System.out.println(Thread.currentThread().getName()+"\t 正在写入 "+value); map.put(key,value); TimeUnit.MILLISECONDS.sleep(300); System.out.println(Thread.currentThread().getName()+"\t 写入完成"); }catch (Exception e){ e.printStackTrace(); }finally { readWriteLock.writeLock().unlock(); } } // 缓存读取 public String get(String key){ readWriteLock.readLock().lock(); String result = null; try { System.out.println(Thread.currentThread().getName()+"\t 正在读取"); result = map.get(key); TimeUnit.MILLISECONDS.sleep(200); System.out.println(Thread.currentThread().getName()+"\t 读取完成: "+ result); }catch (Exception e){ e.printStackTrace(); }finally { readWriteLock.readLock().unlock(); } return result; } // 缓存清空 public void clear(){ map.clear(); }
}
- 自旋锁 手写自旋锁: ```java public class SpinLock { AtomicReference<Thread> atomicReference = new AtomicReference<>(); // 加锁 public void myLock(){ Thread thread = Thread.currentThread(); System.out.println(thread.getName()+"线程尝试获得锁.."); while (!atomicReference.compareAndSet(null,thread)); System.out.println(thread.getName()+"线程已经获得锁.."); } // 释放锁 public void myUnlock() { Thread thread = Thread.currentThread(); atomicReference.compareAndSet(thread,null); System.out.println(thread.getName()+"线程释放锁.."); } public static void main(String[] args) { SpinLock spinLock= new SpinLock(); new Thread(()->{ spinLock.myLock(); // 上锁 try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } spinLock.myUnlock();// 释放锁 },"A").start(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } new Thread(()->{ spinLock.myLock(); spinLock.myUnlock(); },"B").start(); } }
3.常用并发的线程工具类
countDownLatch,CyclicBarrier、Semaphore和BlockingQueue是在java1.5被引入的处理多线程的工具类。
CountDownLatch
countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。类似于闭锁
代码示例:
package com.shemuel.同步工具类; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; /** * countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。 */ public class CountDownLatchDemo { public static void main(String[] args) { // 计数器,当数字减到为零的时候,才触发后面的事情 CountDownLatch countDownLatch = new CountDownLatch(2); System.out.println(Thread.currentThread().getName()+"线程开始..."); new Thread(()->{ try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程一执行完毕"); countDownLatch.countDown();// 减1 },"线程1").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程二执行完毕"); countDownLatch.countDown();// 减1 },"线程2").start(); System.out.println("等待其他两个线程执行完毕...."); try { countDownLatch.await(); // 主线程等待 } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("其他两个线程执行完毕,主线程执行完毕"); } }
使用场景:某个任务需要在其他若干个事情都完成后才能执行,例如,下课后班长需要等到所有同学走完之后才能锁门。
CyclicBarrier
CyclicBarrier可以使一定数量的线程反复地在栅栏(Barrier屏障处)位置处汇集。当线程到达栅栏位置时将调用await方法,这个方法将阻塞直到所有线程都到达栅栏位置。如果所有线程都到达栅栏位置,那么栅栏将打开,此时所有的线程都将被释放,而栅栏将被重置以便下次使用。
package com.shemuel.同步工具类; import java.util.concurrent.CyclicBarrier; public class CyclicBarrierDemo { public static void main(String[] args) { CyclicBarrier cb = new CyclicBarrier(7,()-> System.out.println("召唤神龙")); for (int i = 1;i<8;i++){ final int tem= i; new Thread(()-> { String current = Thread.currentThread().getName(); System.out.println(current+"召唤第"+tem+"颗龙珠"); try { cb.await(); // 线程被阻塞等待 } catch (Exception e) { e.printStackTrace(); } // 当所有线程都达到屏障点的时候,线程才被释放 System.out.println(current+"干接下来的活..."); },tem+"").start(); } } }
Semaphore
Semaphore 是 synchronized 和lock的加强版,主要有两个作用:
1,用于多个共享资源的互斥使用
2,用于并发线程数量的控制
package com.shemuel.同步工具类; import java.util.concurrent.Semaphore; import java.util.concurrent.TimeUnit; /** * 信号量主要用于两个目的 :1,用于多个共享资源的互斥使用 * 2.用于并发线程数量的控制 * 例如:10辆车抢占5个车位 */ public class SemaphoreDemo { public static void main(String[] args) { // Semaphore 可以代替sychronized和lock,当构造函数参数permits的值为1. // 则相当于sychronized和lock Semaphore semaphore = new Semaphore(5,false); for (int i = 1; i <=10;i++){ final int temp = i ; new Thread(()->{ try { semaphore.acquire(); // 抢占 System.out.println(Thread.currentThread().getName()+"抢到车位"); TimeUnit.SECONDS.sleep(3); System.out.println(Thread.currentThread().getName()+"停车三秒后离开"); } catch (Exception e) { e.printStackTrace(); }finally { semaphore.release(); // 用完后释放 } },i+"").start(); } } }
4.阻塞队列
当阻塞队列是空的时候,从阻塞队列里获取数据会被阻塞(而非抛异常)
当阻塞队列是满的时候,往阻塞堆里里添加数据会被阻塞(而非抛异常)
阻塞队列常用于生产者和消费者的场景,生产者是往队列里添加元素的线程,消费者是从队列里拿元素的线程。阻塞队列就是生产者存放元素的容器,而消费者也只从容器里拿元素。
常用的阻塞队列有以下三种:(BlockingQueue是与List的同级的接口,常用的实现类如下三种)
ArrayBlockingQueue
阻塞队列提供了如下四种方法:
package com.shemuel.阻塞队列; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.BlockingQueue; public class BlockingQueueDemo { public static void main(String[] args) { BlockingQueue<String> bq = new ArrayBlockingQueue(3); // 以下方法是抛异常组方法 bq.add("1"); bq.add("2"); bq.add("3"); // bq.add("4"); // 满了会抛出异常,IllegalStateException: Queue full bq.remove(); bq.remove(); bq.remove(); // bq.remove(); // 没有元素可移出了 NoSuchElementException BlockingQueue<String> bq1 = new ArrayBlockingQueue(3); // 以下方法会返回特殊值,向队列添加元素成功返回true,满了返回false // offer,和poll 方法,可以有参数,来实现超时退出, System.out.println(bq1.offer("1")); // 返回true System.out.println(bq1.offer("1")); System.out.println(bq1.offer("1")); System.out.println(bq1.offer("1")); // 满了 继续添加 ,会返回false System.out.println(bq1.poll()); // 返回被移除的元素的值 System.out.println(bq1.poll()); System.out.println(bq1.poll()); System.out.println(bq1.poll()); System.out.println(bq1.poll());// 没有元素了 ,会返回null BlockingQueue<String> bq2 = new ArrayBlockingQueue(3); // 以下方法,会阻塞 try { bq2.put("1"); bq2.put("1"); bq2.put("1"); // bq2.put("1"); // 队列满了 会被阻塞 System.out.println(bq2.take()); System.out.println(bq2.take()); System.out.println(bq2.take()); System.out.println(bq2.take()); System.out.println(bq2.take()); // 队列没有元素了,会被阻塞 } catch (InterruptedException e) { e.printStackTrace(); } } }
LinkedBlockingQueue
SychronousBlockingQueue
同步队列,队列里只存储一个元素。
package com.shemuel.阻塞队列; import java.util.concurrent.BlockingQueue; import java.util.concurrent.SynchronousQueue; import java.util.concurrent.TimeUnit; /** * 单元素的队列 * 同步队列 */ public class SychronousBlockingQueueDemo { public static void main(String[] args) { BlockingQueue<String> bq = new SynchronousQueue<>(); new Thread(()->{ try { System.out.println("放入第一个元素"); bq.put("1"); // 队列里放入一个元素后就被阻塞,直到队列为空后释放 System.out.println("放入第二个元素"); bq.put("2"); System.out.println("放入第三个元素"); bq.put("3"); } catch (Exception e) { e.printStackTrace(); } },"线程1").start(); new Thread(()->{ try { TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"取走一个元素:"+bq.take()); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"取走一个元素:"+bq.take()); TimeUnit.SECONDS.sleep(2); System.out.println(Thread.currentThread().getName()+"取走一个元素:"+bq.take()); } catch (InterruptedException e) { e.printStackTrace(); } },"线程二").start(); } }
5.线程池
线程池是用来控制线程运行的数量的,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程的数量超过了线程池的容量,则需排队等候,等其它线程执行完毕,再从队列中取出任务来执行.
线程池的主要特点:线程复用;控制最大并发数;管理线程
- 降低资源消耗.通过复用已经创建的线程,降低创建和销毁线程所带来的消耗
- 提高响应速度. 有任务需要执行时不需要等待线程的创建便可立即执行
- 提高线程的可管理性.线程是稀缺资源,使用线程池可以统一的进行分配,调优和监控
实现方式:使用Executors工具类
package com.shemuel.线程池; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * Created by dengshaoxiang on 2019/11/13 13:51 * description: */ public class TestExecutor { public static void main(String[] args) { //使用固定数量的线程池, 底层使用LinkedBlockingQueue ExecutorService pool= Executors.newFixedThreadPool(5); //会按照任务数量的多少 自动分配合适的线程执行,底层使用SychronousBlockingQueue ExecutorService pool1= Executors.newCachedThreadPool(); //单线程化的线程池,会将任务按照指定顺序执行,底层使用LinkedBlockingQueue ExecutorService pool2= Executors.newSingleThreadExecutor(); try { for (int i = 0; i < 12; i++) { pool1.execute(()-> System.out.println(Thread.currentThread().getName()+"执行任务")); } }finally { pool1.shutdown(); } } }
四.数据库
五.jdk8新特性
六.web相关
1.http和https
2.TCP/UDP传输协议
未完待续...