黑马程序员——Java异常&多线程

-------android培训java培训、期待与您交流! ----------

一、异常

程序出现的不正常的情况。在java中用类的形式对不正常情况进行了描述和封装对象。

描述不正常的情况的类,就称为异常类。 问题很多,意味着描述的类也很多,将其共性进行向上抽取,形成了异常体系。

1:Throwable

|--Error严重问题,我们不处理。

|--Exception

|--RuntimeException运行期异常,我们需要修正代码

|--非RuntimeException   编译期异常,必须处理的,否则程序编译不通过

2:异常的处理:

A:JVM的默认处理

把异常的名称,原因,位置等信息输出在控制台,同时会结束程序。

B:自己处理

a:try...catch...finally捕获异常

自己编写处理代码,后面的程序可以继续执行

b:throws 声明异常

把自己处理不了的,在方法上声明,告诉调用者,这里有问题

3:编译期异常和运行期异常的区别?

编译期异常 必须要处理的,否则编译不通过

运行期异常 可以不处理,也可以处理

4:异常中的方法

A: public String getMessage():异常的消息字符串

B: public String toString():返回异常的简单信息描述

C: printStackTrace() 获取异常类名和异常信息,以及异常出现在程序中的位置。返回值void。把信息输出在控制台。

D: printStackTrace(PrintStream s):通常用该方法将异常内容保存在日志文件中,以便查阅。

5:异常处理格式

A: throws 异常类名。这个格式必须跟在方法的括号后面。

B: try...catch...finally的处理格式

try {

 可能出现问题的代码;

}catch(异常名 变量) {

针对问题的处理;

}finally {

释放资源;

}

C: 变形格式:

try {

可能出现问题的代码;

}catch(异常名 变量) {

针对问题的处理;

}

try{

可能出现问题的代码;

}finally{

...

}

D: 处理多个异常

a:每一个写一个try...catch

b:写一个try,多个catch

try{

...

}catch(异常类名 变量名) {

...

 }catch(异常类名 变量名) {

...

}...

finally{

...

}

注意事项:
(1)能明确的尽量明确,不要用大的来处理。
(2)平级关系的异常谁前谁后无所谓,如果出现了子父关系,父必须在后面

(3)被finally控制的语句体一定会执行,除非在执行到finally之前jvm退出了。

用于释放资源,在IO流操作和数据库操作中会见到

(4)try是一个独立的代码块,在其中定义的变量只在该变量块中有效
    如果在try以外继续使用,需要在try外建立引用,在try中对其进行初始化。IO,Socket就会遇到。

E: JDK7出现了一个新的异常处理方案:

try{

...

}catch(异常名1 | 异常名2 | ...  变量 ) {

...

}

注意:这个方法虽然简洁,但是也不够好。

(1)处理方式是一致的。

(2)多个异常间必须是平级关系

6:在try中写了return,后面又写了finally,是先执行return还是先执行finally?

会,前。其实是在中间。

public static void main(String[] args) {
	System.out.println(Demo());
}

private static String Demo(){
	try{
	     System.out.println("我是try!");
	     return TestReturn();
	}finally{
	     System.out.println("我是finally!");
             //return "true";
	}
}

private static String TestReturn(){
	System.out.println("testreturn.");
	return "我是return!";
}

结果

我是try!

testreturn.

我是finally!

我是return!

如果finally中没有return语句,try里面有return,return语句先执行,再执行finally语句,不过并没有直接返

回,而 是保存起来了,等finally语句执行完了再返回结果。如果在finally中也包含return语句,将会直接返回,不再去执行try中的return语句。或者覆盖掉try中的return?在finally中使用return 编译器会警告,故不建议使用。

7:自定义异常

继承自Exception或者RuntimeException,只需要提供无参构造和一个带参构造即可,通过throw将自定义异常抛出

throw和throws的区别

throws

用在方法声明后面,跟的是异常类名

可以跟多个异常类名,用逗号隔开

表示抛出异常,由该方法的调用者来处理

throws表示出现异常的一种可能性,并不一定会发生这些异常

throw

用在方法体内,跟的是异常对象名

只能抛出一个异常对象名

表示抛出异常,由方法体内的语句处理

throw则是抛出了异常,执行throw则一定抛出了某种异常

定义功能方法时,需要把出现的问题暴露出来让调用者去处理,那么就通过throws在函数上标识。

在功能方法内部出现某种情况,程序不能继续运行,需要进行跳转时,就用throw把异常对象抛出。

8:异常处理的原则:

A: 函数内容如果抛出需要检测的异常,那么函数上必须要声明。

否则,必须在函数内用try/catch捕捉,否则编译失败。

B: 如果调用到了声明异常的函数,要么try/catch,要么throws,否则编译失败。

C: 功能内容可以解决,用catch。解决不了,用throws告诉调用者,由调用者解决。

D: 一个功能如果抛出了多个异常,那么调用时,必须有对应多个catch进行针对性处理。

内部有几个需要检测的异常,就抛几个异常,抛出几个,就catch几个。

8:异常的注意事项

A: 父的方法有异常抛出,子的重写方法在抛出异常的时候必须要小于等于父的异常 

B: 父的方法没有异常抛出,子的重写方法不能有异常抛出

C: 父的方法抛出多个异常,子的重写方法必须比父少或者小

D: RuntimeException以及子类如果在函数中被throw抛出,可以不用在函数声明。

二、多线程

进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。

线程是程序中单个顺序的控制流,是程序使用CPU的基本单位。

一个进程如果有多条执行路径,则称为多线程程序。

1:多线程的利与弊

好处:解决了多部分代码同时运行的问题。

弊端:线程太多,会导致效率的降低。

其实,多个应用程序同时执行都是CPU在做着快速的切换完成的。这个切换是随机的。CPU的切换是需要花费时间的,从而导致了效率的降低。多进程的作用不是提高执行速度,而是提高CPU的使用率。

2:JVM启动时最少启动了两个线程

执行main函数的线程。

负责垃圾回收的线程。

3:创建线程

(1)继承Thread类

A: 步骤

a: 定义一个类继承Thread类。

b: 覆盖Thread类中的run方法。

c: 直接创建Thread的子类对象创建线程。

d:调用start方法开启线程并调用线程的任务run方法执行。

B: 线程对象的名称

public final String getName():获取线程的名称。

public final void setName(String name):设置线程的名称

C: 针对不是Thread类的子类

public static Thread currentThread():返回当前正在执行的线程对象

Thread.currentThread().getName()

public class MyThread extends Thread {
	public MyThread(){}
	public MyThread(String name){ 
		super(name);
	}
	@Override
	public void run(){ //重写run()方法
		for(int x=0; x<200; x++){
			System.out.println(getName()+"---"+x);
		}
	}
}
public static void main(String[] args) {
	//创建程序对象
	MyThread my = new MyThread();
	MyThread my1 = new MyThread();
	//调用方法设置名称
	my.setName("Donald");
	my1.setName("Jesus");
	my.start();//开启线程
	my1.start();
        //带参构造方法给线程起名字
	MyThread my2 = new MyThread("刘亦菲");
	MyThread my3 = new MyThread("小龙女");
	my2.start();
	my3.start();
	//public static Thread currentThread():返回当前正在执行的线程对象
	System.out.println(Thread.currentThread().getName());//得到的是主线程的名称
}
 
 

(2)实现Runnable接口

A: 步骤

a: 自定义类MyRunnable实现Runnable接口

b: 重写run()方法

c: 创建MyRunnable类的对象

d: 创建Thread类的对象,并把C步骤的对象作为构造参数传递

e: 调用线程对象的start方法开启线程

B: 实现Runnable接口的好处:

a: 将线程的任务从线程的子类中分离出来,进行了单独的封装,按照面向对象的思想将任务封装成对象

b: 避免了Java单继承的局限性。所以,创建线程的第二种方式较为常用。

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			// 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}
}
public static void main(String[] args) {
	// 创建MyRunnable类的对象
	MyRunnable my = new MyRunnable();

	// 创建Thread类的对象,并把C步骤的对象作为构造参数传递
	// Thread(Runnable target, String name)
	Thread t1 = new Thread(my, "Jesus");
	Thread t2 = new Thread(my, "Donald");

	t1.start();
	t2.start();
}
 

(3)Thread类中run()和start()方法的区别如下:

run()方法:在本线程内调用该Runnable对象的run()方法,可以重复多次调用;和普通方法没有区别。

start()方法:启动一个线程,由JVM调用该Runnable对象的run()方法,不能多次启动一个线程。

(4)两个创建线程方式的比较

A extends Thread:

简单

不能再继承其他类了(Java单继承)

同份资源不共享

A implements Runnable:(推荐)

多个线程共享一个目标资源,适合多线程处理同一份资源。

该类还可以继承其他类,也可以实现其他接口。

4:线程控制

public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。

当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。

public final void join():等待该线程终止。 

public final int getPriority():返回线程对象的优先级

public final void setPriority(int newPriority):更改线程的优先级。

public static void sleep(long millis):线程休眠

public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。

public static void yield():暂停当前正在执行的线程对象,并执行其他线程。 让多个线程的执行更和谐,但是不

能靠它保证一人一次。

5:线程的生命周期

Thread类内部有个public的枚举Thread.State,里边将线程的状态分为:

新建:创建线程对象

就绪:有执行资格,没有执行权

运行:有执行资格,有执行权

阻塞:由于一些操作让线程处于该状态。没有执行资格,没有执行权。

而另一些操作却可以把它激活,激活后处于就绪状态。

死亡:线程对象变成垃圾,等待被收回。

6:多线程安全问题

(1)产生原因同时也是判断线程是否有安全问题的标准

A:在多线程环境下

B:有共享数据

C:有多条语句操作共享数据

(2)解决方案

A:同步代码块

a: 思路:把多条语句操作共享数据的代码给包成一个整体,让某个线程执行的时候,别的线程不能执行。必须要当前线程把这些代码执行完毕后,其他线程才可以参与运算。

是Java给我们提供了:同步机制。可以解决这个问题

同步代码块:

synchronized(对象){

需要同步的代码;

}

同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。

public void add(int num){
      synchronized(this){
             sum = sum + num;
             System.out.println(sum);
      }
}

b: 前提多个线程使用同一个锁对象

c: 同步的好处同步的出现解决了多线程的安全问题

d: 同步的弊端当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程

序的运行效率

B: 同步方法

把同步加在方法上,这里的锁对象是this

class FinalDemo1 implements Runnable {
	private int num = 50;
	@Override
	public void run() {
		for (int i = 0; i < 100; i++) {
			gen();
		}
}
public synchronized void gen() {
		for (int i = 0; i < 100; i++) {
			if (num > 0) {
				try {
					Thread.sleep(10);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
				System.out.println(Thread.currentThread().getName() + "卖出了第"+ num-- + "张票!");<pre name="code" class="java">                 }
         }
}
 
 

C:静态同步方法

把同步加在方法上,这里的锁对象是当前类的字节码文件对象(.class)

D:同步锁—JDK5后的另一个同步机制

通过显示定义同步锁对象来实现同步,这种机制,同步锁应该使用Lock对象充当。

在实现线程安全控制中,通常使用ReentrantLock(可重入锁)。使用该对象可以显示地加锁和解锁。

具有与使用synchronized同步代码块就是对于锁的操作是隐式的。
Lock接口:出现替代了同步代码块或者同步函数,将同步的隐式操作变成显示锁操作。同时更为灵活,可以一个锁上加上多组监视器。
lock():获取锁。
unlock():释放锁,为了防止异常出现,导致锁无法被关闭,所以锁的关闭动作要放在finally中。
方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class SellTicket implements Runnable {

	// 定义票
	private int tickets = 100;

	// 定义锁对象
	private Lock lock = new ReentrantLock();

	@Override
	public void run() {
		while (true) {
			try {
				// 加锁
				lock.lock();
				if (tickets > 0) {
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println(Thread.currentThread().getName()
							+ "正在出售第" + (tickets--) + "张票");
				}
			} finally {
				// 释放锁
				lock.unlock();
			}
		}
	}
}

7:以前线程安全的类

把一个线程不安全的集合类变成一个线程安全的集合类用Collections工具类的方法即可。

// 线程安全的类
StringBuffer sb = new StringBuffer();
Vector<String> v = new Vector<String>();
Hashtable<String, String> h = new Hashtable<String, String>();
// Vector是线程安全的时候才去考虑使用的,但是即使要安全,也不用。
// public static <T> List<T> synchronizedList(List<T> list)
List<String> list1 = new ArrayList<String>();// 线程不安全
List<String> list2 = Collections.synchronizedList(new ArrayList<String>()); // 线程安全
8:死锁

两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。

public class DieLock extends Thread {
	private boolean flag;
	public DieLock(boolean flag) {
		this.flag = flag;
	}
	@Override
	public void run() {//同步的嵌套
		if (flag) {
			synchronized (MyLock.objA) {
				System.out.println("if objA");
				synchronized (MyLock.objB) {
					System.out.println("if objB");
				}
			}
		} else {
			synchronized (MyLock.objB) {
				System.out.println("else objB");
				synchronized (MyLock.objA) {
					System.out.println("else objA");
				}
			}
		}<pre name="code" class="java">         }
}
 
 

9:线程间通信

(1)Object类中提供了三个方法:

wait():让当前线程放弃监视器进入等待,直到其他线程调用同一个监视器并调用notify()或notifyAll()为止。

notify():唤醒在同一对象监听器中调用wait方法的第一个线程。

notifyAll():唤醒在同一对象监听器中调用wait方法的所有线程。

这些方法的调用必须通过锁对象调用,而使用的锁对象可以是任意锁对象。所以,这些方法必须定义在Object类中。

两种情况:

A: synchronized修饰的方法,因为该类的默认实例(this)就是同步监视器,所以可以在同步方法中调用这三个方法。

B: synchronized修饰的同步代码块,同步监视器是括号里的对象,所以必须使用该对象调用这三个方法。
public class Student {
	String name;
	int age;
	boolean flag; // 默认情况是没有数据,如果是true,说明有数据
}
public class SetThread implements Runnable {
	private Student s;
	private int x = 0;
	public SetThread(Student s) {
		this.s = s;
	}
	@Override
	public void run() {
		while (true) {
			synchronized (s) {
				//判断有没有
				if(s.flag){
					try {
						s.wait(); //t1等着,释放锁
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}			
				if (x % 2 == 0) {
					s.name = "Jesus";
					s.age = 27;
				} else {
					s.name = "Donald";
					s.age = 30;
				}
				x++; //x=1
				
				//修改标记
				s.flag = true;
				//唤醒线程
				s.notify();//唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。
			}
			//t1有,或者t2有
		}
	}
}
public class GetThread implements Runnable {
	private Student s;
	public GetThread(Student s) {
		this.s = s;
	}
	@Override
	public void run() {
		while (true) {
			synchronized (s) {
				if(!s.flag){
					try {
					     s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}			
				System.out.println(s.name + "---" + s.age);
				//Jesus---27
				//Donald---30				
				//修改标记
				s.flag = false;
				//唤醒线程
				s.notify(); //唤醒t1
			}
		}
	}
}
public static void main(String[] args) {
		//创建资源
		Student s = new Student();		
		//设置和获取的类
		SetThread st = new SetThread(s);
		GetThread gt = new GetThread(s);
		//线程类
		Thread t1 = new Thread(st);
		Thread t2 = new Thread(gt);
		//启动线程
		t1.start();
		t2.start();
}

sleep()和wait()的区别

sleep指线程被调用时,占着CPU不工作,形象地说明为“占着CPU睡觉”,此时,系统的CPU部分资源被占用,其他线程无法进入,会增加时间限制。

wait指线程处于进入等待状态,让出系统资源,形象地说明为“等待使用CPU”,此时线程不占用任何资源,不增加时间限制。

sleep(100L)意思为:占用CPU,线程休眠100毫秒

wait(100L)意思为:不占用CPU,线程等待100毫秒

(2)同步锁中方法

当使用Lock对象来保证同步的,系统中不存在隐式的同步监视器对象,那么就不能使用者上面那三个方法了。

Condition接口:出现替代了Object中的wait、notify、notifyAll方法。将这些监视器方法单独进行了封装,变成Condition监视器对象,可以任意锁进行组合。

Condition接口中的await方法对应于Object中的wait方法。

Condition接口中的signal方法对应于Object中的notify方法。

Condition接口中的signalAll方法对应于Object中的notifyAll方法。

(3)线程的状态转换图


10:线程组&线程池

(1)线程组:

把多个线程组合到一起。它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

线程默认情况下,所有的线程都属于属于main的线程组。创建其他线程的时候,把其他线程的组指定为我们自己新建线程组。

private static void method() {
	//ThreadGroup(String name)创建线程组
	ThreadGroup tg = new ThreadGroup("这是一个新的组");
	MyRunnable my = new MyRunnable();
	//Thread(ThreadGroup group, Runnable target, String name)//指定线程组
	Thread t1 = new Thread(tg,my,"Jesus");
	Thread t2 = new Thread(tg,my,"Donald");	
	System.out.println(t1.getThreadGroup().getName());//这是一个新的组
	System.out.println(t2.getThreadGroup().getName());//这是一个新的组
		
	//通过组名称设置后台线程,表示该组的线程都是守护线程
	tg.setDaemon(true);
}

(2)线程池

线程池可以很好的提高性能,尤其是当程序中要创建大量生存期很短的线程时,更应该考虑使用线程池。

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法

public static ExecutorService newCachedThreadPool()

public static ExecutorService newFixedThreadPool(int nThreads)

public static ExecutorService newSingleThreadExecutor()

这些方法的返回值是ExecutorService对象,该对象表示一个线程池,可以执行Runnable对象或者Callable对象代表的线程。它提供了如下方法

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T> task)

步骤

A: 创建一个线程池对象,控制要创建几个线程对象。

public static ExecutorService newFixedThreadPool(int nThreads)

B: 这种线程池的线程可以执行Runnable对象或者Callable对象代表的线程

做一个类实现Runnable接口。

C: 调用如下方法即可

Future<?> submit(Runnable task)

<T> Future<T> submit(Callable<T> task)

D: 结束

public class MyRunnable implements Runnable {
	@Override
	public void run() {
		for (int x = 0; x < 100; x++) {
			System.out.println(Thread.currentThread().getName() + ":" + x);
		}
	}
}<pre name="code" class="java">public static void main(String[] args) {
	// 创建一个线程池对象,控制要创建几个线程对象。
	// public static ExecutorService newFixedThreadPool(int nThreads)
	ExecutorService pool = Executors.newFixedThreadPool(2);

	// 可以执行Runnable对象或者Callable对象代表的线程
	pool.submit(new MyRunnable());
	pool.submit(new MyRunnable());

	//结束线程池
	pool.shutdown();
}
 
 

11:Callable

是带泛型的接口,这里指定的泛型其实是call()方法的返回值类型。

好处:
可以有返回值

可以抛出异常
弊端:
代码比较复杂,所以一般不用

public class MyCallable implements Callable<Integer> {
	private int number;
	public MyCallable(int number) {
		this.number = number;
	}
	@Override
	public Integer call() throws Exception {
		int sum = 0;
		for (int x = 1; x <= number; x++) {
			sum += x;
		}
		return sum;
	}
}<pre name="code" class="java">public class CallableDemo {
	public static void main(String[] args) throws InterruptedException, ExecutionException {
		// 创建线程池对象
		ExecutorService pool = Executors.newFixedThreadPool(2);
		// 可以执行Runnable对象或者Callable对象代表的线程
		Future<Integer> f1 = pool.submit(new MyCallable(100));
		Future<Integer> f2 = pool.submit(new MyCallable(200));
		// V get()
		Integer i1 = f1.get();
		Integer i2 = f2.get();
		System.out.println(i1);//5050
		System.out.println(i2);//20100
		// 结束
		pool.shutdown();
	}
}
 
12:匿名内部类方式使用多线程 

本质其实就是该类或者接口的子类对象。

格式

new 类名或者接口名() {

重写方法;

};

public static void main(String[] args) {
	// 继承Thread类来实现多线程
	new Thread() {
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println(Thread.currentThread().getName() + ":"
						+ x);
			}
		}
	}.start();

	// 实现Runnable接口来实现多线程
	new Thread(new Runnable() {
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println(Thread.currentThread().getName() + ":" + x);
			}
		}
	}) {}.start();

	// 继承+实现
	new Thread(new Runnable() {
		@Override
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println("hello" + ":" + x);
			}
		}
	}) {
		public void run() {
			for (int x = 0; x < 100; x++) {
				System.out.println("world" + ":" + x);
			}
		}
	}.start();
}

13:定时器

可以让我们在指定的时间做某件事情,还可以重复的做某件事情。

依赖Timer和TimerTask这两个类:

Timer——定时

public Timer()

public void schedule(TimerTask task,long delay)

public void schedule(TimerTask task,long delay,long period)

public void cancel()

TimerTask——任务

public class TimerDemo2 {
	public static void main(String[] args) {
		//创建定时器对象
		Timer t = new Timer();
		//定时重复
		t.schedule(new MyTask2(), 3000, 2000);			
	}
}
//做一个任务
class MyTask2 extends TimerTask{	
	@Override
	public void run() {
		System.out.println("beng,爆炸了");		
	}
}


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值