递归方法:一个方法体内调用它自身。
1.2如何理解递归方法?方法递归包含了一种隐式的循环,它会重复执行某段代码,但这种重复执行无须循环控制。
1.3举例:递归一定要向已知方向递归,否则这种递归就变成了无穷递归,类似于死循环。
// 例1:计算1-n之间所有自然数的和
public int getSum(int n) {// 3
if (n == 1) {
return 1;
} else {
return n + getSum(n - 1);
}
}
// 例2:计算1-n之间所有自然数的乘积:n!
public int getSum1(int n) {
if (n == 1) {
return 1;
} else {
return n * getSum1(n - 1);
}
}
//例3:已知一个数列:f(0) = 1,f(1) = 4,f(n+2)=2*f(n+1) + f(n),
//其中n是大于0的整数,求f(10)的值。
public int f(int n){
if(n == 0){
return 1;
}else if(n == 1){
return 4;
}else{
return 2*f(n - 1) + f(n - 2);
}
}
@Test
public void testRecursion(){
//例1
System.out.println(getSum(3)); //6
//例2
System.out.println(getSum1(3)); //6
//例3
System.out.println(f(10)); //10497
}
//例3笔记
//f(10)= 2 * f(10-1) + f(10-2);//继续调用f()传进去10-1
//f(9)= 2 * f(9-1) + f(9-2);//继续调用f()传进去9-1
//~~~
//f(1) return 4
//f(0) return 1
//f(3) return 2 * 4 + 1; //9
//f(4) return 2 * 9 + 4; //22
//~~~
//例4:斐波那契数列
//例5:汉诺塔问题
//例6:快排
2.单例模式
2.1 饿汉模式
//饿汉模式1:
public class Blank {
private Blank (){}
private static Blank blank = new Blank ();
private static Blank getInstance(Blank blank ){
return blank ;
}
}
//饿汉模式2:使用静态代码块
public class Blank {
private Blank (){}
private static Blank blank = null;
static{
blank = new Blank ();
}
private static Blank getInstance(){
return blank ;
}
}
2.2懒汉式
public class Order{
private Order(){}
private static Order order = null;
public static Order getInstance(){
if(order){
order = new Order();
}
return order;
}
}
6.4 两种方式的对比:
饿汉式:
- 坏处:对象加载时间过长。
- 好处:饿汉式是线程安全的
懒汉式:
-
好处:延迟对象的创建。
-
目前的写法坏处:线程不安全。—>使用多线程可以解决
1.代码块的作用:用来初始化类、对象的信息
2.分类:代码块要是使用修饰符,只能使用static
内部可以输出语句
随着类的加载而执行,而且只执行一次
作用:初始化类的信息
如果一个类中定义了多个静态代码块,则按照声明的先后顺序执行
静态代码块的执行要优先于非静态代码块的执行
静态代码块内只能调用静态的属性、静态的方法,不能调用非静态的结构
内部可以输出语句
随着对象的创建而执行
每创建一个对象,就执行一次非静态代码块
作用:可以在创建对象时,对对象的属性等进行初始化
如果一个类中定义了多个非静态代码块,则按照声明的先后顺序执行
非静态代码块内可以调用静态的属性、静态的方法,或非静态的属性、非静态的方法
abstract修饰类:抽象类
此类不能实例化
抽象类中一定有构造器,便于子类实例化时调用(涉及:子类对象实例化的全过程)
开发中,都会提供抽象类的子类,让子类对象实例化,完成相关的操作 —>抽象的使用前提:继承性
abstract修饰方法:抽象方法
抽象方法只方法的声明,没方法体
包含抽象方法的类,一定是一个抽象类。反之,抽象类中可以没有抽象方法的。
若子类重写了父类中的所有的抽象方法后,此子类方可实例化
4.2 注意点:若子类没重写父类中的所的抽象方法,则此子类也是一个抽象类,需要使用abstract修饰
abstract不能用来修饰:属性、构造器等结构
4.3 abstract应用的举例abstract不能用来修饰私有方法、静态方法、final的方法、final的类
abstract class GeometricObject{
public abstract double findArea();
}
class Circle extends GeometricObject{
private double radius;
public double findArea(){
return 3.14 * radius * radius;
};
}
举例二:IO流中设计到的抽象类:InputStream/OutputStream / Reader /Writer。在其内部
定义了抽象的read()、write()方法。
5. 多线程
5.1、程序(program)
概念:是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码。
5.2、进程(process)概念:程序的一次执行过程,或是正在运行的一个程序。
说明:进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
5.3、线程(thread)概念:进程可进一步细化为线程,是一个程序内部的一条执行路径。
说明:线程作为调度和执行的单位,每个线程拥独立的运行栈和程序计数器(pc),线程切换的开销小。
5.4、并行、并发5.5、多线程的创建并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
方式一:继承Thread类的方式:
- 创建一个继承于Thread类的子类
- 重写Thread类的run() --> 将此线程执行的操作声明在run()中
- 创建Thread类的子类的对象
- 通过此对象调用start():①启动当前线程 ② 调用当前线程的run()
说明两个问题:
问题一:我们启动一个线程,必须调用start(),不能调用run()的方式启动线程。
问题二:如果再启动一个线程,必须重新创建一个Thread子类的对象,调用此对象的start().
方式二:实现Runnable接口的方式:
-
创建一个实现了Runnable接口的类
-
实现类去实现Runnable中的抽象方法:run()
-
创建实现类的对象
-
将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
-
通过Thread类的对象调用start()
两种方式的对比:
开发中:优先选择:实现Runnable接口的方式
- 原因:
-
实现的方式没类的单继承性的局限性
-
实现的方式更适合来处理多个线程共享数据的情况。
- 联系:public class Thread implements Runnable
- 相同点:两种方式都需要重写run(),将线程要执行的逻辑声明在run()中。
目前两种方式,要想启动线程,都是调用的Thread类中的start()。
- start():启动当前线程;调用当前线程的run()
- run(): 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- currentThread():静态方法,返回执行当前代码的线程
- getName():获取当前线程的名字
- setName():设置当前线程的名字
- yield():释放当前cpu的执行权
- join():在线程a中调用线程b的join(),此时线程a就进入阻塞状态,直到线程b完全执行完以后,线程a才结束阻塞状态。
- stop():已过时。当执行此方法时,强制结束当前线程。
- sleep(long millitime):让当前线程“睡眠”指定的millitime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
- isAlive():判断当前线程是否存活
MAX_PRIORITY:10
MIN _PRIORITY:1
NORM_PRIORITY:5 -->默认优先级
5.8 如何获取和设置当前线程的优先级:-
getPriority():获取线程的优先级
-
setPriority(int p):设置线程的优先级
说明:高优先级的线程要抢占低优先级线程cpu的执行权。但是只是从概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只当高优先级的线程执行完以后,低优先级的线程才执行。
5.9 Thread的生命周期线程通信:wait() / notify() / notifyAll() :此三个方法定义在Object类中的。
说明:
1.生命周期关注两个概念:状态、相应的方法
2.关注:状态a–>状态b:哪些方法执行了(回调方法)
某个方法主动调用:状态a–>状态b
3.阻塞:临时状态,不可以作为最终状态
4.死亡:最终状态。
例子:创建个窗口卖票,总票数为100张.使用实现Runnable接口的方式
-
问题:卖票过程中,出现了重票、错票 -->出现了线程的安全问题
-
问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票。
-
如何解决:当一个线程a在操作ticket的时候,其他线程不能参与进来。直到线程a操作完ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能被改变。
Java解决方案:同步机制
- 在Java中,我们通过同步机制,来解决线程的安全问题。
方式一:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明同步的。
关于同步方法的总结:
-
同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
-
非静态的同步方法,同步监视器是:this
-
静态的同步方法,同步监视器是:当前类本身
方式三:Lock锁 — JDK5.0新增
使用的优先顺序:
- Lock —> 同步代码块(已经进入了方法体,分配了相应资源 ) —> 同步方法(在方法体之外)
5.11 线程安全的单例模式(懒汉式):利弊
同步的方式,解决了线程的安全问题。—好处
操作同步代码时,只能一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。
class Blank{
private Blank(){}
private static Blank blank = null;
public static Blank getInstance(){
//方式一,效率更低
synchronized(Blank.class){
if(blank == null){
blank = new Blank();
}
return blank;
}
}
}
class Blank{
private Blank(){}
private static Blank blank = null;
public static Blank getInstance(){
//方式二,效率更高
if(blank == null){
synchronized(Blank.class){
if(blank == null){
blank = new Blank();
}
}
}
return blank;
}
}
5.12死锁问题
-
死锁的理解:
- 不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
-
说明:
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
- 我们使用同步时,要避免出现死锁。
-
举例:
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}



