包装类
1.基本数据类型对应的引用数据类型(包装类)
1.概述:所谓的包装类就是基本类型对应的类(引用类型),我们需要将基本类型转成包装类,从而让基本类型具有类的特性(说白了,就是将基本类型的数据转成包装类,就可以使用包装类中的方法来操作此数据)2.为啥要学包装类:a.将来有一些特定操作,调用方法需要传递包装类比如:ArrayList集合,add(Object obj),我们只能传递Object的子类对象,此时如果我们想传递一些基本类型的数据,Object作为一个引用类型不能直接接受基本类型,所以我们需要先将基本类型转成包装类传递到Object中3.将来我们需要让基本类型和包装类互相转换,为啥:a.基本类型为啥转成包装类:调用某个方法,方法参数传递Object类型,ArrayList集合,add(Object obj),我们只能传递Object的子类对象,此时如果我们想传递一些基本类型的数据,Object作为一个引用类型不能直接接受基本类型,所以我们需要先将基本类型转成包装类传递到Object中b.包装类为啥转成基本类型:包装类不能直接使用+ - * /做运算,所以需要先将包装类转成基本类型,才能直接使用+ - * /符号
基本类型 | 包装类 |
---|---|
byte | Byte |
short | Short |
int | Integer |
long | Long |
float | Float |
double | Double |
char | Character |
boolean | Boolean |
2.Inteaer的介绍以及应用
2.1Integer基本使用
1.概述:Integer是int的包装类
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);}
}
反编译后如下图:
笔试题:
public class Demo05Integer { public static void main(String[] args) {Integer i1 = 100;Integer i2 = 100;System.out.println(i1==i2);//trueInteger i3 = 127;Integer i4 = 127;System.out.println(i3==i4);//trueInteger 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 没有 Character 0-127 Boolean true和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的方法
位置 | 方法 | 说明 |
---|---|---|
Byte | static byte parseByte(String s) | 将字符串转成byte |
Short | static short parseShort(String s) | 将字符串转成short |
Integer | static int parseInt(String s) | 将字符串转成int |
Long | static long parseLong(String s) | 将字符串转成long |
Float | static float parseFloat(String s) | 将字符串转成float |
Double | static double parseDouble(String s) | 将字符串转成double |
Boolean | static 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;}@Overridepublic String toString() {return "User{" +"uid=" + uid +", name='" + name + '\'' +", age=" + age +'}';} }
多线程
1.多线程_线程和进程
进程(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方法的
创建线程的方式(重点)
1.第一种方式_extends Thread
步骤 :
①定义类 , 继承Thread类(Thread是一个线程类)
②重写Thread中的run方法 , 设置线程任务(自定义的线程需要干什么 , 将业务写进run())
③创建自定义线程类对象
④调用Thread中的start方法(使线程开始执行 ; Java虚拟机会调用线程中的 run 方法)
public class MyThread extends Thread{@Overridepublic 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{@Overridepublic 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{@Overridepublic 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() {@Overridepublic 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;@Overridepublic 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;@Overridepublic 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;@Overridepublic 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;@Overridepublic 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.死锁介绍(锁嵌套就有可能产生死锁)
指的是两个或者两个以上的线程在执行的过程中,由于竞争同步锁而产生的一种阻塞现象;如果没有外力的作用,他们将无法继续执行下去,这种情况就称之为死锁.
根据上图所示:线程T1正在持有R1锁,但是T1线程必须再拿到R2锁,才能继续执行
而线程T2正在持有R2锁,但是T2线程需要再拿到R1锁,才能继续执行
此时两个线程处于互相等待的状态,就是死锁,在程序中的死锁将出现在同步代码块的嵌套中
2.死锁的分析
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;}@Overridepublic 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();}
}