多线程编程:
一个正在运行的程序通常称为一个进程,每一个任务称为一个线程,中能够在一个程序内运行多线程的程序称为多线程程序。
线程与进程的区别:①每个进程都需要操作系统为其分配独立的内存空间;
②而同一个进程中的所有线程都在同一内存空间工作,这些线程可以共享同一块内存和系统资源。
线程的创建:
1)通过继承Thread类来创建线程:
①创建一个继承Thread类的类;
②在创建的Thread子类中重写run()方法,在方法中写入想要线程运行的代码;
③创建Thread子类的实例;
④通过调用该实例上的start()方法,开始运行线程。
2)通过实现Runnable接口来创建线程:
①创建一个类实现Runnable接口,用于代表我们需要线程完成的任务;
②在Runnable指定的方法内放入想要在线程中执行的代码。
③创建一个Runnable类的实例;
④创建一个Thread对象,将Runnable的实例作为构造器参数传进去;
⑤通过调用Thread类的实例的start()方法,开始执行线程。
两种方式的联系与区别:
联系:Thread类实现了Runnable接口,也就是说Thread类也是Runnable接口的一个子类;
区别:实现Runnable接口的方式相对于继承Thread类的方式来说有几个显著优势
①使用Runnable接口可以将虚拟CPU(Thread类)与线程要完成的任务有效分离,较好的的体现了面向对象设计的基本原则;
②可以避免Java单继承的局限。
线程的优先级:
线程运行的顺序并不是以该线程创建的先后顺序而定的,而是以优先级来决定。
除了使用sleep()方法让线程暂停运行一段时间外,还可以使用yield()方法让线程停止运行。
线程同步:
当两个以上的线程需要访问共享资源时,我们必须确定在同一时间点只有一个线程能够存取共享资源,而运行这个目标的过程就称为同步。
1.同步块:Synchronized ( 取得锁的对象 ){
//需要锁定的代码
}
1)当某个线程在运行这个块时,别的线程无法使用这个块的代码;
2)当一个线程取得“对象锁”时,如果其他线程也想要取得该对象锁,它们必须等到对象锁被释放。
2.同步方法:eg: Synchronized void PrintValue(){
System.out.println();
}
线程生命周期:
线程状态转换图:
1、新建状态
用new关键字和Thread类或其子类建立一个线程对象后,该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间,通过调用start方法进入就绪状态(runnable)。
注意:不能对已经启动的线程再次调用start()方法,否则会出现java.lang.IllegalThreadStateException异常。
2、就绪状态
处于就绪状态的线程已经具备了运行条件,但还没有分配到CPU,处于线程就绪队列(尽管是采用队列形式,事实上,把它称为可运行池而不是可运行队列。因为cpu的调度不一定是按照先进先出的顺序来调度的),等待系统为其分配CPU。等待状态并不是执行状态,当系统选定一个等待执行的Thread对象后,它就会从等待执行状态进入执行状态,系统挑选的动作称之为“cpu调度”。一旦获得CPU,线程就进入运行状态并自动调用自己的run方法。
提示:如果希望子线程调用start()方法后立即执行,可以使用Thread.sleep()方式使主线程睡眠一伙儿,转去执行子线程。
3、运行状态
处于运行状态的线程最为复杂,它可以变为阻塞状态、就绪状态和死亡状态。
处于就绪状态的线程,如果获得了cpu的调度,就会从就绪状态变为运行状态,执行run()方法中的任务。如果该线程失去了cpu资源,就会又从运行状态变为就绪状态。重新等待系统分配资源。也可以对在运行状态的线程调用yield()方法,它就会让出cpu资源,再次变为就绪状态。
当发生如下情况是,线程会从运行状态变为阻塞状态:
①、线程调用sleep方法主动放弃所占用的系统资源
②、线程调用一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
③、线程试图获得一个同步监视器,但更改同步监视器正被其他线程所持有
④、线程在等待某个通知(notify)
⑤、程序调用了线程的suspend方法将线程挂起。不过该方法容易导致死锁,所以程序应该尽量避免使用该方法。
当线程的run()方法执行完,或者被强制性地终止,例如出现异常,或者调用了stop()、desyory()方法等等,就会从运行状态转变为死亡状态。
4、阻塞状态
处于运行状态的线程在某些情况下,如执行了sleep(睡眠)方法,或等待I/O设备等资源,将让出CPU并暂时停止自己的运行,进入阻塞状态。
在阻塞状态的线程不能进入就绪队列。只有当引起阻塞的原因消除时,如睡眠时间已到,或等待的I/O设备空闲下来,线程便转入就绪状态,重新到就绪队列中排队等待,被系统选中后从原来停止的位置开始继续运行。有三种方法可以暂停Threads执行:
5、死亡状态
当线程的run()方法执行完,或者被强制性地终止,就认为它死去。这个线程对象也许是活的,但是,它已经不是一个单独执行的线程。线程一旦死亡,就不能复生。 如果在一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
推荐在run方法中使用控制循环条件的方式来结束一个线程。
wait:告诉当前线程放弃对象锁并进入等待状态,直到其他线程进入同一对象锁并调用notify为止。
notify:唤醒同一对象锁中调用wait的第一个线程。
notifyAll:唤醒同一对象锁中调用wait的所有线程,具有最高优先级的线程首先被唤醒并执行。
补充:
线程睡眠——sleep
如果我们需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,则可以通过调用Thread的sleep方法
线程让步——yield
yield()方法和sleep()方法有点相似,它也是Thread类提供的一个静态的方法,它也可以让当前正在执行的线程暂停,让出cpu资源给其他的线程。但是和sleep()方法 不同的是,它不会进入到阻塞状态,而是进入到就绪状态。yield()方法只是让当前线程暂停一下,重新进入就绪的线程池中,让系统的线程调度器重新调度器重新调度 一次,完全可能出现这样的情况:当某个线程调用yield()方法之后,线程调度器又将其调度出来重新进入到运行状态执行。实际上,当某个线程调用了yield()方法暂停 之后,优先级与当前线程相同,或者优先级比当前线程更高的就绪状态的线程更有可能获得执行的机会,当然,只是有可能,因为我们不可能精确的干涉cpu调度程。
线程的完整的生命周期:
图片说明:
1、线程的实现有两种方式,一是继承Thread类,二是实现Runnable接口,但不管怎样,当我们new了这个对象后,线程就进入了初始状态;
2、当该对象调用了start()方法,就进入可运行状态;
3、进入可运行状态后,当该对象被操作系统选中,获得CPU时间片就会进入运行状态;
4、进入运行状态后情况就比较复杂了:
①run()方法或main()方法结束后,线程就进入终止状态;
②当线程调用了自身的sleep()方法或其他线程的join()方法,就会进入阻塞状态(该状态既停止当前线程,但并不释放所占有的资源)。当sleep()结束或join()结束后,该线 程进入可运行状态,继续等待OS分配时间片;
③线程调用了yield()方法,意思是放弃当前获得的CPU时间片,回到可运行状态,这时与其他进程处于同等竞争状态,OS有可能会接着又让这个进程进入运行状态;
④当线程刚进入可运行状态(注意,还没运行),发现将要调用的资源被synchroniza(同步),获取不到锁标记,将会立即进入锁池 状态,等待获取锁标记(这时的锁池里也 许已经有了其他线程在等待获取锁标记,这时它们处于队列状态,既先到先得),一旦线程获得锁标记后,就转入可运行状态,等待OS分配CPU时间片;
⑤当线程调用wait()方法后会进入等待队列(进入这个状态会释放所占有的所有资源,与阻塞状态不同),进入这个状态后,是不能自动唤 醒的,必须依靠其他线程调用 notify()或notifyAll()方法才能被唤醒(由于notify()只是唤醒一个线程,但我们由不能确定具体唤醒的是哪一个线程,也许我们需要唤醒的线程不能够被唤醒,因此在实 际使用时,一般都用notifyAll()方法,唤醒有所线程),线程被唤醒后会进入锁 池,等待获取锁标记。
Java I/O系统:
1. Java类库提供了大量类,可以帮助我们从不同的设备读取数据,并保存或输出到不同的设备中。这些类同一放在java.io包和java.nio包中,统称为java I/O系统。
2. 流:我们可以将流理解为传送数据的管道。
流是java I/O的基础,是java语言对 I/O 的一个最基本的抽象,其原因:流的两个最基本特性:①含有流质。流质就是数据;
②有方向。方向就是读或写,即针对设备进行信息的输入和输出。
流的分类:
1)输入流和输出流——按流的方向分
输入流:从数据源读取数据到程序中。只能读不能写。io包中的输入流都继承自抽象类OutputStream或Reader 。
输出流:将数据从程序写入数据目的地。只能写不能读。io包中的输出流都继承自抽象类InputStream或Writer 。
2)字节流和字符流——流按处理数据的最小单位的不同分
字节流:以byte为最小单位进行数据传送。io包中的字节流都继承自抽象类InputStream或OutputStream 。
字符流:以char为最小单位进行数据传送。io包中的字节流都继承自抽象类Reader 或Writer 。
3)节点流和处理流——按流的功能分
节点流:又叫低级流,是可以直接 从 / 向 一个特定的数据源 读 / 写 数据的流 。
处理流:又叫高级流,处理流不能直接连接到设备,而是连接在已存在的流之上,通过对数据的处理为程序提供更强大的 读 / 写 功能 。
如何分辨高级流、低级流: 如果类的构造器带有一个已存在的流作为参数,那它就是高级流 。
3. I/O库中类具有对成性:1)输入流和输出流的对称;
2)字节流和字符流的对称。
I/O操作步骤:
①建立流;
②操作流;
③关闭流。
4. 文件类:将File对象看成是代表一个文件或目录的名称和位置的字符串。
File类中的常用方法:
boolean canExecute() 测试应用程序是否可以执行此抽象路径名表示的文件。
boolean canRead() 测试应用程序是否可以读取此抽象路径名表示的文件。
boolean canWrite() 测试应用程序是否可以修改此抽象路径名表示的文件。
int compareTo(File pathname) 按字母顺序比较两个抽象路径名。
boolean createNewFile() 当且仅当不存在具有此抽象路径名指定名称的文件时,不可分地创建一个新的空文件。
static File createTempFile(String prefix, String suffix) 在默认临时文件目录中创建一个空文件,使用给定前缀和后缀生成其名称。
static File createTempFile(String prefix, String suffix, File directory) 在指定目录中创建一个新的空文件,使用给定的前缀和后缀字符串生成其名称。
boolean delete() 删除此抽象路径名表示的文件或目录。
void deleteOnExit() 在虚拟机终止时,请求删除此抽象路径名表示的文件或目录。
boolean equals(Object obj) 测试此抽象路径名与给定对象是否相等。 boolean exists() 测试此抽象路径名表示的文件或目录是否存在。
String getName() 返回由此抽象路径名表示的文件或目录的名称。
boolean isAbsolute() 测试此抽象路径名是否为绝对路径名。
boolean isDirectory() 测试此抽象路径名表示的文件是否是一个目录。
boolean isFile() 测试此抽象路径名表示的文件是否是一个标准文件。
boolean isHidden() 测试此抽象路径名指定的文件是否是一个隐藏文件。
long lastModified() 返回此抽象路径名表示的文件最后一次被修改的时间。
long length() 返回由此抽象路径名表示的文件的长度。
String[] list() 返回一个字符串数组,这些字符串指定此抽象路径名表示的目录中的文件和目录。
Boolean mkdir() 创建目录。
5. 字节流:用于处理二进制文件
java.io.InputStream类中的常用方法:
int available() 返回可从该输入流中读取的字节数
void close() 关闭输入流
int read( byte[] b )从输入流中读取一些字节,并将这些字节存入缓冲数组 b ,读取完毕返回 -1
int read( byte[] b, int off, int len )从输入流制定位置off开始读取数据的前len个字节,并将其读入字节型数据中 ,读取完毕返回 -1
5. 字符流:用于处理文本文件
Reader: BufferedReader
CharArrayReader
FilterReader
InputStreamReader
PipedReader
StringReader
Writer: BufferedWriter
CharArrayWriter
FilterWriter
InputStreamWriter
PipedWriter
StringWriter
PrintWriter
5. 字符流:基于对象的读写,由ObjectInputStream / ObjectOutputStream来提供
读写对象:①对应的类必须实现java.io.Serializable接口;
②类被序列化之后通过ObjectInputStream / ObjectOutputStream对对象进行读写。
在定义类是用 transient 修饰属性则该属性不能被实例化。这样有利于保护数据安全性。
实现接口后使用ObjectInputStream 类将对象拆成字节序列的过程称为序列化;使用 ObjectOutputStream 类将字节序列还原称对象的过程称为反序列化。
Java图形界面编程:
1.Java API中提供了两套组件用于支持编写图形用户界面,包括AWT 和 Swing。
AWT: 定义在你java.awt包中
① AWT严重依赖于下层操作系统——外观取决于程序运行的平台;
② 效率低,AWT组件也被称为重量级组件;
③ 开发速度较快。
Swing:
①轻量级GUI组件;
②有更好的平台无关性和可移植性;
2.容器和组件:Swing视图部分由两个基本元素组成:
①组件:独立的可视化控件,例如按钮、文本字段;
②容器:一种设计用放置其他组件的特殊类型的组件。
一个组件要显示出来,就必须放在一个容器内。因此所有的Swing GUI组件必须至少有一个组件。组件三要素:内容、外观显示、行为。
add():将组件添加到该容器中;
removeAll():移掉容器中的全部组件;
remove( Component c ):移掉容器中参数指定的组件;
组件在容器中的布局:
1)FlowLayout管理器(JPanel默认):流式布局管理器,组件以从左到右的流式顺序布局
三个构造器:public FlowLayout()
public FlowLayout( int align ) align为对其方式:FlowLayout.CENTER、FlowLayout.RIGHT、LEFT
oublic FlowLayout( int align, int hgap, int vgap ) hgap:水平间距, vgap:垂直间距
2)BorderLayout管理器(JFrame默认):边框布局管理器,将容器分为东南西北中五个区域。每个区域只能添加一个组件,组件大小由区域决定
二个构造器:public Borderayout()
oublic Borderayout( int hgap, int vgap ) hgap:水平间距, vgap:垂直间距
3)GridLayout管理器:网格布局管理器,将容器分为行和列的网格
三个构造器:public GridLayout()
public GridLayout( int rows, int cols )
oublic GridLayout( int rows, int cols, int hgap, int vgap ) rows:行数目, cols:列数目
4)BoxLayout管理器:盒式布局管理器,组件按照垂直或水平方式显示
一个构造器:public GridLayout( Container target, int axis )为指定目标和给定的轴创建一个新的BoxLayout管理器
axis:BoxLayout.X_AXIS:沿x轴方向从左到右水平放置;
BoxLayout.Y_AXIS:沿y轴方向从上到下放置。
3.事件驱动编程:
1)事件和事件源
①事件:用于描述发生了什么事;
事件源:生成一个事件并触发它的组件被称为事件源。
②如果一个组件可以触发一个事件,那么该组件的任何子类都可以触发相同类型的时间。
2)时间监听器、注册和处理事件
成为事件监听器:
①创建一个监听对象,监听对象必须是对应事件监听器接口的实例;
②将监听对象注册到事件源上,注册方法取决于事件类型。通常对于Xxx Event,对应的注册方法为addXxxListener.
3)定义监听器类的可选方法:
①让GUI程序本身实现监听器接口;
②使用成员内部类定义监听器类;
③使用匿名内部类定义监听器类。