Throw:
Error:
错误,不可修复的,计算机硬件问题
Expection:
Runtime:运行时异常,继承自Exception
自定义异常:需要继承Exception
try{
运行可能会出现问题的代码
}catch(异常类,变量名){
变量名.printStackTrace():对异常信息详细描述
变量名.getMessage():对异常的简短描述
}finally{
语句
}
执行流程:
出现异常信息并且捕获到了对应的异常执行catch里面的语句然后执行finally里面的,如果没有捕获到就会把异常丢给jvm由虚拟机处理就会结束程序,执行不到finally语句,没有异常的话就是不执行catch,执行finally
处理异常的三种方式:
一:try{}catch{} 抓捕处理,抓捕没到抛给jvm处理了
二:在方法名后面用throws 异常名声明异常,谁调用这个方法谁就要处理这个异常(甩锅)
三:自定义异常:
①定义一个类MyClass去继承Exception
public MyClass extends Exception{ // 使用idea的快捷键自动生成无参和带参构造方法
// 无参构造
public MyClass(){
}
// 带参构造
public MyClass(String message){
super(message);
}
}
public class Demo{
public static void main(String[]args){
try{
login("");
}catch(MyClass e){
e.getMessage();
}
}
}
public static void login(String userName) throws MyClass(这是异常声明,也可以try catch包裹if语句处理,如果用try catch main方法调用这个方法就不用处理了){
if(userName.equals("")){
throw new MyClass(“用户名不能为空)”)
}
}
进程:
程序就是进程,一个程序可以有多个进程,一个进程里面只有一个线程就是单线程,有多个就是多线程程序
线程:
单线程:像我们之前运行的程序就是单线程的程序只有一个main线程在执行,是顺序执行
多线程:
并行:真正意义的同时执行
并发:是cpu快速切换的起来执行,肉眼察觉不到,所以看起来就是同时执行
线程安全:
多个线程同时执行,对一个变量进行读写操作,会导致线程不安全.
如何保证安全(以下三种都是悲观锁):
同步代码块:
synchronized(同步锁){
// 需要同步操作的代码
}
同步方法:
在需要只有一个线程单独执行的方法上加上synchronized
public synchronized void run{
//修饰符后面,返回值前
}
Lock锁(Lock是个接口,所以需要子类去实现他):
Lock lock=new ReentrantLock();
lock.lock():开启锁
lock:unlock():关闭锁
public class VolatileThread extends Thread {
// 定义成员变量
private boolean flag = false ;
public boolean isFlag() { return flag;}
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 将flag的值更改为true
this.flag = true ;
System.out.println("flag=" + flag);
}
}
public class VolatileThreadDemo {
public static void main(String[] args) {
// 开启了一个新的线程
VolatileThread t1 = new VolatileThread();
t1.start(); // 新线程将自己的工作内存中的flag值改为true
// 主线程
while(true){
// 判断
if (t1.isFlag()) { // 访问的是主线程自己的工作内存的数据值为false [无法访问其它的工作内存的]
System.out.println("执行了==========");
}
}
}
}
输出的结果是flag=true,永远不会输出执行了
原理(JMM内存原理):
线程在执行的时候都会开辟自己的工作内存,而成员变量是存在主内存的,线程在执行的时候会从主内存拿过来这个成员变量,保存到自己的工作内存,然而其他线程改变这个值后在传回主内存,另外的线程是不知道的,保存的还是之前的那个没被修改后的值,所以在这个程序中main线程拿到的是flase,不会执行.
解决方法一:
public class VolatileThreadDemo {
public static void main(String[] args) {
// 开启了一个新的线程
VolatileThread t1 = new VolatileThread();
t1.start(); // 新线程将自己的工作内存中的flag值改为true
// 主线程
while(true){
synchronized (t1) { // 进入后,清空自己的工作内存!
// 判断
if (t1.isFlag()) { // 拿的是从主内存获得最新数据!true
System.out.println("执行了==========");
}
}
}
}
}
// 此时,测试的时候,可以看到main线程里面的语句: 执行了================\
锁住了以后,有一个线程修改了变量的值,再次进去main线程,锁会清空工作内存
某一个线程进入synchronized代码块前后,执行过程入如下:
-
线程获得锁
-
清空工作内存
-
从主内存拷贝共享变量最新的值到工作内存成为副本
-
执行代码
-
将修改后的副本的值刷新回主内存中
-
线程释放锁
解决方法二:
使用volatile关键字对全局变量进行修饰:
如果一个线程修改了这个全局变量,其他工作内存的这个局部变量会失效,执行到其他内存后悔重新拿这个值
缺点:volatie可以解决线程的不可见性,解决不了线程的不安全性,线程还是不 安全的
volatile和synchronized区别
volatile:只能修饰成员变量
synchronized:可以修饰方法和代码块
原子性:
public class VolatileAtomicThread implements Runnable {
// 定义一个int类型的遍历
private int count = 0 ;
@Override
public void run() {// 对该变量进行++操作,100次 for(int x = 0 ; x < 100 ; x++) { count++ ; System.out.println("count =========>>>> " + count); }
}
}
public class VolatileAtomicThreadDemo {
public static void main(String[] args) {
// 创建VolatileAtomicThread对象
VolatileAtomicThread volatileAtomicThread = new VolatileAtomicThread() ;
// 开启100个线程对count进行++操作
for(int x = 0 ; x < 100 ; x++) {
new Thread(volatileAtomicThread).start();
}}
}
结果不一定是1000,看运气啦
为什么:
比如count到9993了,然后一个线程拿到了这个9993,还没有进行++的操作就被其他的线程抢占了cpu执行权,那个线程拿到的也是9993,然后++操作了变成了9994写会,然后又被那个线程抢回来了cpu执行权进行了++操作,也是变成了9994就覆盖了数据所以就浪费了一次循环.
所以这个count操作就是三步,从主内存拿数据,然后操作这个数据,然后写会这个数据.
用volatile修饰count也是不行的,因为这个关键字只能保证可见性并不能保证安全性,第一个线程拿到的是9993,但是还没有写会,就被第二个线程抢占了,所以跟可见性没有关系
解决方法一:
锁机制,进行count++操作之前加锁,只能让一个线程进行操作,操作完以后释放锁,但是效率会很慢
解决方法二:
AtomicInteger ait=new AtomicInteger();
原子型Integer,可以实现原子更新操作
public AtomicInteger():// 初始化一个默认值为0的原子型Integer [重要]
public AtomicInteger(int initialValue):// 初始化一个指定值的原子型Integer
int get(): // 获取值
int getAndIncrement(): // 以原子方式将当前值加1,注意,这里返回的是自增前的值。[重要]
int incrementAndGet(): // 以原子方式将当前值加1,注意,这里返回的是自增后的值。
int addAndGet(int data): // 以原子方式将输入的数值与实例中的值(AtomicInteger里的value)相加,并返回结果。
int getAndSet(int value): // 以原子方式设置为newValue的值,并返回旧值。
public class VolatileAtomicThread implements Runnable {// 创建一个int类型对应的原子类(给定初始值为0)
AtomicInteger i = new AtomicInteger();
public class VolatileAtomicThread implements Runnable {
// 创建一个int类型对应的原子类(给定初始值为0)
AtomicInteger i = new AtomicInteger();
@Override
public void run() {
// 对该变量进行++操作,100次
for (int x = 0; x < 100; x++) {
// 先对原子类里面的int类型的数值+1,然后返回!【+1动作,都是在一个原子内完成的】
int count = i.incrementAndGet();
System.out.println("count =========>>>> " + count);
}
}
}
原子类CAS机制:
比较在交换
从主内存拿到值(比如9995)到自己的工作内存对于线程一来说旧的预期值是9995,新的预期值是9996,然后进行++操作,如果被线程二抢占了cpu执行权,线程二比较幸运读取,然后++然后写回变成了9996,然后之前的线程一抢占回了cpu执行权进行写会,但是!!写会之前他会先拿自己的旧的预期值与主内存的值进行比较如果主内存的值和自己的新的预期值不一样,那么他的旧的预期值就是变成这个值9996,然后新的预期值就是9997,但是如果旧的预期值和主内存的值一致那就是这个主内存的值没有被其他线程操作就会提交写会数据.(乐观锁)
悲观锁和乐观锁:
悲观锁:在提防其他线程,认为其他线程肯定会抢占cpu执行权,所以在自己执行的时候就会锁上不让其他线程进来保证安全性,
乐观锁:不会像悲观锁那样提防,但是会小心翼翼,在自己执行完操作进行写回的时候回多一个心眼,会先比较一下,然后再决定是否写回.
ConcurrentHashMap:
为什么要使用ConcurrentHashMap:
因为HashMap,会导致数据紊乱,不安全,HashTable:效率太低
CopyOnWriteArrayList
CopyOnWriteArraySet