admin管理员组

文章数量:1546232

一、集合

1、Iterable

(1)List
/**
 *
 * List
 *     有索引,有序(插入元素的先后顺序),允许重复的元素
 *      常用的List实现类:ArrayList、LinkedList
 *      ArrayList: 底层基于数组结构,查询效率高,增删效率低
 *      LinkedList: 底层基于链表结构,增删效率高,查询效率低,LinkedList中有很多方法可以操作首位元素
 *ArrayList
 *     ArrayList是线程不安全的,尽量声明为局部变量,在方法或者代码块中使用
 *     如果想在成员变量中定义ArrayList,要考虑到线程安全问题,建议使用
 *     CopyOnWriteArrayList或者调用Collections.synchronizedList()方法将ArrayList转换为一个线程安全的List
 *
 *     CopyOnWriteArrayList 通过“写时复制”保证高并发下的线程安全问题
 *     Collections.synchronizedList()是创建一个新的List,线程安全,底层通过同步锁来保证线程安全
 *
 *      扩展: CopyOnWriteArrayList “写时复制”
 *
 *LinkedList
 *      链表结构,有很多首尾元素操作的方法
 *      使用LinkedList可以模拟栈(先进后出)和队列(先进先出)这两种数据结构
 *
 * ArrayList和LinkedList的选择:
 *
 *      项目中一定要选好,效率影响非常大
 *      查询多:ArrayList
 *      增删多:LinkedList
 *
 */
①ArrayList

底层基于数组结构,查询效率高,增删效率低


②LinkedList

底层基于链表

(2)Set
/**
 * Set:
 *
 *      没有索引,无序(插入顺序),自然排序或者定制排序规则,元素不重复(定制去重规则)
 *      常用的Set:HashSet、TreeSet
 *
 * HashSet:
 *      排序规则:根据Hash值进行排序
 *      **去重规则:先判断hashCode,再判断equals,这两个都相等,则判定为相同元素
 *              如果hashcode不同,则认定为不同元素
 *              如果hashcode相同,再判断equals,如果equals返回true,则相同元素,如果equals返回false,则不同元素
 *
 * TreeSet:二叉树可以减少比较次数
 *      **排序规则:  自然排序,比较器
 *      去重规则:  根据排序规则返回0则为重复元素
 *
 *      TreeSet中的元素必须是可以比较的
 *      要么在创建TreeSet的时候指定了比较器
 *      要么元素本身就可以比较
 *
 * Iterable 实现了这个借口的集合都可以使用foreach
 *
 *      我们可以使用Iterator对元素进行遍历
 *      如果你想在元素遍历过程中对元素进行删除操作,我们需要使用Iterator,不要for循环
 *
 */
①HashSet

底层是Hash表

public class Student {

    private String name;
    private Integer age;

    @Override
    public boolean equals(Object obj) {
        System.out.println("判断equals");
        Student st = (Student) obj;
        return st.getName().equals(this.name);
    }

    @Override
    public int hashCode() {
        System.out.println("判断hashCode");
        return this.age;
    }
    //getter and setter
}
②TreeSet

底层是二叉树,减少比较次数

如果元素放入TreeSet中,要么元素可以自然排序(实现Comparable接口,重写compareTo方法),要么在创建TreeSet的时候指定比较器(实现Comparator接口,重写compare方法)

public class Teacher implements Comparable<Teacher>{

    private String name;

    private Integer age;

    /**
     * 如果当前对象小于比较的对象,则返回负整数
     * 如果当前对象大于比较的对象,则返回正整数
     * 如果当前对象等于比较的对象,则返回0
     * 我们以年龄来排序
     * @param other
     * @return
     */
    @Override
    public int compareTo(Teacher other) {
        return this.age - other.getAge();
    }
    //getter and setter
}

如果使用比较器:

public class SetDemo {

    public static void main(String[] args) {

//        Set<Student> students = new HashSet<>();
//        students.add(new Student("张三",20));
//        students.add(new Student("张三",20));
//
//        System.out.println(students);


//        TreeSet<Teacher> teachers = new TreeSet<>();
        teachers.add(new Teacher("吴老师",18));
        teachers.add(new Teacher("杜老师",19));

        System.out.println(teachers);


        TreeSet<Student> students = new TreeSet<>((s1, s2) -> s1.getAge()-s2.getAge());
        students.add(new Student("张三",20));
        students.add(new Student("李四",20));

        System.out.println(students);

    }

}

2、Map

key,value形式存放

/**
 * Map:
 *
 *      key,value形式存储的
 *      常用的实现类:HashMap
 *      但是HashMap是线程不安全的,所以可以使用Hashtable代替
 *      但是Hashtable使用的是sychronized将整个map加锁,效率低
 *      可以选择使用ConcurrentHashMap,使用的是分段锁的形式,在保证线程安全的前提下,又可以调高操作的效率
 */
(1)HashMap

jdk1.7之前,只有链表结构,没有二叉树

高并发情况下会导致HashMap链表成环,造成死循环,数据无法插入

https://wwwblogs/lonelyJay/p/9726187.html

①底层原理


put方法

get方法:

先根据key运算hash,然后根据hash和数组的长度运算某个数组的索引

去Node<>[]数组中获取指定索引的First Node,

如果该节点的First Node是一个TreeNode,那么根据二叉树的规则寻找对应的key-value的Node

如果该节点的First Node不是TreeNode,则根据链表以此向后寻找,直到直到对应的key-value,返回value

属性:

// 默认的初始容量是16
static final int DEFAULT_INITIAL_CAPACITY = 1 << 4; //16

// 当桶(bucket)上的结点数大于这个值时会转成红黑树
static final int TREEIFY_THRESHOLD = 8; //(jdk1.8以后才有)
// 当桶(bucket)上的结点数小于这个值时树转链表
static final int UNTREEIFY_THRESHOLD = 6;

// 存储元素的数组,总是2的幂次倍
transient Node<k,v>[] table; 

transient关键字:

序列化的时候忽略这个成员变量【如果不理解作为扩展点】

②遍历方式
* Map的遍历方式有两种:
*      (1)调用keySet获取所有的key,然后遍历所有的key再获取value
*      (2)调用entrySet方法,获取key和value组成的Entry集合,遍历每一个Entry,调用getkey和getvalue获取entry中的key和value
*      建议使用第二种方式,效率高
public class MapDemo {

    public static void main(String[] args) {

        Map<String,String> map = new HashMap<>();
        map.put("java","张三");
        map.put("vue","李四");


        //两种方式 - keySet()方法获取key组成的set集合
        //先取key,再取value,效率低
        for (String key : map.keySet()) {
            String value = map.get(key);
            System.out.println(key+":"+value);
        }

        System.out.println("=======================================");


        //第二种遍历方式,entryset,获取一组key,value的Entry的Set集合
        //建议使用这种方式,一次性把key和value取出,效率高
        for (Map.Entry<String, String> entry : map.entrySet()) {
            String key = entry.getKey();
            String value = entry.getValue();
            System.out.println(key+":"+value);
        }


    }

}
(2)Hashtable

(3)ConcurrentHashMap

二、多线程

1、线程创建方式

(1)继承Thread类
public class ThreadDemo {

    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        myThread.start();//start方法启动线程

        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if(i==10){
                myThread.interrupt();
            }

            System.out.println("main:"+i);
        }


    }

}

class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            //在业务代码中通过中断状态判断是否结束线程
            if(Thread.currentThread().isInterrupted()){
                break;
            }
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MyThread:"+i);
        }
    }
}
(2)实现Runnble接口
public class RunnableDemo {

    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();

        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main:"+i);
        }


    }

}

class MyRunnable implements Runnable{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MyRunnable:"+i);


        }
    }
}

(3)实现Callable接口,获取线程的返回值
public class CallableDemo {

    public static void main(String[] args) {

        MyCallable myCallable = new MyCallable();
        FutureTask<Integer> futureTask = new FutureTask<>(myCallable);//实现了Runnable接口
        Thread thread = new Thread(futureTask);
        thread.start();


        for (int i=0; i < 20; i++) {
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main:"+i);
        }

        try {
            //主线程中获取其他线程的返回值,那么使用callable
            Integer result = futureTask.get(); //callable接口重写call方法的返回值
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }


    }

}

/**
 * 线程第三种实现方式 - callable接口,有返回值
 * 返回值通过FutrueTask进行封装
 */
class MyCallable implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        int i = 0;
        for (; i < 20; i++) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MyCallable:"+i);
        }
        return i;
    }

}
(4)选择

能使用实现尽量不选择继承

如果获取线程返回值,则使用Callable

2、线程生命周期

(1)生命周期

新建:刚刚new出来线程对象

就绪:调用start方法之后进入就绪状态,等待获取cpu资源执行

运行:获取cpu执行

死亡:run结束或者异常抛出

阻塞:sleep、wait、synchronized、io阻塞

(2)wait和sleep区别

sleep是Thread的方法,可以设置睡眠时间,时间到达后自动唤醒,进入就绪状态,sleep不释放锁资源

wait是Object的方法,需要等待notify唤醒,wait会释放锁资源

(3)线程中断interrupt

线程中断只是一个状态,不会真正中断线程,我们通常可以在线程业务代码中判断这个状态做出相关操作

public class ThreadDemo {

    public static void main(String[] args) {

        MyThread myThread = new MyThread();
        myThread.start();//start方法启动线程

        for (int i = 0; i < 20; i++) {
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            if(i==10){
                myThread.interrupt();
            }

            System.out.println("main:"+i);
        }


    }

}

class MyThread extends Thread{

    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            //在业务代码中通过中断状态判断是否结束线程
            if(Thread.currentThread().isInterrupted()){
                break;
            }
            try {
                Thread.sleep(i);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("MyThread:"+i);
        }
    }
}

3、线程通信

wait,notify,notifyAll

容器(锁的对象)

public class Conainter {

    private int num = 0;

    /**
     * 生产
     */
    public synchronized void product(){

        num++;
        System.out.println("生产了一个包子,当前"+num+"个包子");
        try {
            if(num>=5){
                //盘子放满了
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //唤醒其他线程去消费
        notify();


    }

    /**
     * 消费
     */
    public synchronized void consume(){

        num--;
        System.out.println("消费了一个包子,当前"+num+"个包子");
        try {
            if(num<=0){
                //盘子空了
                wait();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        //唤醒其他线程去生产
        notify();

    }



}

生产者(一个线程对象)

/**
 * 生产者
 */
public class Producter implements Runnable {

    private Conainter conainter;

    public Producter(Conainter conainter){
        this.conainter = conainter;
    }

    @Override
    public void run() {
        try {
            //死循环
            for(;;){
                Thread.sleep(new Random().nextInt(1000));
                conainter.product();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

}

消费者(一个线程对象)

/**
 * 消费者
 */
public class Consumer implements Runnable {

    private Conainter conainter;

    public Consumer(Conainter conainter){
        this.conainter = conainter;
    }

    @Override
    public void run() {
        try {
            //死循环
            for(;;){
                Thread.sleep(new Random().nextInt(1000));
                conainter.consume();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

测试类

public class Demo {

    public static void main(String[] args) {
        Conainter conainter = new Conainter();
        Thread product = new Thread(new Producter(conainter));
        Thread consumer = new Thread(new Consumer(conainter));

        product.start();
        consumer.start();


    }

}

4、线程同步

(1)synchronized关键字

原理:jvm的monitor监视器锁

用法:

修饰代码块(锁为this)

修饰方法(锁为this)

修饰静态方法(锁为类型的class字节码)

作用:

l 确保线程互斥的访问同步代码

l 保证共享变量的修改能够及时可见 可见性

l 有效解决重排序问题。(指令重排序)

(2)Lock

是一个接口,有实现类

lock():获取锁,如果锁被暂用则一直等待

unlock():释放锁,在finally中释放锁,防止死锁

tryLock(): 注意返回类型是boolean,如果获取锁的时候锁被占用就返回false,否则返回true

tryLock(long time, TimeUnit unit):比起tryLock()就是给了一个时间期限,保证等待参数时间

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YXOazjKV-1587210763412)(images/集合&线程并发库-笔记/wps2.jpg)]

Lock底层其实是CAS乐观锁的体现

public class LockDemo {

    public static void main(String[] args) {

        MyRunable myRunable = new MyRunable();

        Thread t1 = new Thread(myRunable,"线程1");
        Thread t2 = new Thread(myRunable,"线程2");

        t1.start();
        t2.start();

    }

}


class MyRunable implements Runnable{

    //同一把锁,Lock默认是非公平锁,可以通过构造方法创建一个公平锁
    Lock lock = new ReentrantLock(true);


    @Override
    public void run() {
        test1();
    }

    private void test(){

        try{
            //使用lock实现同步锁
            lock.lock();
            for (int i = 0; i < 20; i++) {
                try {
                    Thread.sleep(i);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+":"+i);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //一定在finally中释放锁资源
            lock.unlock();
        }
    }

    private void test1(){
        try{
            if(lock.tryLock()){
                for (int i = 0; i < 20; i++) {
                    try {
                        Thread.sleep(i);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }else{
                //获取不到锁,不会一直等待,做其他使用
                System.out.println(Thread.currentThread().getName()+":我不等了,我走了...................");
            }

        }catch (Exception e){
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
}

5、线程安全

线程安全的实现手段有两种方式:线程同步和线程本地存储

(1)线程同步

使用锁(synchronized,Lock)

(2)线程本地存储(Thread Local Storage) - ThreadLocal

public class ThreadLocalTest {



}

class Container{

    /**
     * 同一个成员变量要在多个线程中使用,并且相互不干涉,使用ThreadLocal,
     * 如果多个线程必须指向同一个副本,使用线程同步(锁)
     */
    private ThreadLocal<Integer> num = new ThreadLocal<>();

    public void add(){
        //get()获取当前线程的副本,set方法设置当前线程的副本值
        num.set(num.get()+1);
    }

}

6、锁

l 可重入锁:

指的是同一线程外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响,执行对象中所有同步方法不用再次获得锁。避免了频繁的持有释放操作,这样既提升了效率,又避免了死锁。

l 自旋锁:

所谓“自旋”,就是让线程去执行一个无意义的循环,循环结束后再去重新竞争锁,如果竞争不到继续循环,循环过程中线程会一直处于running状态,但是基于JVM的线程调度,会让出时间片,所以其他线程依旧有申请锁和释放锁的机会。自旋锁省去了阻塞锁的时间空间(队列的维护等)开销,但是长时间自旋就变成了“忙式等待”,忙式等待显然还不如阻塞锁。所以自旋的次数一般控制在一个范围内,例如10,100等,在超出这个范围后,自旋锁会升级为阻塞锁。

l 公平锁:

按等待获取锁的线程的等待时间进行获取,等待时间长的具有优先获取锁权利

l 读写锁:

对资源读取和写入的时候拆分为2部分处理,读的时候可以多线程一起读,写的时候必须同步地写。

l 独占锁:

是一种悲观锁,synchronized就是一种独占锁,会导致其它所有需要锁的线程挂起,等待持有锁的线程释放锁。

l 乐观锁:(CAS)

每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。

本文标签: 线程区别amp