前言
一、多线程
1、并行与并发
- 并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
- 并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。并发往往在场景中有公用的资源,那么针对这个公用的资源往往产生瓶颈,我们会用TPS或者QPS来反应这个系统的处理能力。
2、线程状态
线程在Running的过程中可能会遇到阻塞(Blocked)情况:
- 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
- 调用wait(),使该线程处于等待池(wait blocked pool),直notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool),释放同步锁使线程回到可运行状态(Runnable);
- 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool
),同步锁被释放进入可运行状态(Runnable)。
3、基本线程类
基本线程类指的是Thread类,Runnable接口,Callable接口。
Thread 类实现了Runnable接口,启动一个线程的方法:
MyThread my = new MyThread();
my.start();
Thread类相关方法:
public static Thread.yield()
public static Thread.sleep()
public join()
public interrupte()
**关于中断:**它并不像stop方法那样会中断一个正在运行的线程。线程会不时地检测中断标识位,以判断线程是否应该被中断(中断标识值是否为true)。终端只会影响到wait状态、sleep状态和join状态。被打断的线程会抛出InterruptedException。 Thread.interrupted()检查当前线程是否发生中断,返回boolean synchronized在获锁的过程中是不能被中断的。
中断是一个状态!interrupt()方法只是将这个状态置为true而已。所以说正常运行的程序不去检测状态,就不会终止,而wait等阻塞方法会去检查并抛出异常。如果在正常运行的程序中添加while(!Thread.interrupted()) ,则同样可以在中断后离开代码体
Thread类最佳实践: 写的时候最好要设置线程名称 Thread.name,并设置线程组 ThreadGroup,目的是方便管理。在出现问题的时候,打印线程栈 (jstack -pid) 一眼就可以看出是哪个线程出的问题,这个线程是干什么的。
如何获取线程中的异常
不能用try,catch来获取线程中的异常
与Thread类似
future模式:并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态
ExecutorService e = Executors.newFixedThreadPool(3);
Future future = e.submit(new myCallable());
future.isDone()
future.get()
4、高级多线程控制类
4.1 ThreadLocal类
用处:保存线程的独立变量。对一个线程类(继承自Thread) 当使用ThreadLocal维护变量时,ThreadLocal为每个使用该变量的线程提供独立的变量副本,所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。常用于用户登录控制,如记录session信息。
实现:每个Thread都持有一个TreadLocalMap类型的变量(该类是一个轻量级的Map,功能与map一样,区别是桶里放的是entry而不是entry的链表。功能还是一个map。)以本身为key,以目标为value。 主要方法是get()和set(T a),set之后在map里维护一个threadLocal -> a,get时将a返回。ThreadLocal是一个特殊的容器。
ThreadLocal的内存泄露原因 由于Thread中包含变量ThreadLocalMap,因此ThreadLocalMap与Thread的生命周期是一样长,如果都没有手动删除对应key,都会导致内存泄漏。
但是使用弱引用可以多一层保障:弱引用ThreadLocal不会内存泄漏,对应的value在下一次ThreadLocalMap调用set(),get(),remove()的时候会被清除。
因此,ThreadLocal内存泄漏的根源是:由于ThreadLocalMap的生命周期跟Thread一样长,如果没有手动删除对应key就会导致内存泄漏,而不是因为弱引用。 ThreadLocal正确的使用方法 每次使用完ThreadLocal都调用它的remove()方法清除数据 将ThreadLocal变量定义成private static,这样就一直存在ThreadLocal的强引用,也就能保证任何时候都能通过ThreadLocal的弱引用访问到Entry的value值,进而清除掉 。
4.2 原子类
常用原子类:
AtomicBoolean、AtomicIndteger、AutomicLong 元老级的原子更新,方法几乎一模一样
DoubleAdder、LongAdder 对Double、Long的原子更新性能进行优化提升
DoubleAccumulator、LongAccumlator 支持自定义运算
原子更新数组类型
AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
原子的更新属性
原子的更新某个类里的字段时,需要使用原子更新字段类,AtomicStampedReference、AtomicReference、FiledUpdater
参阅:https://blog.csdn.net/sinat_36265222/article/details/86636949 https://blog.csdn.net/weixin_38003389/article/details/88569336
4.3 Lock类
使用ReentrantLock实现同步
class MyThread implements Runnable {
private int ticket = 10;
private Lock lock = new ReentrantLock();
public void run() {
try {
while (this.ticket > 0) {
lock.lock();
System.out.println(Thread.currentThread().getName() + "还有" + this.ticket-- + "张票");
lock.unlock();
try {
Thread.sleep(500);
} catch (Exception e) {
}
}
} finally {
}
}
}
测试:
public class ThreadTest {
public static void main(String[] args) throws InterruptedException {
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread, "黄牛A");
Thread thread1 = new Thread(myThread, "黄牛B");
Thread thread2 = new Thread(myThread, "黄牛C");
thread.start();
thread1.start();
thread2.start();
}
}
使用Condition实现等待/通知
5、容器类
5.1 BlockingQueue
阻塞队列。该类是java.util.concurrent包下的重要类,通过对Queue的学习可以得知,这个queue是单向队列,可以在队列头添加元素和在队尾删除或取出元素。类似于一个管道,特别适用于先进先出策略的一些应用场景。
常见的阻塞队列有:
ArrayListBlockingQueue LinkedListBlockingQueue DelayQueue SynchronousQueue
LinkedBlockingQueue : 1、读写锁分开,性能较 ArrayListBlockingQueue 只有一把锁控制读写要高一些。 2、无界队列,不会触发Reject异常,ArrayListBlockingQueue初始化时必须指定宽度。SynchronousQueue的容量只有1。
ArrayListBlockingQueue 1、读写一把锁,在大并发的消费者和生产者场景中,性能较LinkedBlockingQueue差一些。 2、有界队列,会触发Reject异常,在增加队列和删除队列元素时,不会产生额外的数据(数组特性),GC影响比LinkedBlockingQueue更小
SynchronousQueue
1、可以理解为容量为1的阻塞队列,只有被消费了,才能接受生产者的消息。 2、当线程池的线程数已经达到了maxiumSize时,新增加时出发Reject异常 3、有公平策略和不公平策略,对应到不同进出算法 - FIFO,LIFO。
场景归类
无界的消息任务,读写频繁,使用LinkedBlockingQueue 有界的,对GC影响较小,读写不过于频繁的场景,使用ArrayListBlockingQueue 只有一个生产者和消费者,需要使用不同的公平策略的场景,使用 SynchronousQueue
生产者-消费者 简单示例: package container;
import java.util.Random; import java.util.concurrent.BlockingQueue;
public class Producer implements Runnable {
private final BlockingQueue<Integer> blockingQueue;
private Random random;
public Producer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
random = new Random();
}
public void run() {
while (true) {
int info = random.nextInt(100);
try {
boolean result = blockingQueue.offer(info);
System.out.println("生产:" + info + (result? " 成功":"失败!"));
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class Consumer implements Runnable{
private final BlockingQueue<Integer> blockingQueue;
public Consumer(BlockingQueue<Integer> blockingQueue) {
this.blockingQueue = blockingQueue;
}
public void run() {
while(true){
Integer info;
try {
info = blockingQueue.poll(100, TimeUnit.MILLISECONDS);
if(null == info) {
System.err.println("当前商品紧缺!");
}else {
System.out.println("消费:" + info);
}
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class QueueTest {
public static void main(String[] args) {
ArrayBlockingQueue blockingQueue = new ArrayBlockingQueue(100);
Consumer consumer = new Consumer(blockingQueue);
Producer producer = new Producer(blockingQueue);
Thread thread = new Thread(consumer);
Thread thread1 = new Thread(producer);
thread.start();
thread1.start();
}
}
TimeUnit 使用 主要作用
常用的颗粒度
TimeUnit.DAYS
TimeUnit.HOURS
TimeUnit.MINUTES
TimeUnit.SECONDS
TimeUnit.MILLISECONDS
1、时间颗粒度转换
public long toMillis(long d)
public long toSeconds(long d)
public long toMinutes(long d)
public long toHours(long d)
public long toDays(long d)
5.2 ConcurrentHashMap
在JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。 JDK8中ConcurrentHashMap参考了JDK8 HashMap的实现,采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作。并发控制使?synchronized 和 CAS 来操作。
ConcurrentHashMap不论1.7还是1.8,他的执行效率都比HashTable要高的多,主要原因还是因为Hash Table使用了一种全表加锁的方式。
6、管理类
6.1 ThreadPoolExecutor
ExecutorService e = Executors.newCachedThreadPool();
ExecutorService e = Executors.newSingleThreadExecutor();
ExecutorService e = Executors.newFixedThreadPool(3);
e.execute(new MyRunnableImpl());
二、反射
1、反射机制的功能
Java反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类。 在运行时构造任意一个类的对象。 在运行时判断任意一个类所具有的成员变量和方法。 在运行时调用任意一个对象的方法。 生成动态代理。
2、 实现反射机制的类
Java中主要由以下的类来实现Java反射机制(这些类都位于java.lang.reflect包中):
Class类:代表一个类。 Field类:代表类的成员变量(成员变量也称为类的属性)。 有三种方式可以获得类的Class对象实例:
1、Object的getClass()方法
Class clazz = new HelloWorld().getClass();
2、类型的.class
Class clazz = HelloWorld.class;
3、Class.forName(“类的完全限定名”)
try {
Class clazz = Class.forName("com.learn.demo.reflection.HelloWorld");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Method类:代表类的方法。
Method[] methods0 = clazz.getMethods();
Constructor类:代表类的构造方法。
Constructor constructor0 = clazz.getConstructor(int.class);
Object obj0 = constructor0.newInstance(1);
Constructor constructor1 = clazz.getConstructor(int.class, String.class);
Object obj1 = constructor1.newInstance(2, "我出生了!");
获得类的成员变量
Field [] fields = clazz.getDeclaredFields();
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
int rows = 3;
int cols = 2;
Integer[] array = (Integer[]) Array.newInstance(Integer.class,rows);
Array.set(array,0,110);
Double[][] arraymn = (Double[][]) Array.newInstance(Double.class,rows,cols);
Array.set(arraymn[0],0,1D);
三、IO流
具体参阅:https://www.cnblogs.com/wugongzi/p/12092326.html
四、网络编程
1、网络协议
TCP三次握手过程:
TCP四次挥手过程: TCP与UDP的区别
- TCP基于连接,UDP是无连接的;
- 对系统资源的要求,TCP较多,UDP较少;
- UDP程序结构较简单;
- TCP是流模式,而UDP是数据报模式;
- TCP保证数据正确性,而UDP可能丢包;TCP保证数据顺序,而UDP不保证;
2、Socket整体流程
总结
此处记录自己在重温java知识点中的部分重点纪要,对java中的高级基础知识点作一个概括性梳理,铸就更扎实的基础。
|