Java学习(异常,throw,throws关键字,Object非空判断requireNonNUll,处理异常的方法,finally,进程,线程(创建调用),解决线程不安全的问题)

Java学习(异常,throw,throws关键字,Object非空判断requireNonNUll,处理异常的方法,finally,进程,线程(创建调用),解决线程不安全的问题),第1张

Java学习(异常,throw,throws关键字,Object非空判断requireNonNUll,处理异常的方法,finally,进程,线程(创建/调用),解决线程不安全的问题)

2022/1/21

1.异常
        程序在执行过程中,出现的非正常的情况,最终会导致JVM的非正常停止。
        在Java等面向对象的编程语言中,异常本身是一个类,产生异常就是创建异常对象并抛出了一个异常对象。Java处理异常的方式是中断处理。
        异常并不是指语法错误,语法错了,编译不通过,不会产生字节码文件,根本不能运行。
 

2.异常体系
异常机制其实是帮助我们找到程序中的问题,异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Eror与java.lang.Exception.
Error:是指出现了错误。(很严重)
Exception:是指出现了异常。(解决了异常程序就可以正常运行)

3.异常产生的过程解析
1)访问数组中没有的索引,这时候JVM就会检测出程序会出现异常
JVM会做两件事情:
        <1>JVM会根据异常产生的原因创建一个异常对象,这个异常对象包含了异常产生的(内容,
        原因,位置)
         <2>在出错的那个方法中,没有异常处理逻辑(try...catch),那么JVM就会把异常对象抛出
        给方法的调用者main方法来处理这个异常。

2)main方法接受到了这个异常对象,main方法也没有异常处理逻辑,继续把对象抛出给main方法的调用者JVM处理

3)JVM接收到了这个对象,又做了两件事
        <1>把异常对象(内容,原因,位置)以红色的字体打印在控制台
        <2>JVM会终止当前正在执行的Java程序-->中断处理

4.throw关键字

package ln.javatest.day12.demo03;


public class Demo01Throw {
    
    public static void main(String[] args) {
        //int[] arr = null;
        int[] arr = {1,2,3};
        int a = getElement(arr,3);
        System.out.println(a);
    }
    public static int getElement(int[] arr,int index){
        
        if(arr == null){
            throw new NullPointerException("传递的数组的值是null");
        }
        
        if(index<0 || index>arr.length-1){
            throw new ArrayIndexOutOfBoundsException("传递的索引超出了数组的使用范围");
        }
        int ele = arr[index];
        return ele;
    }
}

5.Objects非空判断,requireNonNull

package ln.javatest.day12.demo03;


import java.util.Objects;

public class Demo02Objects {
    public static void main(String[] args) {
        method(null);
    }

    private static void method(Object obj) {
        //对传递过来的参数进行合法性判断,判断是否为null
        
        //Objects.requireNonNull(obj);
        Objects.requireNonNull(obj,"传递的对象的值是null");
        //上面三个效果一样
    }
}

6.异常处理的第一种方式:throws关键字,交给别人处理

package ln.javatest.day12.demo03;


import java.io.FileNotFoundException;
import java.io.IOException;
import java.nio.file.FileAlreadyExistsException;

public class Demo03Throws {
    
    public static void main(String[] args)throws IOException{
        readFile("c:\a.txt");
    }

    
    public static void readFile(String fileName) throws IOException{
        if(!fileName.equals("c:\a.txt")){
            throw new FileNotFoundException("传递的文件路径不是c:\a.txt");
        }
        
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件的后缀名不对");
        }
        System.out.println("路径没有问题,读取文件");
    }
}

7.异常处理的第二种方式:try...catch,自己处理
8.Throwable类中的3个异常处理方法
使用throws处理异常后,JVM直接中断程序,后续代码不再运行
try...catch可以实现后续代码接着运行

package ln.javatest.day12.demo03;


import java.io.IOException;

public class Demo04TryCatch {
    public static void main(String[] args) {
        try {
            readFile("d:\a.tx");
        }catch(IOException e){ //try中抛出什么异常,catch就定义什么异常变量,用来接收这个异常对象
            //System.out.println("catch- 传递的文件后缀不是.txt");
            
            System.out.println(e.getMessage());
        }
        System.out.println("h后续代码");
    }
    
    public static void readFile(String fileName) throws IOException {
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件的后缀名不对");
        }
        System.out.println("路径没有问题,读取文件");
    }
}

9.finally代码块

package ln.javatest.day12.demo03;


import java.io.IOException;

public class Demo05TryCatchFinally {
    public static void main(String[] args) {
        try {
            readFile("d:\a.tx");
        }catch(IOException e){ //try中抛出什么异常,catch就定义什么异常变量,用来接收这个异常对象
            //System.out.println("catch- 传递的文件后缀不是.txt");
            
            System.out.println(e.getMessage());
        }finally {
            System.out.println("资源释放");//无论程序是否出现异常,都要执行的
        }
        System.out.println("h后续代码");
    }
    
    public static void readFile(String fileName) throws IOException {
        if(!fileName.endsWith(".txt")){
            throw new IOException("文件的后缀名不对");
        }
        System.out.println("路径没有问题,读取文件");
    }
}

10.多个异常使用捕获如何处理
        1.多个异常分别处理(一个try一个异常,一个catch一个处理)
        2.多个异常一次捕获,多次处理(一个try多个异常,多个catch多个处理)
        3.多个异常一次捕获一次处理(一个try多个异常,一个catch多次处理)
注意:
1)这种异常的处理方式,要求多个catch中的异常不能相同,并且若catch中的多个异常之间有子父类异常的关系,那么子类异常要求在上面的catch处理,父类异常在下面的cat吃处理。
2)运行时异常被抛出可以不处理。即不捕获也不声明抛出
3)如果finally有return语句,永远返回finally中的结果,所以要避免这种情况。
4)子父类的异常
            1>如果父类抛出了多个异常,子类覆盖父类方法时,只能抛出相同的异常或者是他的子集
                或者不抛出异常
            2> 如果父类方法没有抛出异常,子类重写父类该方法时也不可抛出异常。此时子类产生该
                异常,只能捕获处理(try...catch),不能声明抛出
注意: 父类异常是什么样,子类异常就什么样

11.自定义异常类

package ln.javatest.day12.demo03;


public class RegisterException extends Exception{
   //添加一个空参数的构造方法
    public RegisterException(){
        super();
    }
    //添加一个带异常信息的构造方法
    //查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常
    public RegisterException(String message){
        super(message);
    }
}

12.练习

package ln.javatest.day12.demo03;


public class RegisterException extends RuntimeException{
   //添加一个空参数的构造方法
    public RegisterException(){
        super();
    }
    //添加一个带异常信息的构造方法
    //查看源码发现,所有的异常类都会有一个带异常信息的构造方法,方法内部会调用父类带异常信息的构造方法,让父类来处理这个异常
    public RegisterException(String message){
        super(message);
    }
}
package ln.javatest.day12.demo03;


import java.util.Scanner;

public class Demo01RegisterException {
    //1.使用数组保存已经注册过的用户名(数据库)
    static String[] usernames = {"张三","李四","王五"};

    public static void main(String[] args) {
        // 2.使用Scanner获取用户输入的注册的用户名(前端,页面)
        Scanner sc = new Scanner(System.in);
        System.out.println("请输入您要注册的用户名:");
        String username = sc.next();
        checkUsername(username);
    }
    //3.定义一个方法,对用户输入的用户名进行判断
    public static void checkUsername(String username)  {
    // 遍历存储已经注册过用户名的数组,获取每一个用户名
        for (String name : usernames) {
            //使用获取到的用户名和用户输入的用户名比较
            if(name.equals(username)){
                // true:用户名已经存在,抛出RegisterException异常,告知用户“亲,该用户名已经被注册”
                throw new RegisterException("亲,该用户名已经被注册");
            }
        }
        //如果循环结束了,还没有找到重复的用户名,提示用户“恭喜您,注册成功!”
        System.out.println("恭喜您,注册成功!");
    }
}

13.并发与并行
并发:CPU在多个任务见交替执行。(指两个或多个时间在同一个时间段内发生)
并行:CPU分成多个对多个任务同时执行。(指两个或多个事件在同一时刻发生)
并行比并发执行速度快

14.进程:
进入到内存的程序叫进程;
进程也是程序的一次执行过程,是系统运行程序的基本单位;

15.线程:
是进程中的一个执行单元,负责当前进程中程序的执行;
一个进程中至少有一个线程;
一个进程中也可以有多个线程,这个应用程序称为多线程程序。
eg.腾讯电脑管家,功能病毒查杀,清理垃圾等
打开腾讯管家应用软件,进入到cpu执行(进程)
点击功能(病毒查杀,清理垃圾,电脑加速)执行
就会开启一条应用程序到Cpu的执行路径
cpu就可以通过这个路径执行功能
这个路径就叫做线程

16.线程的调度
1)分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
2)抢占式调度:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

17.主线程
单线程程序:java程序中只有一个线程(执行从main方法开始,从上到下依次执行)
JVM执行main方法:
main方法会进入到栈内存
JVM会找 *** 作系统开辟一条main方法通向cpu的执行路径
cpu就可以通过这个路径来执行main方法
而这个路径有一个名字,叫main(主)线程
特点:单线程的弊端,程序执行过程中出现问题后,后面的都不能运行了

18.创建多线程程序的第一种方式:创建Thread类的子类

package ln.javatest.day12.demo03;
//  1.创建一个Thread类的子类
public class MyThread extends Thread {
    //    2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println("run"+i);
        }
    }
}
package ln.javatest.day12.demo03;


public class Demo06Thread {
    public static void main(String[] args) {
        // 3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类中的方法,开启新的线程,执行run方法
        mt.start();
        for (int i = 0; i < 5; i++) {
            System.out.println("main"+i);
        }
    }
}

19.多线程原理
1>JVM执行main方法,找OS开辟一条main方法通向cpu的路径
     这个路径叫main线程,主线程
     cpu通过这个线程,这个路径可以执行main方法
2>new MYThread(创建了一个Thread类的子类对象):
     开辟了一条通向cpu的新路径,用来执行run方法
3>对于cpu而言,就有了两条执行的路径
     cpu就有了选择权,想执行哪个就执行哪个,还可能交替执行(这就是程序的随机打印结果)
     两个线程一起抢夺cpu的执行时间
注意:
在main方法中,如果直接用Thread子类对象调用run方法,则不单独开辟一个线程,而是在和main方法的同一个栈中运行,那么就不是多线程了。
必须用Thread子类对象调用start方法,才是开辟新的线程(栈空间),不同栈运行,多个栈多个线程,共享一个cpu。

20.获取线程名称

package ln.javatest.day12.demo03;


//  1.创建一个Thread类的子类
public class MyThread extends Thread {
    //    2.在Thread类的子类中重写Thread类中的run方法,设置线程任务(开启线程要做什么)

    @Override
    public void run() {
           String name = getName();
           // System.out.println(name);
            //currentThread是个静态方法,可直接用类名调用
            String t = Thread.currentThread().getName();
            System.out.println(t);
    }
}
package ln.javatest.day12.demo03;


public class Demo06Thread {
    public static void main(String[] args) {
        // 3.创建Thread类的子类对象
        MyThread mt = new MyThread();
        //4.调用Thread类中的方法,开启新的线程,执行run方法
        mt.start();
        //获取main线程的线程名
        System.out.println(Thread.currentThread().getName());
    }
}

21.设置线程名称

package ln.javatest.day12.demo03;


//  1.创建一个Thread类的子类
public class MyThread extends Thread {
    // 2.创建一个带参数的构造方法,参数传递线程的名称,调用父类的带参构造方法,把线程名称传递给父类
    public MyThread(){ }
    public MyThread(String name){
        super(name);
    }
    @Override
    public void run() {
            System.out.println(Thread.currentThread().getName());
    }
}
package ln.javatest.day12.demo03;
public class Demo06Thread {
    public static void main(String[] args) {
        // 创建Thread类的子类对象
        MyThread mt = new MyThread();
        // 1.使用Thread类中的方法setName(名字)
        mt.setName("王嘉尔");
        //调用Thread类中的方法,开启新的线程,执行run方法
        mt.start();

        //调用2带参构造方法
        new MyThread("易烊千玺");
    }
}

22.Thread类的sleep方法
public static void sleep(long millis):让当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)
毫秒数结束之后,线程继续执行

package ln.javatest.day12.demo03;

public class Demo07Sleep {
    public static void main(String[] args) {
        //模拟秒表
        for (int i = 0; i < 60; i++) {
            System.out.println(i);
        }
        //因为上面运行速度过快,为了模拟钟表一秒钟一次
        //可使用Thread类的sleep方法让程序睡眠1秒钟
        try {
            Thread.sleep(1000);//1000毫秒=1秒
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

23.开启多线程的第二种方式

package ln.javatest.day12.demo04;


public class Demo01Runnable {
    public static void main(String[] args) {
        // 3.创建一个Runnable接口的实现类对象
        Demo01Runnableimpl i = new Demo01Runnableimpl();
        // 4.创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        Thread t = new Thread(i);
       //5.调用Thread类中的start方法,开启新的线程执行run方法
        t.start();
        for (int j = 0; j < 5; j++) {
            System.out.println(Thread.currentThread().getName() + "-->" + j);
        }
    }
}
package ln.javatest.day12.demo04;
 //1.创建一个Runnable接口的实现类
public class Demo01Runnableimpl implements Runnable{
    //2.在实现类中重写Runnable接口的run方法,设置线程任务
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + "-->" + i);
        }
    }
}

24.Thread和Runnable的区别
实现Runnable接口创建多线程程序的好处:
        1.避免了单继承的局限性
                一个类只能继承一个类,类继承了Thread类就不能继承其他的类
                实现Runnable接口,还可以继承其他的类,实现其他的接口
        2.增强了程序的可扩展性,降低了程序的耦合性(解耦)
                实现Runnable接口的方式,把设置线程任务和开启新线程进行了分离(解耦)
                实现类中,重写了run方法:用来设置线程任务
                创建Thread类对象,调用start方法,来开启新线程

25.匿名内部类实现线程的创建

package ln.javatest.day12.demo04;


public class Demo01InnerClassThread {
    public static void main(String[] args) {
        //线程的父类是Thread
        //new MyThread().start
        new Thread(){
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        }.start();

        //线程接口
        //Runnable r = new RunnableImpl();多态
        //Runnable r = new Runnable
        //new Thread(r).start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println(Thread.currentThread().getName() + i);
                }
            }
        }).start();
    }
}

26.线程安全问题
多线程访问了共享的数据,才会产生线程安全问题.
不是共享的数据,不会出现线程安全问题。

package ln.javatest.day12.demo04;


public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl i = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //共享同一个实现类的对象中的对象都调用同一个实现类对象
        Thread t = new Thread(i);
        Thread t1 = new Thread(i);
        Thread t2 = new Thread(i);
        t.start();
        t1.start();
        t2.start();
    }
}
package ln.javatest.day12.demo04;


public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private int ticket = 100;
    //设置线程任务
    @Override
    public void run() {
        //先判断票是否存在
        //用死循环让卖票 *** 作重复执行
        while(true){
            if(ticket > 0){
                //票存在,卖票
                System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ ticket +"张票");
                ticket--;
            }
        }
    }
}

27.注意:
线程安全问题是不能产生的,我们可以让一个线程在访问共享数据的时候,无论是否失去了cpu的执行权限,让其他的线程只能等待,等待当前线程做完,其它线程再进行执行

28.同步代码块解决共享数据的安全问题

package ln.javatest.day12.demo04;


public class RunnableImpl implements Runnable{
    //定义一个多个线程共享的票源
    private int ticket = 100;
    //创建一个锁对象
    Object obj = new Object();
    //设置线程任务
    @Override
    public void run() {
        //先判断票是否存在
        //用死循环让卖票 *** 作重复执行
        while(true){
            synchronized(obj){
                if(ticket > 0){
                    //票存在,卖票
                    System.out.println(Thread.currentThread().getName()+"--->正在卖第"+ ticket +"张票");
                    ticket--;
                }
            }
            }
    }
}
package ln.javatest.day12.demo04;


public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl i = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //共享同一个实现类的对象中的对象都调用同一个实现类对象
        Thread t = new Thread(i);
        Thread t1 = new Thread(i);
        Thread t2 = new Thread(i);
        t.start();
        t1.start();
        t2.start();
    }
}

29.同步技术的原理:使用了一个锁对象,这个锁对象叫同步锁,也叫对象监视器
        3个线程一起抢夺cpu,谁抢到就先执行run方法,遇到synchronized代码表,这时候当前线程会检查synchronized代码块是否有锁对象,发现有,这时候会获取到锁对象,进入到同步中执行
        这时候第二个线程抢到了cpu,执行run方法,遇到synchronized代码表,检查synchronized代码块是否有锁对象,发现没有,第二个线程就进入到阻塞状态,等待上一个线程归还锁对象,第二个对象才能进入到同步中执行。
总结:同步中的线程,没有执行完毕不会释放锁,同步外的线程没有锁进不去同步内
 同步保证了只能有一个线程在同步中执行共享数据
保证了安全
程序频繁的判断锁,获取锁,释放锁,程序的效率会降低

30..同步方法、静态同步方法来解决共享数据的安全问题

package ln.javatest.day12.demo04;


public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private static int ticket = 100;
    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务
    @Override
    public void run() {
        //先判断票是否存在
        //用死循环让卖票 *** 作重复执行
        while (true) {
            payTicketstatic();
        }
    }

    
    
    //和上面效果一样
    

    
    
    public static void payTicketstatic() {
        synchronized (RunnableImpl.class) {
            if (ticket > 0) {
                //票存在,卖票
                System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票");
                ticket--;
            }
        }
    }
}
package ln.javatest.day12.demo04;


public class Demo01Ticket {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        RunnableImpl i = new RunnableImpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对象
        //共享同一个实现类的对象中的对象都调用同一个实现类对象
        Thread t = new Thread(i);
        Thread t1 = new Thread(i);
        Thread t2 = new Thread(i);
        t.start();
        t1.start();
        t2.start();
    }
}

31.Lock锁解决线程安全的问题

package ln.javatest.day12.demo04;


import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RunnableImpl implements Runnable {
    //定义一个多个线程共享的票源
    private static int ticket = 100;
    //1.在成员位置创建一个ReentrantLock对象
    Lock l = new ReentrantLock();

    //设置线程任务
    
    //下面这个和上面那个实现效果一样,不同在于,下面这个无论程序是否出现异常,锁对象都会被释放掉,提高程序效率
    @Override
    public void run() {
        //先判断票是否存在
        //用死循环让卖票 *** 作重复执行
        while (true) {
            //2.在可能会出现安全问题的代码前调用Lock接口中的方法locks获取锁
            l.lock();
            if (ticket > 0) {
                //提高安全问题出现的概率,让程序睡眠
                try {
                    Thread.sleep(10);
                    //票存在,卖票
                    System.out.println(Thread.currentThread().getName() + "--->正在卖第" + ticket + "张票");
                    ticket--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
                    l.unlock();
                }
            }
        }
    }
}

32.线程的状态
当线程被创建并启动之后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
java.lang.Thread.State这个枚举中给出了六种线程状态:
NEW(新建),Runnable(可运行),Blocked(锁阻塞),Waiting(无限等待),Timed Waiting(计时等待),Teminated(死亡状态,运行期间出现问题)

33.等待缓唤醒案例

package ln.javatest.day12.demo04;


public class Demo01Wait {
    public static void main(String[] args) {
        //创建锁对象,保证唯一
        Object obj = new Object();
        //创建一个顾客线程(消费者)使用匿名内部类
        new Thread(){
            @Override
            public void run() {
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("告知老板要的包子的种类和数量:");
                    //调用wait方法,放弃cpu的执行,进入到WAITING状态
                    try {
                        obj.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //唤醒之后执行的代码
                    System.out.println("准备吃包子啦!");
                }
            }
        }.start();
        //创建一个老板线程(生产者)
        new Thread(){
            @Override
            public void run() {
                //花了5秒做包子
                try {
                    Thread.sleep(5000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //保证等待和唤醒的线程只能有一个执行,需要使用同步技术
                synchronized (obj){
                    System.out.println("老板5秒钟做好了包子.");
                    //唤醒顾客
                    obj.notify();
                }
            }
        }.start();
    }
}

进入到TimeWaiting(计时等待)有两种方式:
1.使用sleep(long  m)方法,在毫秒值结束之后,线程睡醒进入到Runnable/Blocked状态
2.使用wait(long m)方法,wait方法如果在毫秒值结束之后,还没有被notfity唤醒,就会自动醒来,线程睡醒进入到Runnable/Blocked状态

唤醒的方法:
        void notify() : 唤醒在此对象监视器上等待的单个线程。(如果有多个,则随机唤醒一个)
        void notifyAll() : 唤醒在此对象监视器上等待的所有线程。

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5712569.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存