Java基础记录

------------------------------------------------------聚沙成塔,每天进步一点------------------------------------------------------

1、多线程

1.1 多线程创建的方式?

1.继承Thread,重写run();

2.实现Runnable,重run();

3.实现Callable,重写call();
Callable接口弥补了Runnable接口不能抛异常和带返回值的缺陷。
4.使用Executors线程池
使用线程池的优点:
1、频繁的创建、销毁对象很消耗性能,导致占用过多的资源,可能会导致我们的服务由于资源不足而宕机,线程池可以提升线程的使用率,减少对象的创建、销毁;
2、线程池可以控制线程数,有效的提升服务器的使用资源;

1.2 线程池的创建方式?

常用的线程池的四种使用方式
1、newCachedThreadPool
如果线程池中的线程数量过大,它可以有效的回收多余的线程,并且复用现有的线程,如果线程数不足,那么它可以创建新的线程,提高了线程的利用率。缺点就是需要多少个线程同时处理无法控制,如果同时需要n个线程处理,他会直接创建n个进行处理,宕机也是分分钟的事;

Executors.newCachedThreadPool().execute(() -> System.out.println(Thread.currentThread().getName()));

2、newFixedThreadPool
可以指定线程池中的线程数,如果线程都处于占用状态任务则会等待,直到有线程释放再执行。

 ExecutorService executor = Executors.newFixedThreadPool(2);
        for (int i = 0; i < 10; i++) {
            final int index = i;
            executor.execute(() -> {
                try {
                    Thread.sleep(2000L);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "  " + index);
            });
        }
        executor.shutdown();
    }

优点很明显,可以根据服务器的配置控制最大线程,不会造成资源被吃尽的情况。
3、newScheduledThreadPool
支持定时及周期性的任务执行的线程池,可以延迟任务的执行时间,也可以设置一个周期性的时间让任务重复执行。 该线程池中有以下两种延迟的方法。
scheduleAtFixedRate

 ScheduledExecutorService executor = Executors.newScheduledThreadPool(5);
        executor.scheduleAtFixedRate(() -> {
            long start = System.currentTimeMillis();
            System.out.println("scheduleAtFixedRate执行开始:" + DateFormat.getTimeInstance().format(new Date()));
            try {
                Thread.sleep(3000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("scheduleAtFixedRate执行花费:" + (end - start) / 1000 + "m");
            System.out.println("scheduleAtFixedRate执行完成:" + DateFormat.getTimeInstance().format(new Date()));
            System.out.println("======================================");
        }, 1, 5, TimeUnit.SECONDS);

值得注意的是如果间隔时间大于任务的执行时间,任务不受执行时间的影响。如果间隔时间小于任务的执行时间,那么任务执行结束之后,会立马执行,间隔时间就会被打乱失去意义。
scheduleWithFixedDelay

  ScheduledExecutorService executor = Executors.newScheduledThreadPool(2);

        executor.scheduleWithFixedDelay(() -> {
            long start = System.currentTimeMillis();
            System.out.println("scheduleWithFixedDelay执行开始:" + DateFormat.getTimeInstance().format(new Date()));
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            long end = System.currentTimeMillis();
            System.out.println("scheduleWithFixedDelay执行花费:" + (end - start) / 1000 + "m");
            System.out.println("scheduleWithFixedDelay执行完成:"+ DateFormat.getTimeInstance().format(new Date()));
            System.out.println("======================================");
        }, 1, 2, TimeUnit.SECONDS);
    }

scheduleWithFixedDelay的间隔时间不会受任务执行时间长短的影响。
4、newSingleThreadExecutor
顾名思义就似乎创建一个唯一线程的线程池,自始至终都是这一个线程在跑。

 ExecutorService executor = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 5; i++) {
            final int index = i;
            executor.execute(() -> {
                try {
                    Thread.sleep(2 * 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "   " + index);
            });
        }
        executor.shutdown();
    }

2、序列化和反序列化

2.1 为什么要序列化?

序列化和反序列化就是对象转换为字节和字节转换为对象的过程,主要是为了实现跨进程跨网络的数据传输。

2.2 序列化方式

1. Serializable接口

最常见就是实现Serializable序列化标记接口,说它是标识是因为它的接口源码中其实什么都没有定义,只是为了标识而使用,一个类只有实现了这个接口,才能对它进行序列化。

2.Externalizable接口

Externalizable这个接口其实是继承了Serializable,它更加灵活一点,它里面定义了writeExternal和readExternal两个方法分别用于序列化和反序列化使用。通过这两个方法,我们可以自己决定需要序列化那些数据。如果对象中涉及到很少的属性需要序列化,大多数属性无需序列化,这种情况使用Externalizable接口是比较灵活的。
3.第三方序列化

也可以使用第三方的序列化进行操作,比如:hessian,它最大的特点就是跨语言,hessian提供了一整套的byte[]的写入规范。这样其他语言在实现hessian序列化的时候就可以参照这套的标准规范,从而达到不同语言之间的兼容效果,因此hessian的序列化都是围绕这byte数组来的。

2.3 serialVersionUID

serialVersionUID用于验证版本的一致性,在反序列化时,jvm会把传来的字节流中的serialVersionUID和本地相应实体类的serialVersionUID进行比较,如果相同就认为一致,可以进行反序列化,否则出现InvalidCastException异常。

2.4 静态变量的序列化

static静态变量不是对象状态的一部分,因此它不参与序列化。那么反序列化之后。我们可以这么理解,静态变量的话,不属于对象的特有的,谁都可以进行改变,所以序列化了之后,在反序列返回来可能就不是那个值了。因此可以使用static final修饰,由于final定义的不可被改变,那么这时候这个属性就会被持久化了。

2.5 transient

transient关键字的作用就是让某些被修饰的成员属性变量不被序列化。

3、类加载

3.1 双亲委派机制

类的加载顺序:
当需要加载一个类时,如果没有自定义类加载器,默认会使用 AppClassLoader 进行加载,
AppClassLoader 先会查找一下该类是否已经被加载,若已经被加载则返回,若没有则调用它的父加载器 ExtClassLoader 进行加载,ExtClassLoader 也会查找一下该类是否已经被加载,若已经被加载则返回,若没有则向上调用它的父加载器 BootStrapClassLoader 进行加载,BootStrapClassLoader 会尝试加载该类,若加载不到则返回,接着 ExtClassLoader 也会尝试加载该类,若加载不到则返回,接着 AppClassLoader 也会尝试加载该类,假设该类被加载到(在项目目录下),则成功返回,否则抛出 ClassNotFoundException
当然以上过程也可以从自定义的类加载器开始,过程是一样的。这种先委派父加载器进行加载的机制就是双亲委派机制。

为什么不直接加载,而是往上委派双亲加载呢?
因为内存资源是宝贵的,如果一个类已经加载过一次了,也就没有必要再加载一份相同的拷贝在内存中了。为了保证一个类只被加载一次,加载类时就会从最顶层的父类加载器开始加载,只要加载过了,在后续需要用的时候就会返回已经加载过的 class 文件。

为了保证安全性和稳定性。Java 自身是有许多核心类的,这些类都通过顶层的父加载器进行加载,保证运行的时候用的是 Java 系统自己的类。如果没有双亲委派机制保证,用户自己也可以编写一些系统类并用自己编写的类加载器进行加载,那么就会导致Java 自身的系统类和用户编写的类混在一起,破坏了 Java 程序执行的安全性和稳定性。

3.2 类的创建过程

在这里插入图片描述
链接阶段要做的是将加载到JVM中的二进制字节流的类数据信息合并到JVM的运行时状态中。

1、加载
由类加载器负责根据一个类的全限定名来读取此类的二进制字节流到JVM内部,并存储在运行时内存区的方法区,然后将其转换为一个与目标类型对应的java.lang.Class对象实例
2、验证
格式验证:
验证是否符合class文件规范
语义验证:
检查一个被标记为final的类型是否包含子类;检查一个类中的final方法是否被子类进行重写;
确保父类和子类之间没有不兼容的一些方法声明(比如方法签名相同,但方法的返回值不同)
操作验证:
在操作数栈中的数据必须进行正确的操作,对常量池中的各种符号引用执行验证(通常在解析阶段执行,检查是否可以通过符号引用中描述的全限定名定位到指定类型上,以及类成员信息的访问修饰符是否允许访问等)
3、准备
为类中的所有静态变量分配内存空间,并为其设置一个初始值(由于还没有产生对象,实例变量不在此操作范围内)
被final修饰的static变量(常量),会直接赋值;
4、解析
将常量池中的符号引用转为直接引用(得到类或者字段、方法在内存中的指针或者偏移量,以便直接调用该方法),这个可以在初始化之后再执行。

解析需要静态绑定的内容。// 所有不会被重写的方法和域都会被静态绑定
5、初始化
⋙ 为静态变量赋值
⋙ 执行static代码块

注意:static代码块只有jvm能够调用

如果是多线程需要同时初始化一个类,仅仅只能允许其中一个线程对其执行初始化操作,其余线程必须等待,只有在活动线程执行完对类的初始化操作之后,才会通知正在等待的其他线程。

因为子类存在对父类的依赖,所以类的加载顺序是先加载父类后加载子类,初始化也一样。不过,父类初始化时,子类静态变量的值也有有的,是默认值。

最终,方法区会存储当前类信息,包括类的静态变量、类初始化代码(定义静态变量时的赋值语句 和 静态初始化代码块)、实例变量定义、实例初始化代码(定义实例变量时的赋值语句实例代码块和构造方法)和实例方法,还有父类的类信息引用。
6、卸载
严格意义上来说,这里已经不能算作是类的加载过程中的一步,可以说是类的生命周期的一部分。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值