新手入学
1.1内部类概述
如果一个类存在的意义就是为指定的另一个类,可以把这个类放入另一个类的内部。 就是把类定义在类的内部的情况就可以形成内部类的形式。 A类中又定义了B类,B类就是内部类,B类可以当做A类的一个成员看待:
1.2 特点
1) 内部类可以直接访问外部类中的成员,包括私有成员
2) 外部类要访问内部类的成员,必须要建立内部类的对象
3) 在成员位置的内部类是成员内部类
4) 在局部位置的内部类是局部内部类
1.3 练习 : 内部类入门案例
创建包: cn.tedu.innerclass 创建类: TestInner1.java
package cn.tedu.innerclass;
/*本类用作测试内部类的入门案例*/
public class TestInner1 {
public static void main(String[] args) {
//3.创建内部类对象,使用内部类的资源
/*外部类名.内部类名 对象名 = 外部类对象.内部类对象*/
Outer.Inner oi = new Outer().new Inner();
oi.delete();
System.out.println(oi.sum);
//4.调用外部类的方法--这样是创建了一个外部类的匿名对象,只使用一次
new Outer().find();
}
}
//1.创建外部类 Outer
class Outer{
//1.1创建外部类的成员变量
String name;
private int age;
//1.2创建外部类的成员方法
public void find(){
System.out.println("Outer...find()");
//6.测试外部类如何使用内部类的资源
//System.out.println(sum);--不能直接使用内部类的属性
//delete();--不能直接调用内部类的方法
/*外部类如果想要使用内部类的资源,必须先创建内部类对象
* 通过内部类对象来调用内部类的资源*/
Inner in = new Inner();
System.out.println(in.sum);
in.delete();
}
//2.创建内部类Inner--类的特殊成员
/*根据内部类位置的不同,分为:成员内部类(类里方法外)、局部内部类(方法里)*/
class Inner{
//2.1定义内部类的成员变量
int sum = 10;
//2.2定义内部类的成员方法
public void delete(){
System.out.println("Inner...delete()");
//5.测试内部类是否可以使用外部类的资源
/*结论:内部类可以直接使用外部类的资源,私有成员也可以!*/
System.out.println(name);
System.out.println(age);
/*注意:此处测试完毕需要注释掉,否则来回调用
* 会抛出异常StackOverFlowException栈溢出异常*/
//find();
}
}
}
1.4练习 : 被private修饰
创建包: cn.tedu.innerclass 创建类: TestInner2.java
package cn.tedu.innerclass;
/**本类用来测试成员内部类被private修饰*/
public class TestInner2 {
public static void main(String[] args) {
/**怎么使用内部类Inner2的资源?*/
//4.创建内部类Inner2对象进行访问
//Outer2.Inner2 oi = new Outer2().new Inner2();
//oi.eat();
/**如果Inner2被private修饰,无法直接创建对象该怎么办?*/
//7.创建外部类对象,间接访问私有内部类资源
new Outer2().getInner2Eat();
}
}
//1.创建外部类Outer2
class Outer2{
//6.提供外部类公共的方法,在方法内部创建Inner2内部类对象,调用内部类方法
public void getInner2Eat() {
Inner2 in = new Inner2();//外部类可以访问内部类的私有成员
in.eat();
}
//2.1创建成员内部类Inner2
/**成员内部类的位置:类里方法外*/
//5.成员内部类,被private修饰私有化,无法被外界访问
private class Inner2{
//3.创建内部类的普通成员方法
public void eat() {
System.out.println("我是Inner2的eat()");
}
}
}
总结:
成员内部类被Private修饰以后,无法被外界直接创建创建对象使用
所以可以创建外部类对象,通过外部类对象间接访问内部类的资源
1.5 练习 : 被static修饰
创建包: cn.tedu.innerclass 创建类: TestInner3.java
package cn.tedu.innerclass;
/**本类用来测试成员内部类被static修饰*/
public class TestInner3 {
public static void main(String[] args) {
/**如何访问内部类的show()?*/
//4.创建内部类对象访问show()
//方式一:按照之前的方式,创建内部类对象调用show()
//Outer3.Inner3 oi = new Outer3().new Inner3();
//oi.show();
//方式二:创建匿名内部类对象访问show()
//new Outer3().new Inner3().show();
/**现象:当内部类被static修饰以后,new Outer3()报错*/
//6.用static修饰内部类以后,上面的创建语句报错,注释掉
//通过外部类的类名创建内部类对象
Outer3.Inner3 oi = new Outer3.Inner3();
oi.show();
//7.匿名的内部类对象调用show()
new Outer3.Inner3().show();
//9.访问静态内部类中的静态资源--链式加载
Outer3.Inner3.show2();
}
}
//1.创建外部类Outer3
class Outer3{
//2.创建成员内部类Inner3
//5.内部类被static修饰—并不常用!浪费内存!
static class Inner3{
//3.定义成员内部类中普通的成员方法
public void show() {
System.out.println("我是Inner3类的show()");
}
//8.定义成员内部类的静态成员方法
static public void show2() {
System.out.println("我是Inner3的show2()");
}
}
}
总结:
静态资源访问时不需要创建对象,可以通过类名直接访问
访问静态类中的静态资源可以通过”. . . ”链式加载的方式访问
1.6局部内部类
创建包: cn.tedu.innerclass 创建类: TestInner4.java
package cn.tedu.innerclass;
/**本类用来测试局部内部类*/
public class TestInner4 {
public static void main(String[] args) {
/**如何使用内部类的资源呢?
* 注意:直接调用外部类的show()是无法触发内部类功能的
* 需要再外部类中创建内部类对象并且进行调用,才能触发内部类的功能
* */
//5.创建外部类对象调用show()
//7.当在外部类show()中创建局部内部类对象并且进行功能调用后,内部类的功能才能被调用
new Outer4().show();
}
}
//1.创建外部类Outer4
class Outer4{
//2.创建外部类的成员方法
public void show() {
//3.创建局部内部类Inner4—不太常用!!!
/**位置:局部内部类的位置在方法里*/
class Inner4{
//4.创建局部内部类的普通属性与方法
String name;
int age;
public void eat() {
System.out.println("我是Inner4的eat()");
}
}
/**如何使用局部内部类的资源?*/
//6.在show()里创建内部类对象
Inner4 in = new Inner4();
in.eat();
System.out.println(in.name);
System.out.println(in.age);
}
}
1.7 匿名内部类
创建包: cn.tedu.innerclass 创建类: TestInner5.java
package cn.tedu.innerclass;
/*本类用于测试匿名内部类,没有名字,通常结合着匿名对象一起使用*/
public class TestInner5 {
public static void main(String[] args) {
/*接口可以创建对象吗?不可以!!!*/
//new Inner1();//接口不能创建对象
//3.new Inner1()匿名对象
//以前我们创建接口,接口不能实例化
//所以我们创建接口实现类 +重写接口中的抽象方法 + 创建实现类对象 + 调用方法
/*就相当于创建了一个接口的实现类 + 重写接口中的所有抽象方法*/
new Inner1() {//3.1接口的实现类
总结: 匿名内部类属于局部内部类,而且是没有名字的局部内部类,通常和匿名对象一起使用
2 反射
2.1反射需要用到的API
2.1.1 获取字节码对象
Class.forName(“类的全路径”);
类名.class
对象.getClass();
2.1.2 常用方法
获取包名 类名
clazz.getPackage().getName()//包名 clazz.getSimpleName()//类名 clazz.getName()//完整类名
获取成员变量定义信息 getFields()//获取所有公开的成员变量,包括继承变量 getDeclaredFields()//获取本类定义的成员变量,包括私有,但不包括继承的变量 getField(变量名) getDeclaredField(变量名)
获取构造方法定义信息 getConstructor(参数类型列表)//获取公开的构造方法 getConstructors()//获取所有的公开的构造方法 getDeclaredConstructors()//获取所有的构造方法,包括私有 getDeclaredConstructor(int.class,String.class)
获取方法定义信息 getMethods()//获取所有可见的方法,包括继承的方法 getMethod(方法名,参数类型列表) getDeclaredMethods()//获取本类定义的的方法,包括私有,不包括继承的方法 getDeclaredMethod(方法名,int.class,String.class)
反射新建实例 clazz.newInstance();//执行无参构造创建对象 clazz.newInstance(666,”海绵宝宝”);//执行含参构造创建对象 clazz.getConstructor(int.class,String.class)//获取构造方法
反射调用成员变量 clazz.getDeclaredField(变量名);//获取变量 clazz.setAccessible(true);//使私有成员允许访问 f.set(实例,值);//为指定实例的变量赋值,静态变量,第一参数给null f.get(实例);//访问指定实例变量的值,静态变量,第一参数给null
反射调用成员方法 Method m = Clazz.getDeclaredMethod(方法名,参数类型列表); m.setAccessible(true);//使私有方法允许被调用 m.invoke(实例,参数数据);//让指定实例来执行该方法
2.2反射的应用
2.2.1 创建 : 测试物料类
创建包: cn.tedu. reflection 创建类: Student.java
-
package cn.tedu.reflection;
/*本类用作反射测试的物料类
* 反射的前提:获取字节码对象,并且获取的不是我们自己写的资源
* 假装这个Student是别人写的类*/
public class Student {
//1.定义成员变量
private String name;
int age;
//1.2提供私有属性name的get与set方法
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//2.定义构造方法
public Student() { }
public Student(String name, int age) {
this.name = name;
this.age = age;
}
//3.定义成员方法
public void eat(int n){
System.out.println("今天我想吃"+n+"根冰激凌");
}
//4.添加本类重写的toString()
//为的是打印对象时,不使用Object中的默认实现打印地址值,而是对象的类型与属性值
2.2.2练习 : 获取类对象
创建包: cn.tedu. reflection 创建类: TestReflect.java
/**本类用来测试反射*/
public class TestReflect {
//1.创建入口函数main()--不用
/**单元测试方法:是java测试的最小单位,使用灵活,推荐使用
语法要求: @Test + void + 没有参数 + public
注意使用时需要导包:Add JUnit 4 library to the build path:import org.junit.Test;
单元测试方法执行方式:选中方法名-->右键运行(Run As-->JUnit Test)-->出现小绿条说明执行成功
*/
//2.通过单元测试来测试如何获取类对象
2.2.3 练习 : 类获取构造方法
/**本类用来测试反射*/
public class TestReflect {
/*2.本方法用于练习引用类型数组的定义与遍历*/
@Test
public void getStuden(){
//1.创建Student类的3个对象
Student s1 = new Student("张三",18);
Student s2 = new Student("李四",18);
Student s3 = new Student("王五",18);
//2.创建student类型的数组,存放这3个对象
Student[] s = {s1,s2,s3};
//3.创建高效for遍历Student[],每轮遍历到的都是一个个的Student类型的对象
for(Student ss:s){
//System.out.println(ss);
//通过本轮遍历到的Student对象,获取对象的name属性值,注意这个需要在Student类中提供getName()
System.out.println(ss.getName());
}
}
/*3.本方法用于练习获取指定类Student的构造方法*/
@Test
public void getConstruct(){
//1.获取字节码对象Class对象
Class<?> clazz = Student.class;
//2.获取构造方法们
/*本方法用于从字节码对象中获取Student类的多个构造函数对象
* 所以这个方法的返回值类型是一个数组,数组中存着的是Student类的多个构造函数对象*/
Constructor<?>[] cs = clazz.getConstructors();
//3.遍历数组获取每一个构造函数对象
//使用高效for循环遍历:
//for(每轮遍历到的元素的类型 每轮遍历到的元素的名字 : 要遍历的数组/集合){循环体}
for(Constructor c : cs){
//4.从本轮遍历到的构造函数对象中,获取构造方法相关的信息
System.out.println(c.getName());//获取构造方法的方法名
Class[] pt = c.getParameterTypes();//获取构造方法的参数类型
System.out.println(Arrays.toString(pt));//打印方法参数类型数组的具体元素
}
}
2.2.4 练习 : 获取成员方法
/**本类用来测试反射*/
public class TestReflect {
//4.通过单元测试来测试获取成员方法
2.2.5 练习 : 获取成员变量
/**本类用来测试反射*/
public class TestReflect {
//5.通过单元测试来测试获取成员变量
2.2.6 练习 : 创建对象
-
-
/**本类用来测试反射*/
public class TestReflect {
/*6.本方法用于练习通过反射创建指定类Student的对象*/
/*方式一:通过字节码对象之间调用newInstance(),触发目标类的无参构造来创建对象
* 方式二:先获取指定参数类型的构造函数对象
* 再通过获取到的这个构造函数对象调用newInsatnce(参数列表)
* 来创建Student类对象*/
@Test
public void getObject() throws Exception {
//1.获取字节码对象
Class<?> clazz = Student.class;
//2.通过反射创建对象
Object o = clazz.newInstance();//这样已经创建对象了
System.out.println(o);
/*以上我们newInstance()触发的是Student类中的无参构造创建对象
* 所以仅仅能创建对象,但不能给对象的属性赋值
* 所以,如果需要触发其他的构造函数来创建对象的话
* 需要先获取指定的构造函数对象*/
//3.想尝试通过其他的构造函数来创建对象
//3.1想用其他构造,先得获取,怎么获取?指定参数列表来获取
/*本方法用于获取指定参数列表的构造函数,获取的是一个构造函数对象
* 注意,这个方法的参数是目标类Student中对应构造函数的参数类型
* 而且参入的是字节码对象,不是普通的类型*/
Constructor<?> c = clazz.getConstructor(String.class,int.class);
//3.2通过刚刚获取到的构造函数对象来帮我们创建Student类的对象
Object o2 = c.newInstance("海绵宝宝", 18);
/*向下转型:之前转成父类类型的子类对象
* 如果想要使用子类的特有功能,需要重新转回成子类类型
* 因为父类对象无法使用子类的特有功能*/
//4.将多态对象转回子类对象--向下转型
Student s = (Student) o2;
System.out.println(s.getName());
System.out.println(s.age);
s.eat(999);
}
}
-
2.3暴力反射
指可以将程序中的私有的属性或者方法通过反射技术,暴力的获取到资源。需要使用的常见方法如
2.3.1创建 : 测试物料类
创建包: cn.tedu. reflection 创建类: Person.java
/**本类用来测试暴力反射*/
public class Person {
//1.提供私有属性
private String name;
private int age;
//2.提供私有方法
private void save(int m,String n) {
System.out.println("save().."+m+n);
}
private void update() {
System.out.println("update()..");
}
}
2.3.2 练习 : 创建测试类
创建包: cn.tedu. reflection 创建类: TestReflect2.java
/*本类用于测试暴力反射*/
public class TestReflect2 {
/*单元测试方法的格式:@Test + public + void + 无参 */
/*1.单元测试1:暴力反射获取和设置私有属性*/
3.设计模式
-
对本类构造方法私有化,防止外部调用构造方法创建对象
-
创建全局唯一的对象,也做私有化处理
-
通过自定义的公共方法将创建好的对象返回(类似封装属性后的getXxx() )
3.1单例设计模式1-饿汉式实现方式
创建包: cn.tedu.design 创建类: Singleton1.java
/*本类用于实现单例设计模式实现方案1:饿汉式*/
public class Singleton1 {
public static void main(String[] args) {
//5.在main()中,不通过对象,直接通过类名,调用静态方法
MySingle single1 = MySingle.getSingle();
MySingle single2 = MySingle.getSingle();
//6.用==检验是否是同一个对象
System.out.println(single1 == single2);//true
System.out.println(single1);
System.out.println(single2);
}
}
//0.创建自己的单例程序
class MySingle{
//1.提供构造方法,并将构造方法私有化
/*1.构造方法私有化的目的:为了防止外界随意创建本类对象*/
private MySingle(){ }
//2.创建本类对象,并将对象也私有化
//4.2由于静态资源只能调用静态资源,所以single对象也需要设置成静态
private static MySingle single = new MySingle();
//3.提供公共的访问方式,返回创建好的对象
//4.1为了不通过对象,直接调用本方法,需要将本方法设置为静态
public static MySingle getSingle(){
return single;
}
}
3.2单例设计模式2-懒汉式实现方式
创建包: cn.tedu.design 创建类: Singleton2.java
/*本类用于实现单例设计模式优化实现方案2:懒汉式
* 关于单例设计模式的两种实现方式:
* 1.饿汉式:不管你用不用这个类的对象,都会直接先创建一个
* 2.懒汉式:先不给创建这个类的对象,等你需要的时候再创建--延迟加载的思想
* 延迟加载的思想:是指不会在第一时间就把对象创建好占用内存
* 而是什么时候用到,什么时候再去创建对象
* 3.线程安全问题:由于我们存在唯一的对象single2,并且多条语句都操作了这个变量
* 如果将程序放到多线程的环境下,就容易出现数据安全的问题,所以解决方案:
* 1) 将3条语句都使用同步代码块包裹,保证同步排队的效果
* 2) 由于getSingle2()只有这3条语句,所以也可以将本方法设置为同步方法*/
public class Singleton2 {
public static void main(String[] args) {
//5.调用方法查看结果
MySingle2 single1 = MySingle2.getSingle2();
MySingle2 single2 = MySingle2.getSingle2();
System.out.println(single1 == single2);
System.out.println(single1);
System.out.println(single2);
}
}
//0.创建自己的单例程序
class MySingle2{
//6.2创建一个静态的唯一的锁对象
static Object o = new Object();
//1.私有化本类的构造方法
private MySingle2(){ }
//2.创建的是本类对象的引用类型变量,用来保存对象的地址值,默认值是null
private static MySingle2 single2 ;
//3.提供公共的get方法
synchronized public static MySingle2 getSingle2(){
//4.判断之前是否创建过对象,之前创建过就直接走return
//之前如果没有创建过,才走if,创建对象并将对象返回
//6.有共享数据+多条语句操作数据,所以尽量提前处理,避免多线程数据安全隐患
//6.1 解决方案1:加同步代码块
//6.2 解决方案2:将本方法getSingle2()设置为同步方法
//因为这个方法里所有的语句都需要同步
synchronized (o) {//静态方法中使用的锁对象也得是静态的
if (single2 == null) {//single2还是默认值,说明之前没有创建过对象
single2 = new MySingle2();//没创建过才创建,并赋值给single2
}
return single2;
}
}
}
4进程与线程
4.1进程的概念
进程就是正在运行的程序,它会占用对应的内存区域,由CPU进行执行与计算。
4.2 进程的特点
独立性
进程是系统中独立存在的实体,它可以拥有自己独立的资源,每个进程都拥有自己私有的地址空间,在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间
动态性
进程与程序的区别在于,程序只是一个静态的指令集合,而进程是一个正在系统中活动的指令集合,程序加入了时间的概念以后,称为进程,具有自己的生命周期和各种不同的状态,这些概念都是程序所不具备的.
并发性
多个进程可以在单个处理器CPU上并发执行,多个进程之间不会互相影响.
4.3多线程的特性
随机性
一个CPU【单核】只能执行一个进程中的一个线程。
串行与并行
串行是指同一时刻一个CPU只能处理一件事,类似于单车道 并行是指同一时刻多个CPU可以处理多件事,类似于多车道
线程的状态
-
就绪(可运行)状态:线程已经准备好运行,只要获得CPU,就可立即执行
-
执行(运行)状态:线程已经获得CPU,其程序正在运行的状态
-
阻塞状态:正在运行的线程由于某些事件(I/O请求等)暂时无法执行的状态,即线程执行阻塞
4.4多线程代码创建方式1:继承Thread
4.4.1概述
Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例 启动线程的唯一方法就是通过Thread类的start()实例方法 start()方法是一native方法,它将通知底层操作系统,.最终由操作系统启动一个新线程,操作系统将执行run()
4.4.2 测试多线程的创建方式1
创建包: cn.tedu.thread 创建类: TestThread1.java
/*本类用于多线程编程实现方案一:继承Thread类来完成*/
public class TestThread1 {
public static void main(String[] args) {
//4.创建线程对象进行测试
/*4.new对应的是线程的新建状态
* 5.要想模拟多线程,至少得启动2个线程,如果只启动1个,是单线程程序*/
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
MyThread t4 = new MyThread();
/*6.这个run()如果直接这样调用,是没有多线程抢占执行的效果的
* 只是把这两句话看作普通方法的调用,谁先写,就先执行谁*/
//t1.run();
//t2.run();
/*7.start()对应的状态就是就绪状态,会把刚刚新建好的线程加入到就绪队列之中
* 至于什么时候执行,就是多线程执行的效果,需要等待OS选中分配CPU
* 8.执行的时候start()底层会自动调用我们重写的run()种的业务
* 9.线程的执行具有随机性,也就是说t1-t4具体怎么执行
* 取决于CPU的调度时间片的分配,我们是决定不了的*/
t1.start();//以多线程的方式启动线程1,将当前线程变为就绪状态
t2.start();//以多线程的方式启动线程2,将当前线程变为就绪状态
t3.start();//以多线程的方式启动线程3,将当前线程变为就绪状态
t4.start();//以多线程的方式启动线程4,将当前线程变为就绪状态
}
}
//1.自定义一个多线程类,然后让这个类继承Thread
class MyThread extends Thread{
/*1.多线程编程实现的方案1:通过继承Thread类并重写run()来完成的 */
//2.重写run(),run()里是我们自己的业务
4.4.3多线程代码创建方式2:实现Runnable接口
创建包: cn.tedu.thread 创建类: Thread2.java
/*本类用于多线程编程实现方案二:实现Runnable接口来完成*/
public class TestThread2 {
public static void main(String[] args) {
//5.创建自定义类的对象--目标业务类对象
MyRunnable target = new MyRunnable();
//6.如何启动线程?自己没有,需要与Thread建立关系
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义多线程类
class MyRunnable implements Runnable{
//2.添加父接口中的抽象方法run(),里面是自己的业务
5售票案例
需求:设计4个售票窗口,总计售票100张。用多线程的程序设计并写出代码
5.1方案1:继承Thread
创建包: cn.tedu.tickets 创建类: TestThread.java
/*需求:设计多线程编程模型,4个窗口共计售票100张
* 本方案使用多线程编程方案1,继承Thread类的方式来完成*/
public class TestThread {
public static void main(String[] args) {
//5.创建多个线程对象
TicketThread t1 = new TicketThread();
TicketThread t2 = new TicketThread();
TicketThread t3 = new TicketThread();
TicketThread t4 = new TicketThread();
//6.以多线程的方式启动
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义多线程售票类,继承Thread
class TicketThread extends Thread{
//3.定义变量,保存要售卖的票数
/*问题:4个线程对象共计售票400张,原因是创建了4次对象,各自操作各自的成员变量
* 解决:让所有对象共享同一个数据,票数需要设置为静态*/
static int tickets = 100;
//2.重写父类的run(),里面是我们的业务
@Override
public void run() {
//4.1循环卖票
while(true){
try {
//7.让每个线程经历休眠,增加线程状态切换的频率与出错的概率
//问题1:产生了重卖的现象:同一张票卖了多个人
//问题2:产生了超卖的现象:超出了规定的票数100,出现了0 -1 -2这样的票
Thread.sleep(10);//让当前线程休眠10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.2打印当前正在卖票的线程名称,并且票数-1
System.out.println(getName()+"="+tickets--);
//4.3做判断,如果没有票了,就退出死循环
if(tickets <= 0) break;//注意,死循环一定要设置出口
}
}
}
5.2方案2:实现Runnable
创建包: cn.tedu.tickets 创建类: TestRunnable.java
/*需求:设计多线程编程模型,4个窗口共计售票100张
* 本方案使用多线程编程方案2,实现Runnable接口的方式来完成*/
public class TestRunnable {
public static void main(String[] args) {
//5.创建Runnable接口的实现类对象,作为目标业务对象
TicketRunnable target = new TicketRunnable();
//6.创建多个Thread类线程对象,并将target业务对象交给多个线程对象来处理
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
//7.以多线程的方式启动多个线程对象
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义多线程类实现Runnable接口
class TicketRunnable implements Runnable{
//3.定义一个成员变量,用来保存票数100
/*由于自定义类对象只创建了一次,所以票数被所有线程对象Thread类的对象共享*/
int tickets = 100;
//2.添加接口中未实现的方法,方法里是我们的业务
@Override
public void run() {
//4.1循环卖票
while(true){
//8.让线程休眠10ms,增加线程状态切换的概率和出错的概率
try {
Thread.sleep(10);//让当前线程休眠10ms
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.2打印当前正在售票的线程名称 & 票数-1
System.out.println(Thread.currentThread().getName()+"="+tickets--);
//4.3设置死循环的出口,没票了就停止卖票
if(tickets <=0 ) break;
}
}
}
6同步锁
同步:体现了排队的效果
异步:体现了多线程抢占资源的效果,线程间互相不等待,互相抢占资源。
6.1同步效果的使用有两个前提:
-
前提1:同步需要两个或者两个以上的线程(单线程无需考虑多线程安全问题)
-
前提2:多个线程间必须使用同一个锁(我上锁后其他人也能看到这个锁,不然我的锁锁不住其他人,就没有了上锁的效果)
6.2练习-改造售票案例
创建包: cn.tedu.tickets 创建类:TestRunnableV2.java
/*本类用于改造多线程售票案例,解决数据安全问题*/
public class TestRunnableV2 {
public static void main(String[] args) {
//5.创建目标业务类对象
TicketR2 target = new TicketR2();
//6.创建线程对象
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
//7.以多线程的方式运行
t1.start();
t2.start();
t3.start();
t4.start();
}
}
/*1.多线程中出现数据安全问题的原因:多线程程序+共享数据+多条语句操作共享数据*/
/*2.同步锁:相当于给容易出现问题的代码加了一把锁,包裹了所有可能会出现数据安全问题的代码
* 加锁之后,就有了同步(排队)的效果,但是加锁的话,需要考虑:
* 锁的范围:不能太大,太大,干啥都得排队,也不能太小,太小,锁不住,还是会有安全隐患*/
//1.创建自定义多线程类
class TicketR2 implements Runnable {
//3.定义成员变量,保存票数
int tickets = 100;
//创建锁对象
Object o = new Object();
//2.实现接口中未实现的方法,run()中放着的是我们的业务
@Override
public void run() {
//4.通过循环结构完成业务
while (true) {
/*3.同步代码块:synchronized(锁对象){会出现安全隐患的所有代码}
* 同步代码块在同一时刻,同一资源只会被一个线程独享*/
/*这种写法不对,相当于每个线程进来的时候都会new一个锁对象,线程间使用的并不是同一把锁*/
//synchronized (new Object()){
//修改同步代码块的锁对象为成员变量o,因为锁对象必须唯一
synchronized (o) {//同步代码块解决的是重卖的问题
//如果票数>0就卖票
if (tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//4.1打印当前正在售票的线程名以及票数-1
System.out.println(Thread.currentThread().getName() + "=" + tickets--);
}
//4.2退出死循环--没票的时候就结束
if (tickets <= 0) break;
}
}
}
}
6.3练习-改造售票案例
创建包: cn.tedu.tickets 创建类:TestThreadV2.java
/*本类用于改造多线程售票案例,解决数据安全问题*/
public class TestThreadV2 {
public static void main(String[] args) {
//5.创建多个线程对象并以多线程的方式运行
TickectT2 t1 = new TickectT2();
TickectT2 t2 = new TickectT2();
TickectT2 t3 = new TickectT2();
TickectT2 t4 = new TickectT2();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
//1.自定义多线程类
class TickectT2 extends Thread {
//3.新增成员变量用来保存票数
static int tickets = 100;
//static Object o = new Object();
//2.添加重写的run()来完成业务
6.4线程创建的其他方式
6.4.1ExecutorService/Executors
ExecutorService:用来存储线程的池子,把新建线程/启动线程/关闭线程的任务都交给池来管理
execute(Runnable任务对象) 把任务丢到线程池
Executors 辅助创建线程池的工具类
-
newFixedThreadPool(int nThreads) 最多n个线程的线程池
-
newCachedThreadPool() 足够多的线程,使任务不必等待
-
newSingleThreadExecutor() 只有一个线程的线程池
6.4.2 练习:线程的其他创建方式
创建包: cn.tedu.tickets 创建类: TestThreadPool.java
/*本类用于测试线程池*/
public class TestThreadPool {
public static void main(String[] args) {
//5.创建接口实现类TicketR3类的对象作为目标业务对象
TicketR3 target = new TicketR3();
/*Executors是用来辅助创建线程池的工具类对象
* 常用方法是newFixedThreadPool(int)这个方法可以创建指定数目的线程池对象
* 创建出来的线程池对象是ExecutorService:用来存储线程的池子,负责:新建/启动/关闭线程*/
//6.使用Executors工具创建一个最多有5个线程的线程池对象ExecutorService池对象
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
/*execute()让线程池中的线程来执行业务,每次调用都会将一个线程加入到就绪队列*/
pool.execute(target);/*本方法的参数就是你要执行的业务,也就是目标业务类对象*/
}
}
}
//同步锁问题解决方案笔记:1.4.1从26行复制到58行,TicketR2改成TicketR3
//1.创建自定义多线程类
class TicketR3 implements Runnable {
//3.定义成员变量,保存票数
int tickets = 100;
//创建锁对象
Object o = new Object();
//2.实现接口中未实现的方法,run()中放着的是我们的业务
6.5尝试用读写锁改造售票案例
ReentrantReadWriteLock 读写锁(乐观锁,无罪假设) 因此引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),我们下面称它为读写锁。
/**
* 本类用于改造售票案例,使用可重入读写锁
* ReentrantReadWriteLock
* */
public class TestSaleTicketsV3 {
public static void main(String[] args) {
SaleTicketsV3 target = new SaleTicketsV3();
Thread t1 = new Thread(target);
Thread t2 = new Thread(target);
Thread t3 = new Thread(target);
Thread t4 = new Thread(target);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class SaleTicketsV3 implements Runnable{
static int tickets = 100;
//1.定义可重入读写锁对象,静态保证全局唯一
static ReentrantReadWriteLock lock = new ReentrantReadWriteLock(true);
@Override
public void run() {
while(true) {
//2.在操作共享资源前上锁
lock.writeLock().lock();
try {
if(tickets > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "=" + tickets--);
}
if(tickets <= 0) break;
} catch (Exception e) {
e.printStackTrace();
}finally {
//3.finally{}中释放锁,注意一定要手动释放,防止死锁,否则就独占报错了
lock.writeLock().unlock();
}
}
}
}
7流
字节流:针对二进制文件
InputStream FileInputStream BufferedInputStream ObjectInputStream
OutputStream FileOutputStream BufferedOutputStream ObjectOutputStream
字符流:针对文本文件
Reader FileReader BufferedReader InputStreamReader
Writer FileWriter BufferedWriter OutputStreamWriter PrintWriter一行行写出
7.1字节流读取案例
创建包: cn.tedu.file 创建类: TestIn.java
/*本类用于练习字节输入流*/
public class TestIn {
public static void main(String[] args) {
//method();//字节流的读取
method2();//高效字节流的读取
}
//本方法用于测试高效字节流的读取
private static void method2() {
//定义一个在本方法中都生效的局部变量in,注意手动初始化,值为null
InputStream in = null;
try {
//1.创建高效字节输入流对象
// InputStream in = new BufferedInputStream(
// new FileInputStream(new File("E:\\ready\\1.txt")));
in = new BufferedInputStream
(new FileInputStream("E:\\ready\\1.txt"));
//2.使用流进行读取
int b;
while ((b= in.read())!= -1){
System.out.println(b);
}
} catch (Exception e) {
e.printStackTrace();
}finally {//关流操作写在finally{}中
//3.流用完以后一定要关闭!!!
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//本方法用于测试字节流的读取
private static void method() {
//创建一个在本方法都生效的局部变量注意手动初始化
InputStream in = null;
try {
//1.创建字节输入流对象用于读取
//InputStream in = new InputStream();//报错原因:抽象类不可实例化
//InputStream in = new FileInputStream(new File("E:\\ready\\1.txt"));
in = new FileInputStream("E:\\ready\\1.txt");
//2.开始读取
/*read()每次调用都会读取一个字节,如果读到了数据的末尾,返回-1*/
// System.out.println(in.read());
// System.out.println(in.read());
// System.out.println(in.read());
// System.out.println(in.read());
//需求:需要循环读取文件中的所有内容,直至读完
//定义变量,记录读到的数据
int b;
while((b=in.read())!= -1){
System.out.println(b);
}
} catch (Exception e) {
e.printStackTrace();//打印错误信息
/*try-catch结构中的第三个部分:finally{}
* 这部分不论是否捕获到异常,是一定会被执行到的代码,常用于关流*/
}finally {
try {
//3.释放资源,流资源用完必须释放!!!
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.2字符流读取案例
创建包: cn.tedu.file 创建类: TestIn2.java
/*本类用于测试字符流的读取*/
public class TestIn2 {
public static void main(String[] args) {
//method();//测试普通字符输入流
method2();//测试高效字符输入流
}
//创建一个用于测试高效字符输入流的方法
private static void method2() {
//1.定义一个在本方法都生效的局部变量,手动初始化值null
Reader in=null;
try{
//1.创建高效字符读取流对象
//in = new BufferedReader(new FileReader(new File("E:\\ready\\1.txt")));
in = new BufferedReader(new FileReader("E:\\ready\\1.txt"));
//2.使用流对象
int b;
while((b=in.read())!=-1){
System.out.println(b);
}
}catch (Exception e){
e.printStackTrace();
}finally {
//3.关闭流对象
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//创建一个用于测试普通字符输入流的方法
private static void method() {
//1.1创建一个在本方法中都生效的局部变量,注意初始化值null
Reader in = null;
try {
//1.2创建字符输入流对象,注意需要捕获异常
//Reader in = new Reader();//报错原因:抽象父级不可实例化
//in = new FileReader(new File("E:\\ready\\1.txt"));
in = new FileReader("E:\\ready\\1.txt");
//2.使用流对象
//System.out.println(in.read());
//需求:循环读取文件中的所有内容,只要不是-1,就说明还有数据,继续读取
//3.1定义变量,记录读取到的数据
int b;
while((b = in.read())!= -1){
System.out.println(b);
}
} catch (Exception e) {
e.printStackTrace();
} finally {//3.关流
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.3字节输出流测试:
创建包: cn.tedu.file 创建类: TestOut.java
/*本类用于测试字节输出流*/
public class TestOut {
public static void main(String[] args) {
method();//用于测试普通字节输出流
//method2();//用于测试高效字节输出流
}
//创建一个用于测试高效字节输出流的方法
private static void method2() {
//1.创建一个在本方法都生效的局部变量,注意手动初始化
OutputStream out = null;
try{
//2.创建高效字节输出流对象
// out = new BufferedOutputStream(new FileOutputStream(new File("E:\\ready\\2.txt")));
out = new BufferedOutputStream(new FileOutputStream("E:\\ready\\2.txt"));
//3.使用流对象--进行写出操作
out.write(97);
out.write(97);
out.write(97);
}catch (Exception e){
e.printStackTrace();
}finally {//关流操作要放在finally{}中
try {
//4.关流
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//创建一个用于测试普通字节输出流的方法
private static void method() {
//1.创建一个在本方法中都生效的局部变量,注意手动初始化null
OutputStream out = null;
//2.创建try-catch-finally结构,因为IO操作可能会产生异常
try{
//3.创建普通字节输出流对象
//out = new FileOutputStream(new File("E:\\ready\\2.txt"));
//out = new FileOutputStream("E:\\ready\\2.txt");
out = new FileOutputStream("E:\\ready\\2.txt",true);
//4.使用流对象--进行写出操作
out.write(99);//对应ASCII码表中的a
out.write(99);//对应ASCII码表中的b
out.write(99);//对应ASCII码表中的c
}catch (Exception e){
e.printStackTrace();
}finally {//如果想要代码一定会执行,需要写在finally中
try {
//5.关流操作
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.4字符输出流测试
创建包: cn.tedu.file 创建类: TestOut2.java
/*本类用于测试字符输出流*/
public class TestOut2 {
public static void main(String[] args) {
//method();//用于测试普通字符输出流
method2();//用于测试高效字符输出流
}
//创建一个用于测试高效字符输出流的方法
private static void method2() {
//1.创建一个在本方法都生效的局部变量,值为null,注意手动初始化!!!
Writer out = null;
//2.由于程序可能会抛出异常,所以需要写一个try-catch-finally结构
try{//存放可能会抛出异常的代码
//3.创建普通字符输出流对象
//out = new BufferedWriter(new FileWriter(new File("E:\\ready\\2.txt")));
//out = new BufferedWriter(new FileWriter("E:\\ready\\2.txt"));
out = new BufferedWriter(new FileWriter("E:\\ready\\2.txt",true));
//4.使用流对象
out.write(100);
out.write(100);
out.write(100);
out.write(100);
out.write(100);
}catch (Exception e){//匹配并捕获异常
e.printStackTrace();//如果捕获到异常就打印错误信息
}finally {//一定会被执行到的代码块,常用于关流
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
//创建一个用于测试普通字符输出流的方法
private static void method() {
//1.创建一个在本方法都生效的局部变量,值为null,注意手动初始化!!!
Writer out = null;
//2.由于程序可能会抛出异常,所以需要写一个try-catch-finally结构
try{//存放可能会抛出异常的代码
//3.创建普通字符输出流对象
//out = new FileWriter(new File("E:\\ready\\2.txt"));
//out = new FileWriter("E:\\ready\\2.txt");
out = new FileWriter("E:\\ready\\2.txt",true);
//4.使用流对象
out.write(98);
out.write(98);
out.write(98);
out.write(98);
}catch (Exception e){//匹配并捕获异常
e.printStackTrace();//如果捕获到异常就打印错误信息
}finally {//一定会被执行到的代码块,常用于关流
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
7.5拓展
通过学习以上的几种流,我们也可以拓展尝试做下文件的复制: 创建包: cn.tedu.file 创建类: TestCopyFile.java
/*本类用于练习文件复制综合案例*/
public class TestCopyFile {
public static void main(String[] args) {
//1.提示并接收用户输入的源文件路径和目标位置
System.out.println("请您输入源文件路径:");
String f = new Scanner(System.in).nextLine();
System.out.println("请您输入目标位置:");
String t = new Scanner(System.in).nextLine();
//2.调用方法完成文件的复制
//ZFCopy(f,t);//用字符流完成复制案例
ZJCopy(f,t);//用字节流完成复制案例
}
private static void ZJCopy(String f,String t) {
//1.定义在整个方法中都生效的局部变量,手动初始化值为null
InputStream in = null;
OutputStream out = null;
//2.由于代码可能会发生异常,所以需要try-catch-finally结构
try{
//3.1创建高效/缓冲字节输入流,参数是f
in = new BufferedInputStream(new FileInputStream(f));
//3.2创建高效/缓冲字节输出流,参数是t
out = new BufferedOutputStream(new FileOutputStream(t));
//4.创建好流对象以后就可以通过流对象完成业务了
//4.1定义变量保存读到的数据
int b;
//4.2使用循环完成复制操作
while((b=in.read())!=-1){
out.write(b);
}
System.out.println("恭喜您~文件复制成功~");
}catch(Exception e){
System.out.println("很抱歉~文件复制失败~");
e.printStackTrace();
}finally {
//5.关流
try {
out.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
in.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private static void ZFCopy(String f,String t) {
//1.定义在整个方法中都生效的局部变量,手动初始化值为null
Reader in = null;
Writer out = null;
//2.由于代码可能会发生异常,所以需要try-catch-finally结构
try{
//3.1创建高效/缓冲字符输入流,传入的参数是源文件的路径
in = new BufferedReader(new FileReader(f));
//3.2创建高效/缓冲字符输出流,传入的参数是目标文件的路径
out = new BufferedWriter(new FileWriter(t));
//4.拿到流对象以后,就可以使用流对象来完成业务了
//4.1定义变量用来记录读到的数据
int b;
//4.2循环读取目标文件,直到返回值为-1,没有数据时结束循环
while((b=in.read())!=-1){
out.write(b);/*将本次循环从源文件读到的内容写出到目标文件中*/
}
System.out.println(