赞
踩
线程在Running的过程中可能会遇到阻塞(Blocked)情况:
基本线程类指的是Thread类,Runnable接口,Callable接口。
Thread 类实现了Runnable接口,启动一个线程的方法:
MyThread my = new MyThread();
my.start();
Thread类相关方法:
//当前线程可转让cpu控制权,让别的就绪状态线程运行(切换)
public static Thread.yield()
//暂停一段时间
public static Thread.sleep()
//在一个线程中调用other.join(),将等待other执行完后才继续本线程。
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);
//submit方法有多重参数版本,及支持callable也能够支持runnable接口类型.
Future future = e.submit(new myCallable());
future.isDone() //return true,false 无阻塞
future.get() // return 返回值,阻塞直到该线程运行结束
用处:保存线程的独立变量。对一个线程类(继承自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值,进而清除掉 。
常用原子类:
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
使用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) { // TODO: handle exception } } } 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实现等待/通知
阻塞队列。该类是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;
/** * TODO: * * @Version 1.0 * @Author HJL * @Date 2021/12/19 23:11 */ //生产者 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) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
/** * TODO: * * @Version 1.0 * @Author HJL * @Date 2021/12/19 23:11 */ //消费者 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) { // TODO Auto-generated catch block 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) //转化天
在JDK1.7中ConcurrentHashMap采用了数组+Segment+分段锁的方式实现。
JDK8中ConcurrentHashMap参考了JDK8 HashMap的实现,采用了数组+链表+红黑树的实现方式来设计,内部大量采用CAS操作。并发控制使⽤synchronized 和 CAS 来操作。
ConcurrentHashMap不论1.7还是1.8,他的执行效率都比HashTable要高的多,主要原因还是因为Hash Table使用了一种全表加锁的方式。
ExecutorService e = Executors.newCachedThreadPool();
ExecutorService e = Executors.newSingleThreadExecutor();
ExecutorService e = Executors.newFixedThreadPool(3);
// 第一种是可变大小线程池,按照任务数来分配线程,
// 第二种是单线程池,相当于FixedThreadPool(1)
// 第三种是固定大小线程池。
// 然后运行
e.execute(new MyRunnableImpl());
Java反射机制主要提供了以下功能:
在运行时判断任意一个对象所属的类。
在运行时构造任意一个类的对象。
在运行时判断任意一个类所具有的成员变量和方法。
在运行时调用任意一个对象的方法。
生成动态代理。
Java中主要由以下的类来实现Java反射机制(这些类都位于java.lang.reflect包中):
Class类:代表一个类。 Field类:代表类的成员变量(成员变量也称为类的属性)。
有三种方式可以获得类的Class对象实例:
1、Object的getClass()方法
// 创建对象,通过对象的getClass()方法获取Class对象实例
Class clazz = new HelloWorld().getClass();
2、类型的.class
// 通过类型的class获得Class实例
Class clazz = HelloWorld.class;
3、Class.forName(“类的完全限定名”)
// 通过Class.forName("类的完全限定名")获取Class对象实例
try {
Class clazz = Class.forName("com.learn.demo.reflection.HelloWorld");
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
Method类:代表类的方法。
// 获得类的public成员方法
Method[] methods0 = clazz.getMethods();
Constructor类:代表类的构造方法。
// 构造方法-一个int类型参数的构造方法
Constructor constructor0 = clazz.getConstructor(int.class);
Object obj0 = constructor0.newInstance(1);
// 构造方法-一个int类型参数和一个String类型参数的构造方法
Constructor constructor1 = clazz.getConstructor(int.class, String.class);
Object obj1 = constructor1.newInstance(2, "我出生了!");
获得类的成员变量
// 通过getDeclaredFields方法获取private成员变量
Field [] fields = clazz.getDeclaredFields();
Array类:提供了动态创建数组,以及访问数组的元素的静态方法。
//利用Array.newInstance创建一维数组
int rows = 3;
int cols = 2;
Integer[] array = (Integer[]) Array.newInstance(Integer.class,rows);
Array.set(array,0,110);
//利用Array.newInstance创建多维数组
Double[][] arraymn = (Double[][]) Array.newInstance(Double.class,rows,cols);
Array.set(arraymn[0],0,1D);
具体参阅:https://www.cnblogs.com/wugongzi/p/12092326.html
TCP三次握手过程:
TCP四次挥手过程:
TCP与UDP的区别
此处记录自己在重温java知识点中的部分重点纪要,对java中的高级基础知识点作一个概括性梳理,铸就更扎实的基础。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。