一、String、StringBuffer、StringBuilder
初始容量为16;扩容为原来的2倍+2
1、区别
String:是不可变的、线程安全的
StringBuffer:是可变的、线程安全的、性能低
StringBuilder:是可变的、线程不安全,性能高
2、为什么String可变、StringBuffer、StringBuilder不可变
String、StringBuffer、StringBuilder底层都是用字符数组存储字符的,String 的底层字符数组被final修饰,因此是不可变的,而StringBuffer、StringBuilder没有被final修饰,是可变的。
3、为什么StringBuffer是线程安全的
StringBuffer类里面的方法,基本都被synchronized关键词修饰,通过同步方法实现了线程同步。
4、StringBuffer、StringBuilder是怎么扩容的
首先,StringBuffer、StringBuilder在初始化时,也就是创建对象时,如果是通过空参构造器创建对象,那么会创建一个初始容量为16个字符数组用来存储字符。如果是有参构造器那么初始长度为,传进的字符串的长度加上16。
然后,通过通过append()方法添加字符串时,底层源码是这样实现的。首先计算出要添加的字符的长度加上已存入的字符长度,如果这个长度大于当前存放字符数组的长度,就会将原来的字符数组扩容为原来的2倍+2。然后利用Arrays.copyOf()将原来数组的字符复制到新的字符数组上。
5、常用方法
//增删改查
//添加
append()
//查
indexOf(0
//改,搭配 split() 分割字符
replace()
//删
delete()
//判断是否为空
isEmpty()
//将字符串转换为字符数组
toCharArray()
6、String创建字符串方式的区别
- 执行下面语句创建了几个字符串(对象),如果常量池里面没有“123”,那么就创建了两个,有就是一个
String str = new String("123");
- 判断是否相等
String ab = "abc123";
String str = "abc";
String a="123";
String b= new String("123");
String s1 = str+"123";
String s2 = "abc"+"123";
String s3 = str + a;
String s4 = str + b;
String s5 = "abc"+ b;
System.out.println(ab==s2);
System.out.println(ab==s1);
System.out.println(s1==s2);
System.out.println(s2==s3);
System.out.println(s1==s3);
System.out.println(s2==s5);
System.out.println(s2==s4);

注意:
1、常量与常量的拼接结果在常量池,且常量池中不会存在相同内容的常量.
2、只要其中有一个是变量,结果就在堆中
3、如果拼接的结果调用intern()方法,返回值就在常量池中
7、三者怎么相互转换
调用构造器传参即可
二、java异常

三、多线程
1、多线程的实现方式有多少种
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 线程池
2、这几种多线程的实现方式有什么区别
- Thread和Runnable在实现上是差不多的,两者都需要重写Run()方式。但是由于Runnable是接口的实现方式,我们会用得比较多,因为接口可以多重实现,继承只能单继承。(比如:Student类一般是继承Person类的,如果为了实现多线程而急继承Thread类,就使得Student的父类不是抽象出来的)
- Callable相比于Thread和Runnable,Callable是支持返回值、泛型、异常的
- 线程池相比于其他三种实现方式性能是比较好的,我们可以设置线程池的最大连接数,连接时间等属性,能够减去了线程的创建和销毁所带来的代价。
3、线程的生命周期
- 新建状态
当Thread类或实现了Thread类的子类的对象创建时(new Thread()),线程就进入了新建状态 - 就绪状态
- 当线程调用start()方法后,线程进入就绪状态,进入就绪队列等待cpu分配时间片。
- 调用join()方法结束后。
- 获得同步锁
- notify()、notifyAll()方法执行后会释放同步锁、并唤醒一个wait()的线程
- 运行状态
线程调用了start()方法后,如果此线程分配到cpu时间片,那么start()方法就会调用run()方法执行线程的核心代码。 - 阻塞状态
线程调用了sleep()、join()、wait()、suspend()方法后 - 死亡状态
- 当Run()方法执行结束
- 调用stop()方法
- 出现异常且没有处理异常
4、解决同步问题的方法有哪些
4.1、同步代码块
synchronized (同步监视器){
//需要实现同步的代码
}
注意:
1、同步监视器可以是任何对象
2、同步监视器在多个线程中必须是唯一的。
4.2、同步方法
使用synchronized修饰需要实现同步的方法。
package synchronized_;
/*
*实现:线程安全的方法
*
* 方式二:同步方法
* 将synchronized写到方法上
*
* */
public class MySynchronizedDemo2 extends Thread {
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket>0){
show();
}else {
break;
}
}
}
//private synchronized void show() { 错误的写法,这时锁对象为(this)当前对象,但是当前对象对于三个线程来说不是唯一的
private static synchronized void show() { //正确的写法 因为方法为静态的,因此,锁对象为MySynchronizedDemo2.class
if (ticket>0){
System.out.println(Thread.currentThread().getName()+"出票:"+ticket);
ticket--;
}
}
}
class TestThreadDemo3{
public static void main(String[] args) {
MySynchronizedDemo2 threadDemo1 = new MySynchronizedDemo2();
MySynchronizedDemo2 threadDemo2 = new MySynchronizedDemo2();
MySynchronizedDemo2 threadDemo3 = new MySynchronizedDemo2();
threadDemo1.setName("窗口一");
threadDemo2.setName("窗口二");
threadDemo3.setName("窗口三");
threadDemo1.start();
threadDemo2.start();
threadDemo3.start();
}
}
注意:
1、使用同步方法是,synchronized的默认同步监视器为this(当前对象)
4.3、Lock
1、Lock与synchronized的区别
synchronized的锁操作是由jvm实现的,不能显示的指定上锁的和解锁的时机。
Lock提供的锁功能与synchronized类似,只是Lock是Jdk提供给我们的API我们通过Lock这个api能显示的指定上锁和解锁的时间。
2、Lock是一个接口,我们想要使用Lock接口的功能就是使用它的实现类——ReentrantLock
4.4、volatile——不是线程安全的,但是在特殊情况下可以实现线程同步

从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:
- 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
- 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。
注意:当变量被volatile修饰后,线程对此变量的操作(读、写)是直接对主内存的变量进行操作,也就是跨过了本地内存。这样就能保证一个线程修改共享数据对其他线程的可见性
4.5、CAS——一种算法(乐观锁)
package atomic;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 一些封装好的原子操作
*
* @author 15594
*/
public class AtomicTest {
// static int count = 0;
static AtomicInteger count = new AtomicInteger(0);
static boolean a = true;
public static void main(String[] args) {
for (int i = 0; i <2 ; i++) {
new Thread(){
@Override
public void run() {
if (a){
try {
sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
a=false;
}
for (int j = 0; j <100 ; j++) {
//使用cas+Volatile实现,点就去可以看到基于cas+Volatile的实现原理
count.incrementAndGet();
}
}
}.start();
}
try{
Thread.sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
System.out.println(count);
}
}
4.6、CAS引发的三个问题
问题:
- ABA问题
一个线程将数据A改变为B后又将数据改为A,CAS会认为这个变量没有发生变化。 - 自旋开销(设置循环时间)
在线程对共享资源竞争大时,线程可能会一直自旋(循环判断变量是否被修改),线程自旋虽然不会引发cup上下文切换,但是会占用cup资源。 - CAS只能针对一个共享变量
CAS只能针对一个共享变量。
解决: - ABA
. 给共享变量添加一个版本号,每次对变量就行修改时版本号加一 - 自旋开销
破环循环,给循环设定一个时间或者循环次数。 - 只能针对一个共享变量
1、可以加锁来解决。2、.封装成对象类解决。
5、synchronized与volatile的区别
- synchronized支持可见性、原子性;volatile只支持可见性、不支持原子性
- synchronized能修饰变量、方法、类、volatile只能修饰变量
- synchronized会造成线程阻塞,volatile不会
- volatile修饰的变量不会被编译器优化、synchronized会
6、如何保证线程安全——需要保证程序执行的顺序性、操作的原子性、修改的可见性
- 什么是顺序性,就是程序执行的顺序必须与逻辑上的一样
- 原子性:也就是执行一些列操作的过程中全部操作都顺利依次执行,没有被打断。比如:可以将(读——改——写)这一些列操作看做一个原子(原子是不能被分割的)。如果保证(读——改——写)这一些列操作连续顺利执行,那么就是保证了原子性
- 修改可见性:当一个线程修改了一个数据后,其他线程能知道这个数据被修改了,那么就支持可见性。
注意:
1、volatile和synchronized是支持可见性的
2、synchronized会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性
3、volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。
四、集合


1、ArrayList
1.1初始化(new)
- 在指定长度(有参构造器)时在创建对象时会创建对应的数组
- 没有指定长度(空参构造器)时在创建对象时,不创建数组
- 当第一次调用add()方法时,会默认创建容量为10的集合
//指定数组大小容量
ArrayList list = new ArrayList(7);
//-------------源码
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
}
}
//不指定
ArrayList list1 = new ArrayList();
//-----------------源码
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
1.2、扩容
- 扩容为原来的1.5倍
- int newCapacity = oldCapacity + (oldCapacity >> 1); old= 6 ;new = 6+6/2 = 9;
扩容源码
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
// oldCapacity >> 1 (右移运算, oldCapacity / 2^1)
int newCapacity = oldCapacity + (oldCapacity >> 1);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
// minCapacity is usually close to size, so this is a win:
elementData = Arrays.copyOf(elementData, newCapacity);
}




注意:当使用空参构造器创建ArrayList时,一开始时没有给List创建存储数组的,在第一次调用add()方法时创建
1.3、特点
- 不是线程安全的
- 查询、修改效率高(根据下标查询indexOf();根据下标修改add(index,value))
2、Vector
Vector实现上与ArrayList相似,只是Vector是线程安全的,因为Vector中许多方法都被synchronized修饰,因此这些方法变成了同步方法
特点:
1、Vector 线程安全
2、效率比较低
扩容:如果创建Vector 指定了每次扩容多大,那么就扩容多大。如果没有指定,为原来的2倍。
//Vector 构造方法(初始大下:initialCapacity , 扩容大小:capacityIncrement)
public Vector(int initialCapacity, int capacityIncrement) {
super();
if (initialCapacity < 0)
throw new IllegalArgumentException("Illegal Capacity: "+
initialCapacity);
this.elementData = new Object[initialCapacity];
this.capacityIncrement = capacityIncrement;
}
//扩容
private void grow(int minCapacity) {
// overflow-conscious code
int oldCapacity = elementData.length;
int newCapacity = oldCapacity + ((capacityIncrement > 0) ?
capacityIncrement : oldCapacity);
if (newCapacity - minCapacity < 0)
newCapacity = minCapacity;
if (newCapacity - MAX_ARRAY_SIZE > 0)
newCapacity = hugeCapacity(minCapacity);
elementData = Arrays.copyOf(elementData, newCapacity);
}
3、LinkedList
1、LinkedList使用节点进行存储数据
2、节点中存在数据域和指针域
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
3.1、初始化
初始化时不创建第一个节点,当调用第一次add()方法时才创建表头。
3.2、特点
- 容量理论上是没有限制的
- 通过节点的指针可以快速获取临近节点
- 添加和删除效率高、遍历(查询)效率低
- 不是线程安全的
4、HashSet
HashSet 底层使用HashMap实现的(甚至可以说两者共用代码)。
HashSet是利用HashMap中的key进行存储。
4.1、特点
- 无序性(不是随机性)
- 不可重复
- 线程不安全
4.2、初始化
- jdk1.7前在new时创建初始容量为16的Node<K,V>[ ]数组
- jdk1.8以后是在第一次put()时创建长度为16的Node<K,V>[ ]数组
4.3、扩容——原来的2倍
- HashSet里面有几个重要的常量:
- DEFAULT_LOAD_FACTOR 默认的加载因子:0.75(这个加载因子决定了数组什么时候扩容)
- DEFAULT_INITIAL_CAPACITY HashMap(HashSet就是用HashMap实现的)的初始容量:16
- threshold 扩容临界值:threshold = DEFAULT_INITIAL_CAPACITY * DEFAULT_LOAD_FACTOR
当 Node<K,V>[ ]数组的长度大于等于扩容临界值(threshold)时, Node<K,V>[ ]数组会扩容为原来的两倍。为什么不是等到容量到达最大容量时扩容呢?因为节点插入的位置是根据 i = (n - 1) & hash 计算出来的。因此不一定能恰好将数组所有的位置填满。
4.4、如何确保加入的元素是唯一的



注意:如果添加的元素是自定义类的对象需要重写hashCode()和equals()方法,否者HashSet无法判断两个对象是否相等。因为hash值是通过hashCode()计算出来的。像String 的 equals()是重写过的。
5、LinkedHashSet
LinkedHashSet与HashSet实现原理相似,只是LinkedHashSet的节点添加了before节点和after节点,这两个节点用来记录此节点的前节点和后节点的地址(引用)

5、TreeSet
TreeSet多用于排序,TreeSet是一颗树。
规定左节点比根节点小,右节点比根节点大
6、HashMap
HashMap的实现原理于HashSet一致。
先判断key是否(存在)添加过。不存在直接将key、value以node节点的方式存储到数值中,如果key存在,就将value的值替换。
6.1、扩容
- jdk7 :底层结构是数组+链表
- jdk8:底层是数组+链表+红黑树
- 何时将链表转换为红黑树:当链表的长度大于8且整个数组的长度大于64时,将链表转换为红黑树(为了加快搜索速度,链表过长不利于遍历)
7、HashTable
特点:
1、线程安全
2、key和value都不能为null
8、HashTable的子类——Properties
特点:用于读取.Properties文件,结合io流使用
也是线程安全的。许多方法被synchronized修饰。
FileInputStream fileInputStream = new FileInputStream("src/classreflex/jdbc.properties");
Properties properties = new Properties();
properties.load(fileInputStream);
String user = properties.getProperty("user");
System.out.println(user);
String password = properties.getProperty("password");
System.out.println(password);
9、线程安全的集合
- Vector
- HashTable
- ConcurrentHashMap
- CopyOnWriteArrayList
- CopyOnWriteArraySet
- Collections.synchronizedList() 返回的集合
- Collections.synchronizedSet()
- Collections.synchronizedMap()
注意:
1、在StringBuffer线程安全的
2、jdk7与jdk8中ConcurrentHashMap实现线程安全的区别
- jdk7
分段锁+synchronized - jdk8
CAS+synchronized
五、 jvm
(一)内存模型
(二)类加载

- 类初始化过程

1万+

被折叠的 条评论
为什么被折叠?



