1e 异常与线程.txt
UP 返回
1.异常
1.1 异常的根类是java.lang.Throwable,其下有两个子类:java.lang.Error和java.lang.Exception(一般是指后者。前者程序员无法处理,或者可以处理)
Exception 编译期异常,进行编译时出现问题。写代码时就会出红线提示
RuntimeException 运行期异常,程序运行时出现问题。比如数组越界,运行时才知道,但是也可以try catch处理掉
int[] arr=new int[1024*1024*1024];//OutOfMemoryError java head space 内存溢出的错误。必须修改代码才能运行
1.2 异常产生的过程
JVM检测程序出现异常,首先会创建一个异常对象,包含了异常产生的原因,内容和位置;如果没有try...catch,JVM就会把异常对象抛出给方法的调用者;抛到main的时候,如果仍然无法处理,就继续抛给JVM处理。JVM接收到异常后,会把异常内容打印到控制台,同时终止当前java程序(中断处理)
1.3 关键字
1.3.1 throw关键字 在指定的方法中抛出指定的异常
throw new xxxException("异常产生的原因");
throw关键字必须写在方法内部
throw后边new的对象必须是Exception或者其子类
throw抛出的异常必须处理(若throw后面创建的是RuntimeException或者其子类,我们可以不处理,默认交给JVM;否则必须用throws或者try...catch处理掉) //NullPointerException也是一个运行期异常,RuntimeException的子类还有很多
Objects类有一个方法判断指定的引用对象是不是null
public static <T> T requireNonNull(T obj){
if(obj==null){
throw new NullPointerException();
}
}
使用时:
public static void main(String []args){
method(null);
}
public static void method(Object obj){
/*if(obj==null){
throw new NullPointerException("传递的对象为null"); //就不用自己写这种判断了
} */
Objects.requireNonNull(obj);
Objects.requireNonNull(obj,"传递的对象为null"); //也可以使用重载的方法,标明原因
}
1.3.2 throws关键字 异常处理(交给调用者处理)
方法 throws AAAException,BBBException{
throw new AAAException();
throw new BBBException();
}
如果抛出的异常有子父类的关系,可以直接声明父类异常即可,比如FileNotFoundException是IOException的子类
1.3.3 try...catch 自己处理异常
try{
}catch(异常类名 变量){
}finally{
}
try中可能会抛出多个异常,就可以使用多个catch处理
1.3.4 finally
无论是否出现异常都会执行
如果finally中有return语句,那么永远都会返回finally中的结果,应避免
2.多线程
2.1 并发:多个事件在同一个时间段内发生(交替执行)
并行:多个事件在同一时刻发生(同时执行)
进程:是指一个内存中运行的程序
线程:是进程中的一个执行单元
java使用抢占式调度方式来执行线程(优先级高的获得的执行几率大一些,同优先级随机执行)
多次启动一个线程是非法的,特别是当线程已经结束执行后,不能再重新启动
2.2 Throwable类中定义了3个异常处理的方法
String getMessage();//返回此Throwable的简短描述
String toString();//返回此Throwable的详细消息字符串
void printStackTrace();//JVM打印异常对象,默认此方法,打印的异常信息是最全面的
2.3 子类重写的方法的异常不可比父类多。若产生了父类throws以外的异常必须自己解决掉
2.4 自定义异常 可以查看java自带的异常类在继承的时候是如何写构造的
public class xxxException extends Exception | RuntimeException {
添加一个空参数的构造方法
添加一个带异常信息的构造方法
}
2.5 JVM执行main方法时,会找OS开辟一条main方法通向cpu的路径(主线程),执行到new MyThread()时的run时,会再开辟一条路径。此后cpu将随机对这两个线程交替执行
每一次new一个新线程执行run方法时,都会新开辟一个新的栈空间来运行
2.6 Thread中的方法
String getName();//返回线程名称
static Thread currentThread();//返回当前正在执行的线程对象的引用
void setName("xxx");//修改线程名称。或者子类修改带参构造函数,也可以达到修改线程名的目的: public MyThread(String name){ super(name); }
sleep(long mills);//暂停
3.线程安全
3.1 使用同步代码块
synchronized(锁对象){
可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:
同步代码块中的锁对象可以为任意对象
但必须保证多个线程使用的锁对象是同一个
锁对象:锁住同步代码块,只让一个线程在同步代码块中执行
public class RunnableImpl1 implements Runnable {
private int ticket = 100;
Object obj = new Object();// 创建一个锁对象
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
}
}
3.2 同步原理
使用了一个锁对象(同步锁,也叫对象监视器),3个线程一起抢夺cpu的执行权。t0抢到时,执行run方法,遇到synchronized代码块,这是t0会检查synchronized代码块是否有锁对象,有的话就会获取锁对象进入到同步执行中
t1抢到执行权时,同样上述步骤,发现此时没有锁对象,t1就会阻塞,等待锁对象被归还(一直到t0执行同步代码结束,才会把锁对象归还)
程序频繁的判断锁,获取锁,释放锁,效率会降低,但保证安全
总结:同步中的线程,不执行完毕不会释放锁;同步外的线程,没有锁进不去
3.3 使用同步方法解决
把访问了共享数据的代码抽取到一个方法中,在方法上添加synchronized修饰符
public class RunnableImpl1 implements Runnable {
private int ticket = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
sellTicket();
}
}
public synchronized void sellTicket() {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
同步方法也会锁住方法内部的代码,其锁对象就是实现类对象RunnableImpl1,也就是this(可以打印对象来验证。所以如果只创建了一个对象,3.1中的同步锁可以直接用this)
3.4 静态同步方法
public class RunnableImpl1 implements Runnable {
private static int ticket = 100;
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
sellTicket();
}
}
public static synchronized void sellTicket() {
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
}
}
静态同步方法的锁对象不能是this(this是创建对象之后产生的,静态方法优先于对象),而是本类的class属性,也就是class文件对象(反射),故此时3.1中的锁对象可以用RunnableImpl1.class
3.5 Lock锁 java.util.concurrent.locks.Lock (jdk1.5以后)
Lock实现了比synchronized更广泛的锁定操作,提供了获取锁lock()和释放锁unlock()的方法。java.util.concurrent.locks.ReentrantLock implements Lock接口
步骤:
在成员位置创建一个ReentrantLock对象
在可能出现线程安全的代码前调用lock()获取锁
在之后调用unlock()释放锁
public class RunnableImpl1 implements Runnable {
private int ticket = 100;
Lock l = new ReentrantLock(); //Lock实现对象
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
l.lock(); //上锁
if (ticket > 0) {
try {
Thread.sleep(10);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
}
l.unlock(); //释放
}
}
}
还有一种更好的写法,以确保锁最终会被释放
public class RunnableImpl1 implements Runnable {
private int ticket = 100;
Lock l = new ReentrantLock();
@Override
public void run() {
// TODO Auto-generated method stub
while (true) {
l.lock();
if (ticket > 0) {
try {
Thread.sleep(10);
System.out.println(Thread.currentThread().getName() + "-->正在卖第" + ticket + "张票");
ticket--;
} catch (Exception e) {
// TODO: handle exception
} finally {
l.unlock(); //放在finally中
}
}
}
}
}
4.线程状态
NEW 新建状态(new Thread();或者new Thread子类();)
RUNNABLE 运行状态 通过new的线程调用start(),获得cpu执行时间即进入
BLOCKED 阻塞状态 通过new的线程调用start(),未获得cpu执行时间即进入(争抢到cpu即可执行)
TERMINATED 死亡状态 run()方法结束,stop()过时则进入
TIMED_WAITING 睡眠状态 sleep(long) wait(long)进入(休眠结束以后仍然需要争夺cpu检查锁以判断是运行还是阻塞)
WAITING 无限等待状态 Object.wait()进入,Object.notify()退出
5.Object等待唤醒方法
wait();//等待
sleep(long m);//等待一定时间
wait(long m);//等待一定时间
notify();//随机唤醒一个线程
notifyAll();//唤醒所有线程
/** 两个线程同时阻塞 **/
public class Demo01 {
public static void main(String[] args) {
Object obj = new Object();
new Thread() {
public void run() {
while (true) {
synchronized (obj) {
System.out.println("顾客1告知老板要买的包子");
try {
obj.wait();// 等待
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("顾客1吃包子");
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
synchronized (obj) {
System.out.println("顾客2告知老板要买的包子");
try {
obj.wait();// 等待
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("顾客2吃包子");
}
}
}
}.start();
new Thread() {
public void run() {
while (true) {
try {
Thread.sleep(5000);// 做包子
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
synchronized (obj) {
System.out.println("包子做好了");
obj.notifyAll(); // 唤醒(如果不用all只会唤醒一个)
}
}
}
}.start();
}
}
6.等待唤醒机制 多个线程之间的协作(通信.)
/**测试类**/
public class Demo01 {
public static void main(String[] args) {
Baozi baozi = new Baozi();
new Customer(baozi).start();
new Market(baozi).start();
}
}
/**包子类**/
public class Baozi {
private boolean exist = false;
public boolean isExist() {
return exist;
}
public void setExist(boolean exist) {
this.exist = exist;
}
}
/**包子铺类**/
public class Market extends Thread {
private Baozi baozi;
public Market(Baozi bb) {
baozi = bb;
}
public void run() {
while (true) {
synchronized (baozi) {
if (baozi.isExist()) {
try {
baozi.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("没有包子,包子铺开始做包子");
try {
Thread.sleep(3000);
} catch (Exception e) {
// TODO: handle exception
}
System.out.println("包子做好了");
baozi.setExist(true);
baozi.notify();
}
}
}
}
/**顾客类**/
public class Customer extends Thread {
private Baozi baozi;
public Customer(Baozi bb) {
baozi = bb;
}
public void run() {
while (true) {
synchronized (baozi) {
if (!baozi.isExist()) {
try {
baozi.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println("有包子了,顾客开始吃包子");
try {
Thread.sleep(1000);
} catch (Exception e) {
// TODO: handle exception
}
baozi.setExist(false);
baozi.notify();
System.out.println("顾客吃掉了");
}
}
}
}
7.线程池
7.1 概念
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
Java中可以通过线程池使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务
7.2 原理
线程池就是一个容器:集合(ArrayList,HashSet,LinkedList<Thread>,HashMap),推荐使用LinkedList
当程序第一次启动,就创建多个线程保存到集合中。要使用的时候,就可以从集合中取出,使用完以后再归还回去
如果是ArrayList就用Thread t=list.remove(0);//返回的就是被移除的元素(线程),执行完毕后list.add(t);
如果是LinkedList就用Thread t=linked.removeFirst();//执行完后再linked.addLast(t);归还
JDK1.5以后内置了线程池,可以直接使用,原理同上
7.3 好处
1. 降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用
2. 提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
3. 提高线程的可管理性。可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内 存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
7.4 使用
java.util.concurrent.Executors :线程池的工厂类,用来生成线程池
Executors中的静态方法:
static ExecutorService newFixedThreadPool(int nThreads) 创建一个可重用固定线程数的线程池(返回的是ExecutorService接口的实现类对象,可以使用ExecutorService接口接收,面向接口编程)
java.util.concurrent.ExecutorService :线程池接口,用来从线程池中获取线程,调用start()执行线程
submit(Runnable task) 提交一个Runnable任务用于执行
void shutdown() 关闭销毁线程池
使用步骤:
newFixedThreadPool创建线程池
创建线程类实现Runnable
调用ExecutorService中的submit传递线程任务开启执行
调用ExecutorService中的shutdown销毁线程(不建议执行,因为线程池就是拿来不断用的)
public class Demo01 {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new MyRunnable());
es.submit(new MyRunnable());
es.submit(new MyRunnable());//可以通过打印线程看出一共有两个线程在使用
es.shutdown();//线程池销毁,之后就不可以再submit了
}
}
DOWN 返回