一、static关键字的作用
1、static修饰成员变量
static修饰的变量属于类,在类初始化时通过类加载器加载到JVM来分配内存空间
2、static修饰成员方法
static修饰的方法属于类方法,不需要创建实例就可以直接调用。在static修饰的成员方法中不能使用this和super等关键字,不能调用非static方法,只能访问所属类的静态成员变量和静态方法
3、static修饰代码块
JVM在加载类时会执行static代码块,static代码块常用于初始化静态变量,static代码块只会在类加载时执行且只执行一次
4、static修饰内部类
static内部类可以不依赖外部类实例对象而被实例化,而内部类需要在外部类实例化后才能实例化
5、static静态导包
用import static 替代import
一般我们导入一个类用
import java.io.File;
而静态导包是这样的
import static java.lang.Integer.*;
二、final关键字
1.修饰一个引用
如果引用是一个基本数据类型,则该引用为一个常量,无法修改
如果引用为引用数据类型,比如对象、数组,则该对象、数组本身可以修改,但指向该对象的引用不可以修改
如果引用是类的成员变量,则必须当场赋值,否则编译报错
2、修饰一个方法
当final修饰一个方法时,该方法为最终方法,无法被子类重写。但可以被继承
3.修饰一个类
final修饰类,这个类就是“断子绝孙类”,最终类,无法被继承
三、transient关键字
1、如果变量被transient修饰,变量将不会是对象持久化的一部分,该变量内容在序列化后无法获得访问
2、transient关键字只能修饰变量,而不能修饰方法和类。本地变量是不能被transient修饰的
3、被transient修饰的变量不能再被序列化,一个静态变量不管是否被transient修饰均不能被序列化
为什么要使用transient关键字?
当持久化对象时,可能会有一些特殊的对象成员数据,比如银行卡密码等,我们不想序列化来持久化保存它,就在这个域前加上关键字transient即可
四、volatile关键字
一旦一个类的成员变量或者类的静态成员变量被volatile修饰,就具备了两层意思:
1.保证了不同线程对这个变量的操作是可见性的,即一个线程修改了值,那么这个新值对于其他线程是立即可见的
2.禁止进行指令重排序
PS:volatile禁止指令重排序有两层含义:1.当程序执行到volatile变量的读写操作时,该变量前面的操作肯定已经完成,且其结果对后面的操作可见;在该变量后面的操作肯定还没有开始进行。2.在进行指令优化时,不能把volatile变量后面的语句放在其前面执行
volatile关键字不能保证对变量操作的原子性(单线程可以)!!!
volatile的底层实现:
jvm底层采用‘内存屏障’来实现volatile语义。在JMM中,线程之间的通信通过共享内存来实现。
volatile内存语义:
当写一个volatile变量时,JMM会把该线程所对应的本地内存中的共享变量值立即刷新到主内存上
当读一个volatile变量时,JMM会把该线程所对应的本地内存中的共享变量值设为无效,直接从主内存上读
volatile的底层是通过插入内存屏障来实现的,但是对于编译器来说,发现一个最优布置来最小化插入内存屏障的总数几乎是不可能的,所以,JMM采用保守策略。如下:
在每一个volatile写操作前面插入一个StoreStore屏障
在每一个volatile写操作后面插入一个StoreLoad屏障
在每一个volatile读操作后面插入一个LoadLoad屏障
在每一个volatile读操作后面插入一个LoadStore屏障
StoreStore屏障可以保证在volatile写之前,其前面的所有普通写操作都已经刷新到主内存中;
StoreLoad屏障的作用是避免volatile写与后面可能有的volatile读/写操作重排序
LoadLoad屏障用来禁止处理器把上面的volatile读与下面的普通读重排序
LoadStore屏障用来禁止处理器把上面的volatile读与下面的普通写重排序
java中volatile关键字提供了一个功能,那就是被其修饰的变量在被修改后可以立即同步到主内存,被其修饰的变量在每次使用之前都从主内存刷新。因此,可以使用volatile来保证多线程操作时变量的可见性。
五、单例模式
单例模式有差不多6、7种写法吧,这是我感觉比较好用的几种:
public class SingleTon {
private static class SingTonHodler{
private static SingleTon INSTANCE = new SingleTon();
}
private SingleTon(){};
public static final SingleTon getInstance(){
return SingTonHodler.INSTANCE;
}
}
还有effectiveJava中提倡的一种写法:
public enum SingleTon {
INSTANCE;
public void doSomething(){
System.out.println("singleTON 创建完成");
}
}
六、多线程
6.1 线程多次调用start方法,会发生什么?
调用Thread类的start()方法时,此时该线程就处于就绪状态,并没有运行。通过调用run()方法来完成运行操作,run()称为线程体,它包含了要执行的这个线程的内容,run()运行完成,此线程终止。如果CPU再运行其他线程,直接调用run()方法,这只是调用一个方法而已,程序中依然只有一个主线程,是没有达到多线程的目的的。
一个线程对象只能调用一次start()方法,如果调用多次就会抛出java.lang.IllegalThreadStateException。
可以被重复调用的是Run()方法。
6.2 常用的几种线程池
打开java.util.concurrent.Executors类,可以看到的线程池列表如下:
但常用的也就几种。
6.2.1 newCachedThreadPool
这是一个可缓存的线程池,如果线程池个数超过需要的个数,可灵活回收空闲线程;如若不够,则创建新线程。
这种线程池的特点是:
1.创建的线程最大个数为Interger. MAX_VALUE,相当于没有限制
2.如果长时间没有向线程池中提交任务——工作线程空闲了指定时间(默认1分钟),则工作线程将自动终止。这时再提交一个新任务,线程池会重新创建一个新的线程。
3.使用该线程时,一定要注意控制线程池线程的数量,否则大量线程同时运行会造成系统瘫痪。
6.2.2 newFixedThreadPool
这是一个创建固定工作线程数量的线程池。每提交一个任务就会创建一个工作线程,如果任务超过了线程池的线程数量,则将超过数量的任务存入池队列中。
FixedThreadPool是一个典型且优秀的线程池。它具有线程池提高程序效率和节省创建线程所耗开销的优点。但是在线程池空闲时,即线程池中没有可运行的任务时,它也不会释放工作线程,还会占用一定的系统资源。
该线程池的大小最好根据系统资源进行设置:Runtime.getRuntime().availableProcessors();
6.2.3 newSingleThreadExecutor
这是一个创建单线程的Executor,即只创建唯一的工作者线程来执行任务,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。如果这个线程异常结束,会有另一个取代它,保证顺序执行。单工作线程最大的特点是可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。
6.2.4 newScheduleThreadPool
这是一个创建定时工作线程的线程池。支持定时执行和周期性执行。