javaSE总结复习

一、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、多线程的实现方式有多少种

  1. 继承Thread类
  2. 实现Runnable接口
  3. 实现Callable接口
  4. 线程池

2、这几种多线程的实现方式有什么区别

  1. Thread和Runnable在实现上是差不多的,两者都需要重写Run()方式。但是由于Runnable是接口的实现方式,我们会用得比较多,因为接口可以多重实现,继承只能单继承。(比如:Student类一般是继承Person类的,如果为了实现多线程而急继承Thread类,就使得Student的父类不是抽象出来的)
  2. Callable相比于Thread和Runnable,Callable是支持返回值、泛型、异常的
  3. 线程池相比于其他三种实现方式性能是比较好的,我们可以设置线程池的最大连接数,连接时间等属性,能够减去了线程的创建和销毁所带来的代价。

3、线程的生命周期

  • 新建状态
    当Thread类或实现了Thread类的子类的对象创建时(new Thread()),线程就进入了新建状态
  • 就绪状态
  1. 当线程调用start()方法后,线程进入就绪状态,进入就绪队列等待cpu分配时间片。
  2. 调用join()方法结束后。
  3. 获得同步锁
  4. notify()、notifyAll()方法执行后会释放同步锁、并唤醒一个wait()的线程
  • 运行状态
    线程调用了start()方法后,如果此线程分配到cpu时间片,那么start()方法就会调用run()方法执行线程的核心代码。
  • 阻塞状态
    线程调用了sleep()、join()、wait()、suspend()方法后
  • 死亡状态
  1. 当Run()方法执行结束
  2. 调用stop()方法
  3. 出现异常且没有处理异常

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——不是线程安全的,但是在特殊情况下可以实现线程同步

java内存模型

在这里插入图片描述
从上图来看,线程A与线程B之间如要通信的话,必须要经历下面2个步骤:

  1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。
  2. 然后,线程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引发的三个问题

问题:

  1. ABA问题
    一个线程将数据A改变为B后又将数据改为A,CAS会认为这个变量没有发生变化。
  2. 自旋开销(设置循环时间)
    在线程对共享资源竞争大时,线程可能会一直自旋(循环判断变量是否被修改),线程自旋虽然不会引发cup上下文切换,但是会占用cup资源。
  3. CAS只能针对一个共享变量
    CAS只能针对一个共享变量。
    解决:
  4. ABA
    . 给共享变量添加一个版本号,每次对变量就行修改时版本号加一
  5. 自旋开销
    破环循环,给循环设定一个时间或者循环次数。
  6. 只能针对一个共享变量
    1、可以加锁来解决。2、.封装成对象类解决。

5、synchronized与volatile的区别

synchronized与volatile的区别

  1. synchronized支持可见性、原子性;volatile只支持可见性、不支持原子性
  2. synchronized能修饰变量、方法、类、volatile只能修饰变量
  3. synchronized会造成线程阻塞,volatile不会
  4. volatile修饰的变量不会被编译器优化、synchronized会

6、如何保证线程安全——需要保证程序执行的顺序性、操作的原子性、修改的可见性

  1. 什么是顺序性,就是程序执行的顺序必须与逻辑上的一样
  2. 原子性:也就是执行一些列操作的过程中全部操作都顺利依次执行,没有被打断。比如:可以将(读——改——写)这一些列操作看做一个原子(原子是不能被分割的)。如果保证(读——改——写)这一些列操作连续顺利执行,那么就是保证了原子性
  3. 修改可见性:当一个线程修改了一个数据后,其他线程能知道这个数据被修改了,那么就支持可见性。
    注意:
    1、volatile和synchronized是支持可见性的
    2、synchronized会创建一个内存屏障,内存屏障指令保证了所有CPU操作结果都会直接刷到主存中,从而保证了操作的内存可见性
    3、volatile关键字解决的是内存可见性的问题,会使得所有对volatile变量的读写都会直接刷到主存,即保证了变量的可见性。

四、集合

在这里插入图片描述
在这里插入图片描述

1、ArrayList

1.1初始化(new)
  1. 在指定长度(有参构造器)时在创建对象时会创建对应的数组
  2. 没有指定长度(空参构造器)时在创建对象时,不创建数组
  3. 当第一次调用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、特点
  1. 容量理论上是没有限制的
  2. 通过节点的指针可以快速获取临近节点
  3. 添加和删除效率高、遍历(查询)效率低
  4. 不是线程安全的

4、HashSet

HashSet 底层使用HashMap实现的(甚至可以说两者共用代码)。
HashSet是利用HashMap中的key进行存储。

4.1、特点
  1. 无序性(不是随机性)
  2. 不可重复
  3. 线程不安全
4.2、初始化
  1. jdk1.7前在new时创建初始容量为16的Node<K,V>[ ]数组
  2. jdk1.8以后是在第一次put()时创建长度为16的Node<K,V>[ ]数组
4.3、扩容——原来的2倍
  • HashSet里面有几个重要的常量:
  1. DEFAULT_LOAD_FACTOR 默认的加载因子:0.75(这个加载因子决定了数组什么时候扩容)
  2. DEFAULT_INITIAL_CAPACITY HashMap(HashSet就是用HashMap实现的)的初始容量:16
  3. 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、扩容
  1. jdk7 :底层结构是数组+链表
  2. jdk8:底层是数组+链表+红黑树
  3. 何时将链表转换为红黑树:当链表的长度大于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

推荐jvm专栏

(一)内存模型

(二)类加载

在这里插入图片描述

- 类初始化过程

在这里插入图片描述

(三)GC

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值