JAVA基础(七)
泛型:
- 泛型又称参数化类型,Jdk5.0出现的新特性,解决数据类型安全性问题。
- 在类声明或实例化时只要指定好语言的具体的类型即可。
- Java泛型可以保证如果程序再编译时没有发出经警告,运行时就不会出现ClassCastException异常。同时,代码更加简洁、健壮。
- 作用:可以在类声明时通过一个标识表示类中某个属性的类型,或者时某个方法的返回值类型,或者是参数类型。
- 好处:编译时,检查添加元素的类型,提高安全性,减少类型转换次数,提高效率。
泛型的使用:
-
interface List {} ,public class HashSet{}…//注:说明T,只能引用类型
List<Integer> list =new ArrayList<Integer>();//OK
-
在指定泛型具体类型后,可以传入该类型或其子类类型。
-
List list3 =new ArrayList(); 默认给它的泛型是 E就说Object[]
自定义泛型:
基本语法:class 类名 <T,R…>{成员}
注意:
- 普通成员可以使用泛型(属性、方法)
- 使用泛型的数组,不能初始化
- 静态方法中不能使用类的泛型
- 泛型类的类型,是在创建对象时确定的(因为创建对象时,需要指点确定类型)
- 如果创建对象时,没有指定类型默认Object
class Tiger<T,R,M>{
String name;
R r;//属性使用泛型
M m;
T t;//数组在new 不能确定T的类型,就无法在内存开空间
}
//静态方法中不能使用类的泛型?
//因为静态是和类相关的,在类加载时,对象还没有创建
//所以,如果静态方法和静态属性使用了泛型,JVM就无法完成初始化
Tiger<Double,String,Integer> g = new Tiger<>("John");
g.setT(10.9);//OK
g.setT("yy")//错误
Tiger g2 = new Tiger ("hello");
g2.setT("yy");//OK
自定义泛型接口:
interface 接口名 <T,R…>{}
- 接口中,静态成员也不能使用泛型(这个和泛型类规定一样)
- 泛型接口的类型,在继承接口或者实现接口时确定。
- 没有指定类型,默认Object
自定义泛型方法:
修饰符 <T,R…>返回类型 方法名(参数列表){}
class Car{//普通类
public <T,R> void fly (T t,R r){//泛型方法
}
}
- 泛型方法,可以定义在普通类中,也可以定义在泛型类中。
- 当泛型方法被调用时,类型会确定
- public void eat (E e){},修饰符后没有<T,R…> eat方法不是泛型方法,而是使用了泛型。
class Apple<T,R,M>{//自定义泛型类
public<E> void fly(E e){//泛型方法
System.out.println(e.getClass().getSimpleName);
}
public void eat(U u){}//错误,没有U声明
public void run(M m){}//ok
}
泛型的继承和通配符说明:
- 泛型不具备继承性。List list = new ArrayList();//❌
- <?>:支持任意泛型类型
- <? extends A> :支持A类以及A类的子类,规定了泛型的上限。
- <? super A>:支持A类以及A类的父类,不限于直接父类,规定了泛型的下限。
JUnit:
为什么要使用JUnit?
- 一个类有很多功能代码需要测试,为了测试,就需要写入main方法中。
- 如果有多个功能测试,就需要来回注销,切换很麻烦。
- 如果可以直接运行一个方法,就方便很多,并且可以给出相关信息,就好了–>JUnit
基本介绍:@Test
- JUnit是一个Java语言的单元测试框架。
- 多数Java的开发环境都已经集成了JUnit作为单元测试的工具。
进程:
进程是指运行中的程序,进程是程序的一次执行过程,或是正在运行的一个程序,是动态的过程:有它自身的产生、存在和消亡。
线程:
线程由进程创建的,是进程的一个实体,一个进程可以有多个线程。
单线程:同一时刻,只允许执行一个线程。
多线程:同一时刻,可以执行多个线程。
并发:同一时刻,多个任务交替执行,造成一种"貌似同时"的错觉,简单的说,单核CPU实现的多任务就是并发。
并行:同一时刻,多个任务同时执行。多核CPU可以实现并行。并发并行也可以同时执行。
Runtime runtime =Runtime.getRuntime();
int n = runtime.availableProcessors();
//获取当前电脑的CPU数量/核数
线程基本使用:
在Java中线程来使用有两种方法:
- 继承Thread类,重写run方法
- 实现Runnable,重写run方法
start()方法使用start0(),该线程不一定会立马执行,只是将线程变成可运行状态,具体什么时候执行,取决于CPU,由CPU统一调度。start0是本地方法,是JVM调用,底层是c/c++实现。
实现Runnable接口(需要动态代理 )
- Java是单继承的,在某些情况下一个类可能已经继承了某个父类,这时在用继承Thread类方法来创建线程显然不可能。
- 设计者提供了另外一个方式创建线程,就说通过实现Runnable接口来创建线程。
继承Thread VS 实现Runnable的区别:
- 从java的设计来看,通过继承Thread或者实现Runnable接口来创建线程的本质没有区别,从jdk帮助文档可以看出Thread类本身就实现了Runnable接口。
- 实现Runnable接口方式更加适合多线程共享一个资源的情况,并且避免了单继承的限制。
线程终止:
- 当线程完成任务后,会自动退出。
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。
线程常用的方法:
setName //设置线程名称,使之与参数name相同
getName //返回该线程的名称
start //使该线程开始执行;Java虚拟机底层调用该线程的start0方法
run //调用线程对方法
setPriority//更改线程的优先级
getPriority//获取线程的优先级
sleep //在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)
interrupt//中断线程
注:
- start 底层会创建新的线程,调用run ,run就是一个简单的方法调用,不会启动新的线程。
- 线程优先级的范围
- interrupt,中断线程,但并没有真正的结束线程,所以一般用于中断正在休眠线程。
- sleep:线程的静态方法,使用当前线程休眠。
- yield:线程的礼让,让出cpu,让其他线程执行,但礼让的时间不确定,所以比一定礼让成功。
- join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程的所有任务。
用户线程和守护线程:
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为了工作线程服务的,当所有用户线程结束,守护线程自动结束。//setDaemon(true);
- 常见的守护线程:垃圾回收机制
线程的生命周期:
-
新建:就是刚使用new方法,new出来的线程;
-
就绪:就是调用的线程的start()方法后,这时候线程处于等待CPU分配资源阶段,谁先抢的CPU资源,谁开始执行;
-
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能;
-
阻塞:在运行状态的时候,可能因为某些原因导致运行状态的线程变成了阻塞状态,比如sleep()、wait()之后线程就处于了阻塞状态,这个时候需要其他机制将处于阻塞状态的线程唤醒,比如调用notify或者notifyAll()方法。唤醒的线程不会立刻执行run方法,它们要再次等待CPU分配资源进入运行状态;
-
销毁:如果线程正常执行完毕后或线程被提前强制性的终止或出现异常导致结束,那么线程就要被销毁,释放资源;
wait()、notify()、notifyAll()三个方法必须在同步代码块或同步方法中。
wait()、notify()、notifyAll()三个方法的调用者必须是同步代码块或同步方法中的同步监视器。否则,会出现IllegalMonitorStateException异常。
Sleep()和wait()的异同?
相同点:一旦执行方法,都可以是当前线程进入阻塞状态
不同点:
- 两个方法声明的位置不同,Thread类中声明sleep(),Object类中声明wait()
- 调用的要求不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块或同步方法中。
- 关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会释放锁
完整的生命周期图如下:
线程同步机制:
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就会使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到线程完成操作,其他线程才能对该内存地址操作。
同步具体方法-Synchronized
-
同步代码块:
synchronized(对象){//得到对象的锁,才能操作同步代码//需要被同步代码
}
-
synchronized还可以放在方法声明中,表示整个方法-为同步方法
public synchronized void m (String name){
//需要被同步的代码
}
-
从JDK5.0开始,Java提供了更强大的线程同步机制—通过显示定义同步锁对象来实现同步,同步锁使用Lock对象充当。
-
java.util.concurrent.locks.Lock接口是 控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象
-
ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显示加锁,释放锁。
ReentrantLock lock = new ReentrantLock(); lock.lock();//上锁 lock.unlock();//解锁
synchronized与Lock的异同?
相同:二者都可以解决线程安全问题。
不同:synchronized机制在执行完相应的代码以后,自动的释放同步监视器。
Lock需要手动的起的同步(lock()),同时结束也需要手动的实现(unlock())
互斥锁:
- Java语言中,引入对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为"互斥锁"的标记,这个标记用来保证在任一时刻,只能有一个线程访问该对象。
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象在任意时刻只能由一个线程访问。
- 同步的局限性:导致程序的执行效率要降低。
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是同一个对象)
- 同步方法(静态的)的锁为当前类本身。//(类名.class)
注:
- 同步方法如果没有使用static修饰:默认锁对象为this
- 如果方法使用static修饰,默认锁对象:当前类.class
- 步骤:需要先分析上锁的代码—>选择同步代码块或同步方法–>要求多个线程的锁对象为同一个即可!
线程的死锁:
多个线程都占用了对方的锁资源,但不肯相让,导致了死锁,在编程是一定要避免死锁的发生。
释放锁:
- 当前线程的同步方法、同步代码执行结束。
- 当前线程在同步代码块、同步方法中遇到break、return.
- 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导致异常结束
- 当前线程在同步代码块、同步方法中执行了线程对象的wait()方法,当前线程暂停,并释放锁。
不会释放锁:
-
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、Thread.yield()方法暂停当前线程的执行不会释放锁。
-
线程执行同步代码块时,其他线程调用了该线程的supend()方法将该线程挂起,该线程不会释放锁。
提示:应尽量避免使用supend()和resume()来控制线程,方法不再推荐使用
JDK5.0新增线程创建方式:
-
实现Callable接口,重写call()
- 与使用Runnable相比,Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助Future Task类,比如获取返回结果。
Future接口:
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- Future Task是Future接口的唯一的实现类
- Future Task同时实现了Runnable,Future接口,它既可以作为Runnable 被线程执行,又可以作为Future得到Callable的返回值
-
使用线程池
-
背景;经常创建和销毁、使用量特别大的资源,比如并发情况下的线程。
-
思路:提前创建好多个线程,放入线程池中,使用直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用
-
好处:
1、提高响应速度(减少了创建线程的时间)
2、降低资源消耗(重复利用线程池中线程,不需要每次创建线程)
3、便于线程管理:
corePoolSize:核心池 的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间会终止。
线程池相关API
-
JDK5.0起提供了线程池相关API:ExecutorService和Exectors
-
ExecutorService:真正的线程池接口。常见子类ThreadPoolExcutor
void execute (Runnable command):执行任务/命令。没有返回值,一般用来执行Runnable
Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
void shutdown():关闭连接池。
-
Executors:工具类、线程池的工厂类、用于创建并返回不同类型的线程池。
Executors.newCachedThreadPool():创建一个可根据需要创建线程的线程池。
Executors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池。
Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
Executors.newSchedleThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
-
//提供指定线程数量的线程池。
ExecutorService service = Executors.newFixedThreadPool(10);
//执行指定的线程的操作,需要提供实现Runnable接口或者ThreadPool
//接口实现类的对象
service.execute(new NumberThread());//适用于Runnable
//service.submit(Callable.callable);//适用于Callable
//关闭连接池
service.shutdown();