1.Java 集合说明
顶级接口
Collection
Collection 顶级接口下面有list和set两个父级接口
list和set的区别
1.1 有序性
list 是有序的,保证按插入顺序排序
set 是无序的,存入和取出的顺序不一致
唯一性
list不唯一可以出现重复元素
set元素唯一不允许重复
获取元素方式
list 可以通过索引直接操作元素
set 不能根据索引获取元素
1.2 list相关实现类比较
ArrayList:底层是数组,查询快,增删慢,线程不安全,效率高,可以存储重复元素
LinkedList: 底层数据结构是链表,查询慢,增删快,线程不安全,效率高,可以存储重复元素
Vector: 底层是数组,查询快,增删慢,线程安全,效率低,可以存储重复元素
1.3 set相关实现类比较
hashSet 底层数据结构采用哈希表实现,元素无序且唯一,线程不安全,效率高,可以存储null 元素,
元素的唯一性是靠存储元素类型是否重写hashCode() 和 equals() 方法保证的
LinkedHashSet 底层数据结构采用链表和哈希表共同创建,链表保证元素的顺序与存储顺序一致,
哈希表保证元素的唯一性。线程不安全,效率高。
TreeSet底层数据结构是二叉树实现, 元素唯一且已经排好序 不允许放入 null
1.4 map详解
map用于保存具有映射关系的数据,map里面保存着两组数据:key和value,它们都可以使用任何引用类型的数据
,但key不可以重复。所以通过指定的key就可以取出对应的value。
**hashMap 和 hashtable 比较**
线程安全方面:
hashtable 是同步的,这个类中的一些方法加入了synchronized 关键字,保证了hashtable中的对象是
线程安全的
hashMap 是异步的,因此hashMap中的对象并不是线程安全的
空值方面:
hashMap 可以让你将空值作为一个表的条目的key或value,但是hashtable是不能放入空值的
hashMap 最多只有一个key值为null,但可以有无数个value值为null
性能方面:
hashMap的性能好,hashtable性能差
用作key的对象必须实现hashCode和equals方法
不能保证其中的键值对的顺序
尽量不要使用可变对象作为它们的key值
TreeMap 是一个有序的key-value集合,它通过红黑树实现
TreeMap 是一个key-value集合
TreeMap 实现了NavigableMap接口,意味着它支持一系列的导航方法,比如返回有序的key集合
TreeMap 实现java.io.Serializable接口,意味着它支持序列化
TreeMap基于红黑树实现,该映射根据其键的自然顺序进行排序
或根据创建映射时提供的Comparator 进行排序,取决与使用的构造方法
TreeMap 时非同步的 线程不安全,效率低。
1.5 与synchronized 自动加解锁 关键字比较的另一个锁机制 ReentrantLock 手动加解锁
synchronized与ReentrantLock区别点击此处
1.synchronized独占锁自动加锁和解锁,易操作,不灵活。
ReentrantLock 独占锁,加锁和解锁需手动进行,且次数需一样,否则其他线程无法获取锁
2.synchronized 可重入锁,因为加锁和解锁自动进行,不必担心最后是否释放锁
ReentrantLock 也可以重入,但加锁和解锁需手动进行,且次数一样,否则其他线程无法获取锁
3.synchronized不可响应中断,一个线程获取不到锁就会一直等着,ReentrantLock可以响应中断。
4.ReentrantLock 可以实现公平锁机制(即访问中谁等待的时间最长谁的请求优先处理)
5.解决死锁问题通过tryLock设置超时等待时间
1.6 CopyOnWrite 写时复制容器
(即:当我们往一个容器里面添加元素的时候,不直接往当前容器添加,而是先将当前容器进行Copy,复制出一个新的容器
,然后往新容器添加元素,添加完元素后,在将原容器的索引指向新的容器。(这样可以对CopyOnWrite容器进行并发的读
而不需要加锁,因为当前容器不会添加任何元素。所以CopyOnWrite 容器也是一种读写分离的思想),CopyOnWrite容器存在两个
问题,即内存占用问题和数据一致问题)
针对内存占用问题可以考虑 ConcurrentHashMap
1.7 java 多线程
并发和并行
并行: 指两个或多个事件在同一时刻发生(同时发生)
并行性使多个程序同一时刻可在不同cpu上同时执行。
并发: 指两个或多个事件在同一时间段内发生(不是真正的同时只是看上去同时) 在同一cpu上运行多个程序。
1.8 线程和进程
进程: 进程是正在运行的程序实例。
进程是线程的容器,即一个进程中可以开启多个线程。
线程: 线程是进程内部的一个独立执行单元,一个进程可以同时并发运行多个线程;
1.9 线程创建的四种方式
1.继承Thread 类
import java.util.Date;
/**
* 继承Thread写法
*/
public class MyThread extends Thread{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println("myThread执行了:"+new Date().getTime());
}
}
}
class Test{
public static void main(String[] args) {
//1.创建自定义线程实例
MyThread myThread = new MyThread();
//2.启动线程
myThread.start();
//3.在main主线程打印信息
try {
Thread.sleep(1000);
for (int i=0;i<10;i++){
System.out.println("主线程执行了:"+ new Date().getTime());
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
2.实现Runnable 接口
/**
* 实现Runnable接口
*/
public class MyRunnable implements Runnable {
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(
Thread.currentThread().getName()+"执行时间:"+new Date().getTime()+"执行次数!");
}
}
}
class TestRunnableDemo{
public static void main(String[] args) {
//1.主线程打印信息
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行时间");
}
//2.通过 thread 类执行Runnable 类
Thread thread = new Thread(new MyRunnable(),"myRunnable");
thread.start();
}
}
3.实现 Callable 接口
import java.util.Date;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
for (int i=0;i<10;i++){
System.out.println("线程执行"+Thread.currentThread().getName());
}
return "MyCallable执行完毕";
}
}
class MyCallableTest{
public static void main(String[] args) {
//1.实现Callable接口
//2.创建FutureTask实例,创建MyCallable实例
FutureTask<String> task = new FutureTask<>(new MyCallable());
//3.创建Thread实例,执行FutureTask
Thread thread = new Thread(task,"MyCallable");
thread.start();
//4.主线程打印信息
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"执行时间:"+new Date().getTime());
}
//获取MyCallable执行结果
try {
String result= task.get();
System.out.println("结果是:"+result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
4.线程池
public class MyRunnableTwo implements Runnable {
@Override
public void run() {
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class ExecutorServiceDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
MyRunnableTwo runnableTwo = new MyRunnableTwo();
executorService.execute(runnableTwo);
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"主线程"+i);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2.0 线程池:
线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好的提高性能
,线程池在系统启动时创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行
这个任务,执行结束以后,改线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务
使用线程池的好处:
多线程运行时间,系统不断的启动和关闭新线程,成本非常高,会过渡的消耗系统资源,以及
过渡切换线程的危险,从而可能导致系统资源的崩溃
2.1 线程通讯方式:
休眠唤醒方式:
Object 的 wait,notify,notifyAll
Condition 的 await,signal ,signalAll
CountDownLatch : 用于某个线程A等待若干个其他线程执行完之后,它才执行
CyclicBarrier: 一组线程等待某个状态之后再全部同时执行
Semaphore: 用于控制对某组资源的访问权限
2.2 sleep 和 wait 区别
(1.) sleep 是线程中的方法,但是wait是Object中的方法
(2.)sleep 方法不会释放锁 ,但是wait会释放,而且会加入到等待队列中
(3.)sleep 方法不依赖于同步器 synchronized,但是wait需要依赖synchronized关键字
(4.) sleep 不需要被唤醒(休眠之后退出阻塞),但wait 需要
2.3 wait 和 notify
wait 和 notify 都是Object 中的方法
wait 和 notify 执行前线程都必须获得锁对象
wait 的作用 是使当前线程等待
notify 作用是通知其他等待当前线程的对象锁的线程
2.4 java 内存模型
jvm 内存共分为 虚拟机栈,堆,方法区,程序计数器,本地方法栈五个部分
2.4.1程序计数器
每个线程对应有一个程序计数器。
各线程的程序计数器是私有的,互不影响,是线程安全的
程序计数器记录线程正在执行的内存地址,以便线程被中断恢复执行时再次按照中断时的指令继续执行
2.4.2 java 栈
每个线程会对应一个Java 栈
每个Java 栈由若干个栈帧组成
每个方法对应一个栈帧
栈帧在方法运行时,创建并入栈,方法执行完毕,该栈帧弹出栈帧中的元素作为该方法的返回值,该栈帧被清除
栈顶的栈帧叫活动栈,表示当前执行的方法,才可以被cpu执行
线程请求的栈深度大于虚拟机所允许的深度,将抛出 StackOverflowError异常
栈扩展时无法申请到足够的内存,就会抛出OutOfMemoryError 异常
2.4.3 方法区
方法区是java堆的永久区
方法区存放了要加载的类的信息(名称,修饰符等)类的静态常量,类中定义为final类型的常量
类中的Field信息,类中的方法信息
方法区是被Java线程共享的
方法区要是使用的内存超出其允许的大小时,会抛出OutOfMemoryError:PremGen space 的错误
2.4.4常量池
常量池是方法区的一部分。
常量池中存储两类数据:字面量和引用量
字面量: 字符串,final 变量等
引用量:类/接口 ,方法和字段的名称和描述符
常量池在编译期间就被确定,并保存在已编译的.class 文件中
2.4.5 本地方法栈
本地栈和Java栈所发挥的作用非常相似,区别不过是Java栈为jvm执行Java方法服务
,而本地方法栈为jvm执行的native方法服务
本地方法栈也会抛出StackOverflowError 和 OutOfMemoryError异常
2.6 线程安全问题
原因: 多个线程在操作共享数据
操作共享数据的代码有多条
多个线程对共享数据都有写操作
线程不安全问题解决:线程同步
要解决线程不安全问题, 只要在某个线程修改共享资源的时候,其他线程不能修改该资源,等待
修改完毕同步之后,才能去抢夺cpu资源,完成对应的操作,保证了数据的同步性。
数据同步的七重机制
同步代码块(synchronized)
/**
* 同步代码块
*/
public class TicketDemo implements Runnable{
private int ticketNum = 80 ; //电影票
private Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
if (ticketNum>0){//判断是否还有票
//有票,让线程睡眠100毫秒 模拟售票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印票
String name = Thread.currentThread().getName();
System.out.println("线程"+name+"销售电影票"+ticketNum--);
}
}
}
}
public static void main(String[] args) {
//1.创建电影票对象
TicketDemo ticketDemo = new TicketDemo();
//2.创建Thread对象,售卖电影票
Thread thread = new Thread(ticketDemo,"窗口1");
thread.start();
Thread thread2 = new Thread(ticketDemo,"窗口2");
thread2.start();
Thread thread3 = new Thread(ticketDemo,"窗口3");
thread3.start();
}
}
同步方法(synchronized)
/**
* 同步方法
*/
public class TicketDemo2 implements Runnable{
private int ticketNum = 80 ; //电影票
@Override
public void run() {
while (true){
ticket();
}
}
public synchronized void ticket(){
if (ticketNum>0){//判断是否还有票
//有票,让线程睡眠100毫秒 模拟售票
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印票
String name = Thread.currentThread().getName();
System.out.println("线程"+name+"销售电影票"+ticketNum--);
}
}
public static void main(String[] args) {
//1.创建电影票对象
TicketDemo ticketDemo = new TicketDemo();
//2.创建Thread对象,售卖电影票
Thread thread = new Thread(ticketDemo,"窗口1");
thread.start();
Thread thread2 = new Thread(ticketDemo,"窗口2");
thread2.start();
Thread thread3 = new Thread(ticketDemo,"窗口3");
thread3.start();
}
}
同步锁(ReentrantLock)
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class TicketDemo3 implements Runnable{
private int ticketNum = 80 ; //电影票
private Lock lock = new ReentrantLock();
@Override
public void run() {
while (true){
lock.unlock();
try{
if (ticketNum>0){//判断是否还有票
//有票,让线程睡眠100毫秒 模拟售票
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//打印票
String name = Thread.currentThread().getName();
System.out.println("线程"+name+"销售电影票"+ticketNum--);
}
}finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
//1.创建电影票对象
TicketDemo ticketDemo = new TicketDemo();
//2.创建Thread对象,售卖电影票
Thread thread = new Thread(ticketDemo,"窗口1");
thread.start();
Thread thread2 = new Thread(ticketDemo,"窗口2");
thread2.start();
Thread thread3 = new Thread(ticketDemo,"窗口3");
thread3.start();
}
}
特殊域变量(volatile)
局部变量(ThreadLocal)
阻塞队列(LinkedBlockingQueue)
原子变量(Atomic*)