包装类、多线程的基本使用

包装类

1.基本数据类型对应的引用数据类型(包装类)

1.概述:所谓的包装类就是基本类型对应的类(引用类型),我们需要将基本类型转成包装类,从而让基本类型具有类的特性(说白了,就是将基本类型的数据转成包装类,就可以使用包装类中的方法来操作此数据)
    
2.为啥要学包装类:
  a.将来有一些特定操作,调用方法需要传递包装类
    比如:ArrayList集合,add(Object obj),我们只能传递Object的子类对象,此时如果我们想传递一些基本类型的数据,Object作为一个引用类型不能直接接受基本类型,所以我们需要先将基本类型转成包装类传递到Object3.将来我们需要让基本类型和包装类互相转换,为啥:
  a.基本类型为啥转成包装类:
    调用某个方法,方法参数传递Object类型,ArrayList集合,add(Object obj),我们只能传递Object的子类对象,此时如果我们想传递一些基本类型的数据,Object作为一个引用类型不能直接接受基本类型,所以我们需要先将基本类型转成包装类传递到Object中
              
  b.包装类为啥转成基本类型:
    包装类不能直接使用+ - * /做运算,所以需要先将包装类转成基本类型,才能直接使用+ - * /符号
基本类型包装类
byteByte
shortShort
intInteger
longLong
floatFloat
doubleDouble
charCharacter
booleanBoolean

2.Inteaer的介绍以及应用

2.1Integer基本使用

1.概述:Integerint的包装类
2.构造:
  a.Integer(String i):i必须是数字格式
  b.Integer(int i)    
public class Exercise {
    public static void main(String[] args) {
        Integer integer = new Integer("10");
        System.out.println("integer = " + integer);
        Integer integer1 = new Integer(100);
        System.out.println("integer1 = " + integer1);

        Boolean aBoolean = new Boolean(true);
        System.out.println("aBoolean = " + aBoolean);
        Boolean aBoolean1 = new Boolean("true");
        System.out.println("aBoolean1 = " + aBoolean1);
        Boolean aBoolean2 = new Boolean("True");
        System.out.println("aBoolean2 = " + aBoolean2);
    }
}
//输出
integer = 10
integer1 = 100
aBoolean = true
aBoolean1 = true
aBoolean2 = true
Boolean aBoolean = new Boolean(true);

Boolean包装类中 , 底层调用了parseBoolean(String s) , 在parseBoolean中调用了equals.IgnoreCase(s) , 忽略了大小写

public static boolean parseBoolean(String s) {
    return "true".equalsIgnoreCase(s);
}

2.2装箱and拆箱

1.装箱:
  将基本类型变成包装类
      
2.方法:
  static Integer valueOf(int i)  
  static Integer valueOf(String s) -> s需要是数字格式
1.拆箱:将包装类转成基本类型
2.方法:
  int intValue()  
public class Exercise03 {
    public static void main(String[] args) {
        //装箱
        Integer integer = Integer.valueOf(10);
        System.out.println("integer = " + integer);
        Integer integer1 = Integer.valueOf("15");
        System.out.println("integer1 = " + integer1);
        //拆箱
        int value = integer.intValue();
        System.out.println("value = " + value);
        int value1 = integer1.intValue();
        System.out.println("value1 = " + value1);
    }
}

2.3自动拆箱装箱

在操作的过程中,基本类型和包装类之间可以自动转换

public class Demo04Integer {
    public static void main(String[] args) {
        Integer i = 10;
        i+=1;
        System.out.println(i);
    }
}

反编译后如下图:

1694497269393

笔试题:

public class Demo05Integer {
public static void main(String[] args) {
    Integer i1 = 100;
    Integer i2 = 100;
    System.out.println(i1==i2);//true

    Integer i3 = 127;
    Integer i4 = 127;
    System.out.println(i3==i4);//true

    Integer i5 = 128;
    Integer i6 = 128;
    System.out.println(i5==i6);//false
}
}
包装类缓存对象
Byte-128-127
Short-128-127
Integer-128-127
Long-128-127
Float没有
Double没有
Character0-127
Booleantrue和false

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

@IntrinsicCandidate
public static Integer valueOf(int i) {
   if (i >= IntegerCache.low && i <= IntegerCache.high)
       return IntegerCache.cache[i + (-IntegerCache.low)];
   return new Integer(i);
}

可以发现当数值超过了128就会开辟一个新空间 , 即new Integer(i)

此为享元设计模式

3.基本类型与String转换

3.1 基本类型往String转

1.方式1:
  +
2.方式2:String中的静态方法
  static String valueOf(int i)  
public class String_transform {
    public static void main(String[] args) {
        //基本数据类型转包装类
        //方式1: static String valueOf(int a)
        String s = String.valueOf(10);
        System.out.println(s+1);

        //方式2:+""
        int i  = 10;
        String s1 = i+"";
        System.out.println(s1+1);

    }
}

3.2 String转成基本数据类型

每一个包装类中都有一个parseXXX的方法
位置方法说明
Bytestatic byte parseByte(String s)将字符串转成byte
Shortstatic short parseShort(String s)将字符串转成short
Integerstatic int parseInt(String s)将字符串转成int
Longstatic long parseLong(String s)将字符串转成long
Floatstatic float parseFloat(String s)将字符串转成float
Doublestatic double parseDouble(String s)将字符串转成double
Booleanstatic boolean parseBoolean(String s)将字符串转成boolean
public class String_transform {
    public static void main(String[] args) {
        //基本数据类型转包装类
        //方式1: static String valueOf(int a)
        String s = String.valueOf(10);
        System.out.println(s+1);

        //方式2:+""
        int i  = 10;
        String s1 = i+"";
        System.out.println(s1+1);


        //将String转为基本数据类型
        int i1 = Integer.parseInt("101");
        System.out.println("i1+1 = " + i1+1);
        double parseDouble = Double.parseDouble("10.2");
        System.out.println("parseDouble+1 = " + parseDouble + 1);
    }
}

将来开发,我们定义javabean的时候,需要将基本类型的字段(成员变量)定义成包装类类型的

1.将来我们的javabean都是和表对应的
2.针对下面的Student,创建出来的表
id   -> 一般作为表的主键使用,而且主键会自增
name
age

如果主键为自增,我们添加数据时sql语句可以这么写:
insert into student (id,name,age) values (NULL,'柳岩',36);
我们可以用NULL占位(主键自增的列)
    
而我们将来需要将javabean对象中的数据添加到sql语句中,从而保存到数据库中
    
包装类型的属性默认值为NULL,所以到时候我们将javabean对象中的数据放到数据库中时,我们不需要单独为id属性赋值
    
如果不给javabean中的id赋值: Student s = new Student(null,"哈哈",36)
此时我们将javabean中的属性值获取出来放到sql中,正好是: 
insert into student (id,name,age) values (NULL,'哈哈',36);
public class User {
   private Integer uid;
   private String name;
   private Integer age;

   public User(Integer uid, String name, Integer age) {
       this.uid = uid;
       this.name = name;
       this.age = age;
   }

   public User() {
   }

   public Integer getUid() {
       return uid;
   }

   public void setUid(Integer uid) {
       this.uid = uid;
   }

   public String getName() {
       return name;
   }

   public void setName(String name) {
       this.name = name;
   }

   public Integer getAge() {
       return age;
   }

   public void setAge(Integer age) {
       this.age = age;
   }

   @Override
   public String toString() {
       return "User{" +
               "uid=" + uid +
               ", name='" + name + '\'' +
               ", age=" + age +
               '}';
   }
}

多线程

1.多线程_线程和进程

1694511947394

进程(Processor) : 程序的一次执行。由操作系统创建并分配资源,执行一个单独的任务。 进程是系统进行资源分配和调度的独立单位,每个进程都有自己的内存空间和系统资源。进程内所有线程共享堆存储空间,保存程序中定义的对象和常量池。

线程作用 : 负责当前进程中程序的运行 , 一个进程中至少有一个线程 . 一个进程中是可以有多个线程的,这样的应用程序就称之为多线程程序 . 线程是进程内的执行单元,不分配单独的资源,执行一个单独的子任务。线程是进程内调度和分派的基本单位,共享进程资源。每个线程有自己的独立的栈存储空间,保存线程执行的方法以及基本类型的数据。 运行的 Java 程序内含至少一个主线程 main ,用户可以在 Java 程序中自定义并调用多个线程。 JVM 垃圾回收线程也是一个独立的线程。

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

并发:同一个时刻多个线程同时操作了同一个数据 , 多个任务交替使用 CPU 核心工作,以提高 CPU 利用率。

并行:同一个时刻多个线程同时执行不同的程序 , 多个CPU核心同时工作,处理不同的任务。

2.CPU调度

1.分时调度:指的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用的cpu的时间片
2.抢占式调度:多个线程轮流抢占CPU使用权,哪个线程先抢到,哪个线程先执行,一般都是优先级高的抢到CPU使用权的概率大,我们java程序都是抢占式调度

线程的运行状态 : 线程除创建状态 New 和结束状态 Terminate 外,主要有以下几种运行状态:

NEW(新建) : 线程刚被创建,但是并未启动。还没调用start方法。

运行 : (Running) CPU 正在执行线程。

就绪 : (Runnable) 线程一切就绪,等待 CPU 执行。

运行/就绪状态 统称为可运行状态 Runnable。 Java 程序中,线程在 运行/就绪状态 之间的切换由 JVM 自动调度,开发者无法获知。线程之间的调度采用分优先级多队列时间片轮转算法。进程在执行完 CPU 时间片切换到就绪状态之前会先保存自己的状态,下次进入运行状态时再重新加载。

阻塞 : (Blocked) 线程因缺少其他资源,比如请求资源被上锁而暂停执行。在获得资源后进入就绪状态。 一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。

等待 : (Waitting) 线程接受了等待指令,释放资源暂停执行。在超时/接受唤醒指令后进入就绪状态。 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。

Terminated(被终止) : 因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。或者调用过时方法stop()

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

3.主线程介绍

在CPU和内存之间专门为main开辟的通道叫做主线程

主线程专门运行main方法的

1694513606433

创建线程的方式(重点)

1.第一种方式_extends Thread

步骤 :

​ ①定义类 , 继承Thread类(Thread是一个线程类)

​ ②重写Thread中的run方法 , 设置线程任务(自定义的线程需要干什么 , 将业务写进run())

​ ③创建自定义线程类对象

​ ④调用Thread中的start方法(使线程开始执行 ; Java虚拟机会调用线程中的 run 方法)

public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            System.out.println("我是MyThread....");
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();	//开启线程 , jvm自动调用重写的run方法

        for (int i = 0; i < 3; i++) {
            System.out.println("我是主线程...");
        }
    }
}

2.多线程在内存中的运行原理

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

当有多个线程开启时 , 进程会为每个线程独自开启新线程的栈空间

注意 : 同一个线程对象 , 不可以连续调用多个start方法

如果需要开启多个线程 , 需要重新new

3.Thread类中的方法

1.void start() 开启线程,jvm会自动执行run方法
2.String getName() 获取线程名字
3.static Thread currentThread()  -> 获取的是当前正在执行的线程对象,此方法在哪个线程中用,获取的就是哪个线程对象 
4.void setName(String name) -> 给线程设置名字
5.static void sleep(long time)->线程睡眠,传递毫秒值,时间超时,线程自动醒来,自动继续执行   
public class MyThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 3; i++) {
            Thread.sleep(1000l);
            System.out.println(getName()+"线程正在执行....");   
            //getName()  获取线程名字   因为继承了Thread,可以直接调用
        }
    }
}
public class Test01 {
    public static void main(String[] args) {
        MyThread myThread = new MyThread();
        myThread.start();		//开启线程 , jvm自动调用重写的run方法
        myThread.setName("haha");	//修改线程名字
        for (int i = 0; i < 3; i++) {
            Thread.sleep(1000l);	//线程睡眠1秒
            System.out.println(Thread.currentThread().getName()+"线程正在执行..."); //static Thread currentThread()   获取的是当前正在执行的线程对象
        }
    }
}
//输出
main线程正在执行...
haha线程正在执行....
main线程正在执行...
haha线程正在执行....
main线程正在执行...
haha线程正在执行....

4.第二种方式_实现Runnable接口

步骤 :

​ ①定义一个类 , 实现Runnable接口

​ ②重写run方法 , 设置线程任务

​ ③创建自定义的线程类对象

​ ④利用Thread中的构造方法 : Thread(Runnable target) , 创建实现类对象 , 放到Thread中

​ ⑤调用start方法 , 启动线程

public class MyThread_implements implements Runnable{
    @Override
    public void run() {
        try {
            Thread.sleep(100L);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+"线程正在执行....");
        }
    }
}
public class Test02 {
    public static void main(String[] args) throws InterruptedException {
        MyThread_implements myThread_implements = new MyThread_implements();
        Thread thread = new Thread(myThread_implements);
        thread.start();

        for (int i = 0; i < 3; i++) {
            Thread.sleep(100l);
            System.out.println(Thread.currentThread().getName()+"线程正在执行...");
        }
    }
}

问题 : 为什么再run方法中处理异常只能使用try_catch?

因为Thread中的run方法没有抛异常 , 子类重写父类之后也不抛 , 只能try_catch自己解决

5.两种实现多线程的方式区别

继承extends Thread : 由于继承只能单继承 , 如果某个场景它有父类也需要继承 , 就无法继承Thread了 , 所以有局限性

实现implement Runnable : 接口可以多继承多实现 , 解决了继承的局限性 , 一个类继承一个父类的同 时可以实现一个或多个接口 -----> 推荐使用

6.第三种方式_匿名内部类创建多线程

匿名内部类回顾:一种格式代表子类对象或实现类对象

使用:
	new 接口/抽象父类(){
        重写方法
    }.重写的方法();

	接口/抽象类 对象名 = new 接口/抽象父类(){
        重写方法
    }
	对象名.重写的方法();
public class Test03Niming {
    public static void main(String[] args) {
        new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 3; i++) {
                    System.out.println(Thread.currentThread().getName()+"匿名线程正在执行...");
                }
            }
        }).start();

        for (int i = 0; i < 3; i++) {
            System.out.println(Thread.currentThread().getName()+"主线程正在执行...");
        }
    }
}

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

线程安全

问 : 什么时候出现线程安全问题 ?

答 : 当多个线程访问同一个资源时 , 可能会出现线程安全问题

1.线程安全问题 —>线程不安全代码

public class MyTicket implements Runnable{
    int ticket = 100;
    @Override
    public void run() {
        while(true){
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if (ticket>0){
                System.out.println(Thread.currentThread().getName()+"买了第"+ticket+"张票");
            }
            ticket--;
        }
    }
}

public class Test01 {
    public static void main(String[] args) {
        MyTicket myTicket = new MyTicket();

        Thread thread1 = new Thread(myTicket,"柳岩");
        Thread thread2 = new Thread(myTicket,"涛哥");
        Thread thread3 = new Thread(myTicket,"金莲");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

原因 : CPU在多个线程中做高速切换

2.解决线程安全问题的第一种方式(使用同步代码块)

格式:
	synchronized(任意对象->锁对象){
        可能出现的线程不安全的代码
    }

执行流程:
	某一个线程抢到锁,进入内部执行,其他线程就抢不到锁了,只能等待,等着执行的线程出了代码块,将锁释放,其他线程才有可能抢到锁对象,去执行
public class Ticket implements Runnable{
    Integer ticket = 50;

    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100L);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            synchronized (this){
                if(ticket > 0){
                    System.out.println(Thread.currentThread().getName()+"抢到了第"+ticket+"张票");
                    ticket--;
                }else{
                    return ;
                }
            }
        }
    }
}
public class Test04 {
    public static void main(String[] args) {
        Ticket ticket = new Ticket();
        Thread t1 = new Thread(ticket, "张三");
        Thread t2 = new Thread(ticket, "李四");
        Thread t3 = new Thread(ticket, "王五");

        t1.start();
        t2.start();
        t3.start();
    }
}

3.解决线程安全问题的第二种方式:同步方法

3.1.普通同步方法

1.格式:
  public synchronized 返回值类型 方法名(参数){
      方法体
      return 结果
  }

  public 返回值类型 方法名(参数){
      synchronized(this){
          方法体
          return 结果
      }
  }

2.默认锁:this
public class Ticket1 implements Runnable{
    Integer ticket = 50;
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100l);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            method();
            if(ticket == 0){
                return;
            }
        }
    }
    /*
    //采用synchronized同步方法
    public synchronized void method(){
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName()+"抢到了第"+ticket+"张票");
            ticket--;
        }
    }*/

    public void method(){
        synchronized (this){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName()+"抢到了第"+ticket+"张票");
                ticket--;
            }
        }
    }
}
public class Test05 {
    public static void main(String[] args) {
        Ticket1 ticket1 = new Ticket1();
        Thread t1 = new Thread(ticket1, "张三");
        Thread t2 = new Thread(ticket1, "李四");
        Thread t3 = new Thread(ticket1, "王五");
        t1.start();
        t2.start();
        t3.start();
    }
}

3.2.静态同步方法

1.格式:
  public static synchronized 返回值类型 方法名(参数){
      方法体
      return 结果
  }

2.默认锁:
  当前类.class
public class Ticket2 implements Runnable{
    static Integer ticket = 50;
    @Override
    public void run() {
        while (true){
            try {
                Thread.sleep(100l);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            method();
            if(ticket == 0){
                return;
            }
        }
    }
    /*
    //采用synchronized同步方法
    public static synchronized void method(){
        if(ticket > 0){
            System.out.println(Thread.currentThread().getName()+"抢到了第"+ticket+"张票");
            ticket--;
        }
    }*/

    public static void method(){
        synchronized (Ticket2.class){
            if(ticket > 0){
                System.out.println(Thread.currentThread().getName()+"抢到了第"+ticket+"张票");
                ticket--;
            }
        }
    }
}
public class Test06 {
    public static void main(String[] args) {
        Ticket2 ticket2 = new Ticket2();
        Thread t1 = new Thread(ticket2, "张三");
        Thread t2 = new Thread(ticket2, "李四");
        Thread t3 = new Thread(ticket2, "王五");

        t1.start();
        t2.start();
        t3.start();
    }
}

死锁

1.死锁介绍(锁嵌套就有可能产生死锁)

​ 指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.

1694519025452
根据上图所示:线程T1正在持有R1,但是T1线程必须再拿到R2,才能继续执行
而线程T2正在持有R2,但是T2线程需要再拿到R1,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中

2.死锁的分析

1694519069437

3.代码实现

public class LockA {
    public static LockA lockA = new LockA();
}
public class LockB {
    public static LockB lockB = new LockB();
}
public class DieLock implements Runnable {
    private boolean flag;

    public DieLock(boolean flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        if (flag) {
            synchronized (LockA.lockA) {
                System.out.println("if...lockA");
                synchronized (LockB.lockB) {
                    System.out.println("if...lockB");
                }
            }
        } else {
            synchronized (LockB.lockB) {
                System.out.println("else...lockB");
                synchronized (LockA.lockA) {
                    System.out.println("else...lockA");
                }
            }
        }

    }
}
public class Test01 {
    public static void main(String[] args) {
        DieLock dieLock1 = new DieLock(true);
        DieLock dieLock2 = new DieLock(false);

        new Thread(dieLock1).start();
        new Thread(dieLock2).start();
    }
}

lag = flag;
}

@Override
public void run() {
    if (flag) {
        synchronized (LockA.lockA) {
            System.out.println("if...lockA");
            synchronized (LockB.lockB) {
                System.out.println("if...lockB");
            }
        }
    } else {
        synchronized (LockB.lockB) {
            System.out.println("else...lockB");
            synchronized (LockA.lockA) {
                System.out.println("else...lockA");
            }
        }
    }

}

}


~~~java
public class Test01 {
    public static void main(String[] args) {
        DieLock dieLock1 = new DieLock(true);
        DieLock dieLock2 = new DieLock(false);

        new Thread(dieLock1).start();
        new Thread(dieLock2).start();
    }
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值