java必须精通第三课

异常处理

为什么需要异常处理呢?
没有异常处理就会用很多条件语句判断,大量的判断语句会很影响程序的性能。java的异常机制恰好改进了这一点,它具有易于使用、可自行定义异常类,处理抛出的异常的同时,又不会降低程序运行的速度和优点。
在计算机发展中,有两大计算机杀手,一个是断电,另一个是被除数为0,因为被除数为0在数学上解释无穷大,则意味着内存将全部被占满。

throws这种抛出的异常,代表这个类有可能产生异常,但是这个类不处理,交给调用者处理,调用者捕获异常。
throw直接抛出异常,抛出时,直接抛出异常的实例化对象。

jar命令的使用:

当开发者为客户开发出一套java类之后,肯定要把这些类交给用户使用,但是如果所有的类都通过*.class的格式给用户,会比较麻烦,所以一般情况下会将这些class文件压缩成一个文件交给客户使用,那么这样的文件就称为jar文件。
如果要想生成jar文件,直接使用jdk中bin目录里的jar.exe,就可以将所有类文件进行压缩。

多线程

进程是程序的一次动态执行过程,它经历了从代码的加载、执行到执行完毕的一整个过程,这个过程也是进程本身从产生、发展到最终消亡的过程。多进程操作系统能同时运行多个进程(程序),由于CPU具备分时机制,所以每个进程都能循环获得自己的CPU时间片。由于CPU执行的速度非常快,使得所有的程序好像是在同时运行一样。
多线程是实现并发机制的一种有效手段,进程和线程一样,都是实现并发的一个基本单位。
线程是比进程更小的执行单位,线程是在进程的基础之上进一步划分,所谓的多线程是指一个进程在执行过程中可以产生多少个线程。这些线程可以同时存在,同时运行,一个进程可能包含了多个同时执行的线程。
java中想实现多线程有两种手段,一种是继承Thread类,另一种就是实现Runnable接口。

1.继承Thread类:一个类只要继承Thread类,此类就称为多线程的操作类。在Thread的子类中必须明确覆写
Tread类中的run()方法,此方法为线程的主体。
public class Demo extends Thread{
	private String name;
	public Demo(String name) {
		this.name=name;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<10;i++) {
			System.out.println(name+"运行"+i);
		}
	}
}
public class Test2 {
	
	public static void main(String[] args) {
		Demo demo1=new Demo("线程1");
		Demo demo2=new Demo("线程2");
		demo1.start();
		//demo1.run();
		demo2.start();
	}
}
程序运行结果部分截图:(有很多种结果,由CPU决定,那个线程抢到了CPU资源,哪个线程就可以运行)
线程2运行0
线程1运行0
线程2运行1
线程1运行1
线程2运行2
线程1运行2
线程1运行3
线程2运行3

线程调用的虽然是start()方法,但其实调用的确实run()方法的主体。

为什么启动线程时必须通过start()方法启动,直接调用run()方法呢?
线程的运行需要本机操作系统的支持。
start方法源码
1,start()用来启动一个线程,当调用start()方法时,系统才会开启一个线程,通过Thead类中start()方法来启动的线程处于就绪状态(可运行状态),此时并没有运行,一旦得到CPU时间片,就自动开始执行run()方法。此时不需要等待run()方法执行完也可以继续执行下面的代码,所以也由此看出run()方法并没有实现多线程。
2,run()方法是在本线程里的,只是线程里的一个函数,而不是多线程的。如果直接调用run(),其实就相当于是调用了一个普通函数而已,直接待用run()方法必须等待run()方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用start()方法而不是run()方法。
3,把需要处理的代码放到run()方法中,start()方法启动线程将自动调用run()方法,这个由java的内存机制规定的。并且run()方法必需是public访问权限,返回值类型为void。
4,当程序调用start方法一个新线程将会被创建,并且在run方法中的代码将会在新线程上运行

2.java中也可以通过实现Runable接口的方式实现多线程。Runnable接口中只定义了一个抽象方法。
public void run();

public class Demo implements Runnable{
	private String name;
	public Demo(String name) {
		this.name=name;
	}
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<10;i++) {
			System.out.println(name+"运行"+i);
		}
	}
}
从之前的方式中可以知道,要想启动一个线程必须使用start()方法,如果继承了Tread的类,则可以
直接从Tread类中使用start()方法,但是现在实现的是Runnable接口,那么该如何启动多线程呢。实际上
此时还是依靠Tread类完成启动。在Tread类中提供了public Tread(Runnable target)和
public Tread(Runnable target,String name)两个构造方法。这两个构造方法都可以接收Runnable的
子类实例对象。
public class Test2 {
	
	public static void main(String[] args) {
		Demo demo1=new Demo("线程1");
		Demo demo2=new Demo("线程2");
		Thread t1=new Thread(demo1);
		Thread t2=new Thread(demo2);
		t1.start();
		t2.start();
	}
}
执行结果类似继承Thread,这里就不展示了。
以上两种实现方式,无论使用哪种方式,最终都必须依靠Thread类才能够启动多个线程。

Thread和Runnable接口
通过Tread类和Runnable都可以实现多线程,那么两者有什么区别呢???
下面观察Thread类的定义:
public class Thread extendsObject implements Runnable
从Thread类的定义可以清楚的发现,Thread类也是Runnable接口的子类。
在Thread类中的run方法调用的是Runnable接口中的run方法,也就是说此方法是由Runnable子类完成的,所以如果要通过继承Thread类实现线程,则必须覆写run方法。
实际上,Thread类和Runnable接口之间在使用上也有区别,如果一个类继承Thread类,则不适合多个线程共享资源。而实现Runnable接口就可以方便的实现资源共享。

public class Demo extends Thread{
	private int ticket=8;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++) {
			if(ticket>0) {
				System.out.println("卖票"+ticket--);
			}
		}
	}
}
public class Test2 {
	
	public static void main(String[] args) {
		Demo demo1=new Demo();
		Demo demo2=new Demo();
		/*Thread t1=new Thread(demo1);
		Thread t2=new Thread(demo2);*/
		demo1.start();
		demo2.start();
	}
}
结果:
卖票8
卖票7
卖票6
卖票5
卖票4
卖票3
卖票2
卖票1
卖票8
卖票7
卖票6
卖票5
卖票4
卖票3
卖票2
卖票1
八张票,两个线程卖了两遍。这是不对的吧。

public class Demo implements Runnable{
	private int ticket=8;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++) {
			if(ticket>0) {
				System.out.println("卖票"+ticket--);
			}
		}
	}
}
public class Test2 {
	
	public static void main(String[] args) {
		Demo demo1=new Demo();
		new Thread(demo1).start();
		new Thread(demo1).start();
	}
}
结果:
卖票8
卖票7
卖票6
卖票5
卖票3
卖票2
卖票1
卖票4

很显然两个线程一共卖掉了八张票。
Thread方式一个实例不能调用两遍start()方法。这也是Runnable和Thread方式的明显区别。

Runnable对于Thread有如下优势:
1.适合多个相同的程序代码的线程去处理同一资源的情况。
2.可以避免由java单继承带来的局限性。

线程的状态
要想实现多线程,必须在主线程中创建新的线程对象,线程一般具有五种状态,创建、就绪、运行、阻塞、终止。
创建状态:Demo demo1=new Demo();
就绪状态:调用该线程的start方法,等待CPU服务,已经具备运行条件。
运行状态:调用run方法,执行该线程的操作和功能。
堵塞状态:一个正在执行的线程在某些特殊的情况下,如被人为挂起或需要执行耗时的输入输出操作时,将让出CPU并暂时终止自己的执行,进入阻塞状态。在可执行状态下,如果调用sleep(),suspend(),wait()等方法,线程都将进入阻塞状态。堵塞时,线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。
死亡状态:调用stop()方法时或者run()方法执行结束后,即处于死亡状态。
线程的强制运行
在线程的操作中,可以使用join方法,让一个线程强制运行,线程强制运行期间,其他线程无法运行必须等待此线程完成之后才可以继续运行。
线程的休眠
在程序中允许一个线程进行暂时的休眠,直接使用Thread.sleep()方法实现休眠。
中断线程
一个线程可以直接通过interrupt()方法中断其运行状态
后台线程
在java中,只要前台有一个线程在运行,则整个java进程都不会消失,所以此时可以设置一个后台线程,这样即使java进程结束了,后台线程依然会继续执行,要想实现这样的操作,直接使用setDeamon方法。
线程的优先级
可以使用setPriority()方法设置一个线程的优先级,一共有三种优先级(依次由低到高):
MIN_PRIORITY,NORM_PRIORITY,MAX _PRIORITY
线程的礼让
可以使用yield()方法,将一个线程的操作暂时让给其它线程执行。

同步与死锁
一个多线程的程序如果通过Runnable接口实现,则意味着类中的属性将被多个线程共享,
那么这样一来会造成一种问题,如果这个多线程要操作统一资源,就有可能出现资源同步的问题。
public class Demo implements Runnable{
	private int ticket=8;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++) {
			
			if(ticket>0) {
				try {
					Thread.sleep(300);
				} catch (InterruptedException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
				System.out.println("卖票"+ticket--);
			}
		}
	}
}
public class Test2 {
	
	public static void main(String[] args) {
		Demo deom=new Demo();
		Thread t1=new Thread(deom);
		Thread t2=new Thread(deom);
		Thread t3=new Thread(deom);
		t1.start();
		t2.start();
		t3.start();
	}
}
结果(可能的一种结果)
卖票8
卖票6
卖票7
卖票5
卖票5
卖票4
卖票3
卖票2
卖票3
卖票0
卖票1
卖票1

程序加入了sleep延迟操作,所以会发现卖票重复的情况,这种情况必须要使用同步。
所谓的同步就是指多个操作在同一时间段内只能有一个线程进行,其它线程要等待此线程完成之后才可以继续
执行。
解决资源共享的同步操作,可以使用同步代码块和同步方法两种方式解决

**1.同步代码块**
和其他代码块一样只不过在前面加一个synchronized关键字。
在使用同步代码块时,必须使用一个需要同步的对象,但一般都将当前的对象(this)设置成同步对象。
代码修改:
public class Demo implements Runnable{
	private int ticket=8;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++) {
			synchronized(this){
				if(ticket>0) {
					try {
						Thread.sleep(300);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("卖票"+ticket--);
				}
			}
		}
	}
}

public class Test2 {
	
	public static void main(String[] args) {
		Demo deom=new Demo();
		Thread t1=new Thread(deom);
		Thread t2=new Thread(deom);
		Thread t3=new Thread(deom);
		t1.start();
		t2.start();
		t3.start();
	}
}

运行结果:
卖票8
卖票7
卖票6
卖票5
卖票4
卖票3
卖票2
卖票1

可以看出来,三个线程卖票,没有出现任何异常。

**2.同步方法**
除了将需要的代码设置成同步方法块之外,还可以使用synchronized关键字将一个方法声明为同步方法。
public class Demo implements Runnable{
	private int ticket=8;
	
	@Override
	public void run() {
		// TODO Auto-generated method stub
		for(int i=0;i<50;i++) {
				this.sale();
		}
	}
	public synchronized void sale() {
		if(ticket>0) {
			try {
				Thread.sleep(300);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("卖票"+ticket--);
		}
	}
}

public class Test2 {
	
	public static void main(String[] args) {
		Demo deom=new Demo();
		Thread t1=new Thread(deom);
		Thread t2=new Thread(deom);
		Thread t3=new Thread(deom);
		t1.start();
		t2.start();
		t3.start();
	}
}
运行结果:
卖票8
卖票7
卖票6
卖票5
卖票4
卖票3
卖票2
卖票1

运行结果正常。
死锁

同步可以保证资源共享的正确性,但是过多的同步也会产生问题。例如:现在张三想要李四的画,李四想要张三的书。张三对李四说:把你的画给我,我就给你书。李四对张三说:把你的说给我,我就给你画。此时,张三等着李四的答复,李四也等着张三的答复。这样下去的结果就是互相等待,造成了程序的停滞,这就产生了死锁。
等待和唤醒
从表中可以发现,可以将一个线程设置为等待状态,但是对于唤醒的操作却有两个,分别是notify()和notifyAll()。一般来说,所有的等待线程都会按照顺序进行排列,如果现在使用了notify()方法,则会唤醒第一个等待的线程执行,而如果使用notifyAll()方法,则会唤醒所有的等待线程,那个线程的优先级高,哪个线程就有可能先执行。
解决可能产生的死锁方式----通过加入等待和唤醒

让生产者不重复生产,消费者不重复取走,则可以增加一个标志位,假设标志位为boolean型变量,如果标志位
的内容为true,则表示可以生产。但是不能取走,此时线程执行到了消费者应该等待,如果标志位
内容为false,则表示可以取走,但是不能生产,如果生产者线程运行,则应该等待。
线程的生命周期

有三个方法要介绍:
suspend()暂时挂起线程
resume() 恢复挂起线程
stop() 停止线程
但是这三种方法不推荐使用,因为会产生线程死锁的问题。
Thread类源码也有@Deprecated注解,表示不建议使用

要点:
1.线程是指程序的运行流程,多线程机制可以同时运行多个程序块,使程序运行更加高效,解决了传统设计语言
无法解决的问题。
2.如果在类中激活线程,必须先做好下面两手准备。
 (1)此类必须是拓展自Thread类
 (2)线程的处理必须编写在run()方法之内
 3.run()方法是定义在Thread类中的一种方法,因此把线程的程序代码编写在run()方法内所做的就是覆写
 的操作。
 4.Runnable接口中声明了抽象的run()方法,因此必须在实现Runnable接口的类中明确定义run()方法。
 5.在每一个线程创建和消亡之前,均会处于创建就绪,运行,阻塞,终止状态之一。
 6.暂停状态的线程可由下列情况产生:
 该线程调用对象的wait()时
 该线程本身调用sleep()时
 该线程和另一个线程join在一起时
 7.被冻结因素消失的原因有以下两点:
 如果线程是由调用对象的wait()方法冻结,则该对象的notify()方法被调用时可以解除冻结。
 如果线程是因为sleep进入休眠的,那么时间到了就可以了
 8.当线程的run()方法运行结束,是由线程调用其stop()方法时,线程进入消亡状态。
 9.要强制某一线程运行,可用join()方法。
 10.join()方法会抛出InterruptedException的异常,所以编写时,必须把join方法编写在try,catch
 块中。
 11.当多个线程对象,操纵同一共享资源时,要使用synchrinized关键字来进行资源同步处理。
泛型

类型使用T的形式,当然可以使用其他的字母
表示属性类型由外部决定,不是固定的。
泛型
通配符
受限泛型

在javaAPI中,可以从其中读入一个字节序列的对象称为输入流,而可以向其中写入一个字节序列的对象称为输出流。这些字节序列的来源和目的地可以是文件,而且通常都是文件,但是也可以是网络连接,甚至是内存块。抽象类InputStream和OutputStream构成了有层次结构的输入输出类的基础。
Reader和Writer专门处理字符流的。
读入字节
InputStream有一个抽象方法:abstract int read()
这个方法将读入一个字节,并将读入的字节返回,并且在遇到输入源结尾时,返回-1。
在设计具体输入流类时,必须覆盖这个方法,以提供适用的功能。
输出字节
与此类似,outputStream定义了一个:abstract void write(int b)
它可以向某个输出位置写出一个字节
read和write方法在执行时都将阻塞,直至字节确实被读入或者写出。
这就意味着如果流不能被立即访问(通常是因为网路连接忙),那么当前的线程将被阻塞。这使得在这个方法等待指定的流变成可用的这段时间里,其他的线程就有机会执行有用的工作。
available使我们可以去检查当前我们可用于读入字节的数量,这意味着像下面的这段代码就不会被阻塞。

		int byteAvailable=in.available();
		if(byteAvailable>0) {
			byte[] data=new byte[byteAvailable];
			in.read(data);
		}
		in.close();

当你完成流的读写时,应该通过close方法去关闭它。这个方法会释放掉十分有限的系统资源。
如果一个程序打开了过多了流而没有关闭它们,那么系统资源将被耗尽。关闭一个输出流的同时也就是在清空用于该输出流的缓冲区。之前所有被临时置于缓冲区中的数据,在关闭输出流时都会被送出。如果不关闭文件,那么写出字节的最后一个包可能永远得不到传递。当然我们可以用flush方法来人为的清空这些输出。
组合流过滤器
FileInputStream FileOutStream DataInputStream
可以通过嵌套过滤器添加多重功能
DataInputStream data=new DataInputStream(
new BufferedInputStream(new FileInputStream(“D:\cui\temp\my.txt”)));
我们把DataInputStream置于构造链的最后,这是因为我们希望使用DataInputStream的方法,并且希望他们能够使用带缓冲机制的read方法。
有时当多个流连接在一起的时候,你需要跟踪各个中介流。例如当读入输入时,你经常需要浏览下一个字节,以了解它是否是你想要的值。java提供了用于此目的PushbackInputStream:

PushbackInputStream push=new PushbackInputStream(
		new BufferedInputStream(new FileInputStream("D:\\cui\\temp\\my.txt")));
现在你可以预读一个字节:
int i=push.read();
并且在它并非你所期望的值抛出。
if(i != '<') {
			push.unread(i);
		}
但是读入和不读入都只是应用于可回推输入流的方法,如果你希望可以预先浏览,并且可以读入数字,那么你就需要一个既是可回推输入流,又是一个数据输入流的引用。
DataInputStream data=new DataInputStream(
				pbin=new PushbackInputStream(
						new BufferedInputStream(new FileInputStream("D:\\cui\\temp\\my.txt"))));
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值