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)
每次不加锁,假设没有冲突去完成某项操作,如果因为冲突失败就重试,直到成功为止。
版权声明:本文标题:线程并发库&集合区别 内容由热心网友自发贡献,该文观点仅代表作者本人, 转载请联系作者并注明出处:https://m.elefans.com/dianzi/1727171824a1100335.html, 本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌抄袭侵权/违法违规的内容,一经查实,本站将立刻删除。
发表评论