《Java核心技术》要点总结:多线程、网络与数据库

多线程

在单独线程中运行任务的简单过程:

1.      定义一个实现了Runnable接口的类,任务代码写到run方法中

2.      创建一个该类的实例

3.      将这个实例传递给Thread类的构造体,实例化一个Thread对象

4.      通过Thread类的start方法开启线程

中断线程:线程会在它的run方法返回时终止

尽管现在已经没有强制终止线程的方法,但是可以用interrupt方法请求终止一个线程;当interrupt方法在一个线程对象上被调用时,该线程的中断状态位将被置为true;为查明一个线程的中断状态,需要首先调用Thread类的静态方法currentThread获取当前线程的对象,然后调用它的isInterrupt方法进行检测;但是,如果一个线程已经被阻塞,对它调用interrupt方法被抛出InterruptedException

请求被中断的线程并不一定需要被终止;检测到中断状态位被置为true之后应该根据实际情况进行处理

对于InterruptedException更好的处理方法是将它抛出给更高层次的代码处理,而不是将它抑制在低层次

线程状态:线程可以有如下4种状态

1.      New:通过new创建线程对象后,线程开始运行之前,线程进入“新生”态,可以进行一些簿记工作

2.      Runnable:一旦调用了start方法,线程就进入了“可运行”态,开始执行run方法中的代码,但实际是否在运行取决于操作系统的线程调度

3.      Blocked:当发生如下任何一种情况时,线程进入“阻塞”态:

1)        线程通过调用sleep方法进入睡眠

2)        线程调用了一个在I/O上被阻塞的操作

3)        线程试图得到一个锁,而该锁正被其它线程持有

4)        线程在等待某个触发条件(参见Synchronized关键字)

5)        (已废弃)suspend方法被调用

通过如下途径中的一种,线程可从阻塞态回到可执行态:

1)        处于睡眠的线程已经经过了指定的睡眠时间

2)        线程正在等待的I/O操作已经完成

3)        其它线程释放了本线程正在等待的锁(或因为超时线程自动解除阻塞)

4)        其它线程发出信号表明本线程正在等待的触发条件已经变化(或因为超时线程自动解除阻塞)

5)        (已废弃,针对suspend)线程被挂起后,它的resume方法被调用

4.      Dead:以下情况会导致线程“死亡”:

1)        run方法执行结束,正常退出

2)        因为一个未捕获的异常导致run方法终止

3)        (已废弃)调用stop方法杀死线程

4)        调用join方法可以使线程等待直到死亡

调用线程的isAlive方法检测线程是否存活:可运行态和阻塞态返回true,新生态和死亡态返回false

线程属性:线程优先级:每个线程都有一个优先级,默认情况下,线程会继承它父线程的优先级;setPriority方法可以设置线程优先级,最小为MIN_PRIORITY(1),最大为MAX_PRIORITY(10),另外常量还有NORM_PRIORITY(5);操作系统的线程调度会优先处理高优先级的线程;静态方法yield会使当前线程进入让步状态,让优先级不低于它的其它线程优先被调度

守护线程:调用t.setDaemon(true)会使线程t变成守护线程;守护线程唯一的作用是为其它线程提供服务;当只剩下守护线程时,程序终止

线程组:线程组允许同时对一组线程进行操作

ThreadGroup类的构造体中的字符串参数用来标识该组,必须是唯一的;创建新线程后为它指定线程组就可以将它添加到线程组中;ThreadGroup类的activeCount可以检查该线程组中可运行态的线程个数;对线程组的操作可以操作它里面的所有线程

未捕获异常处理器:线程的run方法不能抛出异常;线程因为未捕获的异常而死亡前,异常会被自动传递给未捕获异常处理器;未捕获异常处理器必须是实现了Thread.UncaughtExceptionHandler接口的类,并通过setUncaughtExceptionHandler方法设置

同步:Java中有两种机制来保护代码块不受并行访问的干扰:

1.      ReentrantLock类:结构形如:

myLock.lock();

try {

           …

}

finally {

           myLock.unlock();

}

其中myLock是ReentrantLock类的实例,结构中lock方法和unlock方法之间的代码会被加上同步锁;使用try/finally是为了即便是抛出异常也一定要执行unlock,以避免死锁,没异常的情况下可不用

锁是可重入的(线程可以重复获取它已经拥有的锁);锁对象维护一个持有计数来追踪对lock方法的嵌套调用(lock加1,unlock减1),当持有计数为0时,锁被释放

一个锁对象可以用一个或多个相关联的条件(Condition)对象;可以通过ReentrantLock类的newCondition方法获取一个新的条件对象;条件对象调用await方法可以使持有锁的这个线程进入阻塞态并且放弃锁,它维持阻塞直到另一个线程调用同一个条件对象的signalAll方法;signalAll方法将解除所有等待此条件的线程的阻塞态,这些线程将再次竞争锁,一旦获得锁,该线程将从await之后继续执行;另外还有signal方法可以随机地解除等待这个条件的线程中的一个的阻塞

一旦一个线程因为await进入阻塞态,它就必须等待别的线程调用signalAll或signal方法来解除它的阻塞,否则它将永远不会再运行了(死锁);如果最后一个活跃线程在解除别的线程的阻塞前就调用了await方法,则整个程序将被挂起

总结:

1)        锁用来保护代码片段,任何时候只允许一个线程执行被保护的代码

2)        锁可以管理试图进入被保护代码段的线程

3)        锁可以拥有一个或多个相关的条件对象

4)        每个条件对象管理那些已进入被保护代码段但不能运行的线程

2.      Synchronized关键字:隐式的锁;线程通过两种方法获得Synchronized锁:调用一个同步方法或进入一个同步块

同步方法:用Synchronized关键字修饰一个方法,那么对象的锁将保护整个方法;隐式对象锁只有一个关联条件,Object类的wait方法会把线程加到等待集中,notify和notifyAll方法可以解除等待线程的阻塞态

同步块:形如:

Synchronized(obj) {

           …

}

其中obj可以是一个无意义的Object对象,也可以当前方法需要操作的对象(如this)

volatile关键字修饰的域将在被同步访问时提供一个“免锁”机制,即编译器和虚拟机都知道该域可能会被或正在被另一个线程并发更新(需同步检查)

如下条件有其一,则对一个域的并行访问是线程安全的:

1.      该域是volatile的

2.      该域是final的,并且已经被赋值

3.      对域的访问有锁保护

公平锁:创建ReentrantLock时,将它的构造体参数设为true,则创建了一个公平锁对象,它会优待那些等待了最长时间的线程,但是会大大影响性能;默认锁都不是公平的

ReentrantLock类提供了tryLock方法来尝试加锁,成功则返回true,否则返回false,以避免lock方法不时带来的线程阻塞;这个方法会抢夺任何可用的锁,即便当前的ReentrantLock对象被设置为公平的;tryLock和await的重载都有设置超时参数的形式,超时后等待线程的阻塞将被解除

读/写锁ReentrantReadWriteLock适用于多个线程都从某一数据结构中读取数据而很少线程对其进行修改的时候;它的使用必要步骤:

1.      创建ReentrantReadWriteLock对象

2.      抽取读锁对象(readLock方法)和写锁(writeLock方法)对象

3.      对代码段加读锁或写锁,类似ReentrantLock的结构

其中读锁排斥所有写操作,写锁排斥所有其它的读操作和写操作

阻塞队列:在多线程进行合作时,工作者线程可以定期将中间结果存放到阻塞队列中,其它线程可以在工作的时候可以把中间结果取出并修改;试图向一个满的阻塞队列中添加新元素,或从空的阻塞队列中移除元素时,将导致线程阻塞

阻塞队列自动进行同步,线程安全

java.util.concurrent包中提供了四种阻塞队列,它们都是实现了BlockingQueue<E>接口的泛型类:

1.      LinkedBlockingQueue<E>,默认下容量没有上限,但可以指定

2.      ArrayBlockingQueue<E>,必须指定容量,并可以是否需要公平性

3.      PriorityBlockingQueue<E>,带优先级的阻塞队列,进出按优先级顺序,容量无上限

4.      DelayQueue<E extendsDelayed>,必须包含实现了Delayed接口的对象

线程安全的集合:ConcurrentLinkedQueue,ConcurrentHashMap

CopyOnWriteArrayList,CopyOnWriteArraySet

Vector,HashTable

Callable和Future:Callable接口和Runnable类似,但它有返回值,它是泛型化的接口(Callabl<V>,其中V就是它的唯一方法call的返回值类型)

Future接口也是泛型化的接口;它具有两个get方法:无参的get方法会被阻塞直至计算完成,有参的get方法能设置超时时间;此外还有一些检测状态的方法

实际应用中,只需要实现Callable接口的类,然后将该类的对象传递给FutureTask包装器类的构造体,就能创建一个结合了Runnable和Future接口功能的新对象;FutureTask类的实例可能替换Thread中实现了Runnable接口的对象;另外,FutureTask类也提供了Future接口中的方法

执行器:如果程序需要大量生存期限很短的线程,则应该使用线程池,以减少创建新线程的开销;线程池中包含大量准备运行的空闲线程,当Runnable对象传递给线程池时,线程池中的一个线程自动调用run方法;当run方法退出时,该线程回到线程池中准备下一次使用

另一个使用线程池的原因是减少并发线程的数量,通过使用一个线程数“固定”的线程池来实现

执行器Executor类有大量用来构建线程池的静态工厂方法:

1.      newCachedThreadPool:构建一个线程池,对于每个任务,如果有空闲线程可用则立即执行它,否则创建一个新线程;空闲线程将被保留60秒

2.      newFixedThreadPool:创建一个大小固定的线程池;若任务数大于空闲线程数,则得不到执行的任务将被置于队列中等待其他任务完成

3.      newSingleThreadExecutor:创建一个容量为1的线程池,所有任务串行执行

上述三种方法返回一个实现了ExecutorService接口的ThreadPoolExecutor类的对象;该对象提供3个重载的submit方法来将Runnable或Callable对象提交给ExecutorService,并返回Future对象

使用完线程池之后需要调用shutdown方法,该方法启动池的关闭序列,使池不再接收新的任务,并在现有的线程都死亡后关闭池;还可以调用shutdownNow方法,池会取消还没开始的任务并试图中断池中可运行态的线程

4.      newScheduledThreadPool

5.      newSingleScheduledThreadExecutor

上述两个方法返回一个实现了ScheduledExecutorService接口的对象;该接口具有为预订或重复执行任务而设计的方法,可以预定Runnable或Callable在初始延迟后只运行一次,也可以预定一个Runnable对象周期性运行;详见Java API

控制线程组:实现了ExecutorService接口的线程池对象的invokeAny和invokeAll方法可以处理一组Callable接口的任务,详见Java API

同步器:同步器是一些为线程之间的共用集结点提供“预置功能”的类;集结点指线程调用的一个聚集地,每个线程都将结果汇聚于此,形成完整的结果;线程集如果达到可处理的集合点中的一种,就可以直接重用合适的类,而不是手动维护一个锁集合

1.      障栅:CyclicBarrier类实现了一个称为“障栅”的集合点:当大量线程运行在一次计算的不同部分时,所有部分都处理完之后需要被整合;障栅存在于最后的整合之前,每个线程完成它的任务后,运行到障栅处等待,所有线程都到达障栅处时,障栅被撤销,线程可以继续运行;步骤:

1)        创建一个障栅类CyclicBarrier的对象,在构造体参数中设置线程数

2)        每个线程任务(Runnable的run方法中)的最后,障栅对象调用它的await方法

2.      倒计时门栓:CountDownLatch类让线程集等待直至计数器减少(coutDown方法)为0,用于多个线程需要等待直至指定数量的结果可用的情形

3.      交换器:Exchanger类允许两个线程在要交换的对象准备好时交换对象,可用于当两个线程工作在同一个数据结构的两个实例上的时候

4.      同步队列:同步队列是一种生产者和消费者线程配对的机制;当一个线程调用SynchronousQueue的put方法来传递某个实例时,它会被阻塞直到另一个线程调用take方法取走这个实例

5.      信号量:信号量Semaphore类管理大量的许可证,可以通过acquire方法申请,也可以通过release方法释放(但不需要申请它的线程来释放);拥有许可证的线程代码才能被执行,否则所在的行程被阻塞直至获得许可证;许可证的数量就是限制通过的线程数

总的来说,五种同步器的类就是在线程的任务代码中添加特定的等待条件(集合点),若条件满足,则继续执行,否则线程被阻塞直至条件被满足;每个条件同时提供了更新当前状态的方法(使线程汇聚到集合点的途径)

 

网络

套接字:socket,网络软件中的一个抽象概念,负责使程序内部和外部之间的通信;Socket类通过传入的远程地址和端口号来创建对象

服务器套接字:ServerSocket,通过传入构造体的端口号来创建对象,负责监听该端口的网络数据

ServerSocket类accept方法可以阻塞调用这个方法的线程,直到有客户端的连接到这个端口,此时该方法返回一个表示该客户端的Socket对象

输入输出流:Socket类的getInputStream方法和getOutputStream方法可以得到与从远程客户端读取数据的输入流和向远程客户端写入数据的输出流;写入数据时,print类型的方法比write方法更有效;与流使用结束后需要close一样,Socket使用完后也应该close

为多个客户端服务:accept方法每返回一个新连接的客户端的Socket对象时,新开启一个线程来专门处理这个Socket对象的数据读取与写入

发送Email:构造Socket对象时的参数分别是邮箱服务器的地址(String)和端口号25(int,25是SMTP协议的系统固定端口号);向输出流中写入邮件数据时应按照SMTP规范;JavaMail API中提供了一个静态方法Transport.send(String)可以用于发送Email

URI与URL:URI(统一资源标识符)是个纯粹的语法结构,用于指定标识Web资源的字符串的各个不同的部分;URL(统一资源定位符)是URI的一个子集,它只包含了定位Web资源的足够信息;非URL的URI一般称为URN(统一资源名称);通过它并不能定位到资源的确切位置

Java中,URI类不包含任何用于访问资源的方法,它唯一的作用就是对资源的URI字符串进行解析;而URL类能打开到达一个Java类库知道如何处理的资源(http:、https:、ftp:、file:、jar:等)的流或连接

建立URL连接:URL类通过传入的url字符串来初始化对象;URL类中的openStream方法可以直接打开一个访问该资源“内容”的输入流

如果需要获得该资源的更多信息,则应该使用URL类中的openConnection方法创建一个URLConnection对象;之后可以通过URLConnection中的set类型方法设置访问请求的类型;再调用connect方法连接上资源;连接建立后就可以通过URLConnection的get类型方法获得该资源的各种信息了(其中getInputStream可以得到与URL类的openStream方法相同的内容输入流)

提交表单数据:首先通过URL建立一个URLConnection对象;设置该对象的setDoOutput方法为true(默认为false);然后通过getOutputStream方法获得向资源所在服务器写入数据的输出流;调用输出流的print方法,按照name=URLEncoder.encode(value, “UTF-8”)的格式写入数据,多个数据间用“&”分隔;最后关闭输出流,同时打开一个输入流来读取服务器的响应信息

套接字超时:可以通过调用Socket类的setSoTimeout(int)方法设置套接字的超时时间,若之后的读写操作在没有完成前就超过了时间限制,则会抛出SocketTimeoutException,捕获该异常就可以对超时做出响应的处理

此外,默认的构造体Socket(Stringhost, int port)实际上会阻塞当前线程直到连接建立;为了避免阻塞,可以先构造一个无连接的套接字,然后再使用一个超时来进行连接,如下:

Socket s = new Socket();

s.connect(new InetSocketAddress(host, port), timeout);

可中断的套接字:线程因套接字长时间无法响应而阻塞时,是无法通过interrupt来中断的;为了中断套接字操作,java.nio包提供了一个SocketChannel类,该类通过静态方法open绑定InetSocketAddress对象来创建通道(channel);通道与流无关,但可以通过实现自ReadableByteChannel接口和WritableByteChannel接口的read和write方法调用Buffer对象来实现数据的读写;任意时刻,通过close该通道,就可以中断这个套接字

半关闭:通过关闭一个套接字的输出流(shutdownOutput方法)来表示发送给服务器的请求数据已结束,但是保留输入流打开来读取服务器返回的响应信息,以此来告诉服务器请求已经结束但套接字并未关闭,适用于一站式的网络服务

因特网地址:InetAddress类可以转换主机名与因特网IP地址,该类的对象可以通过静态方法getByName(String host)创建

 

数据库编程

建立JDBC连接:

1.      将数据库JDBC驱动程序所在的库添加到编译环境中(修改CLASSPATH环境变量,或将驱动程序包放到jre/lib/ext中)

2.      在驱动管理器中注册驱动程序,可以通过System.setProperty(“jdbc.drivers”, “com.mysql.jdbc.Driver”),也可以通过Class.forName(“com.mysql.jdbc.Driver”)

3.      通过DriverManager打开连接:DriverManager.getConnection(url, username, password);其中url是数据库的URL,username是数据库的账户名,password是密码;返回一个Connection对象

执行SQL命令:Connection对象的方法createStatement可以创建一个Statement对象,该对象中的:

1.      int executeUpdate(String sql)方法可以执行INSERT、DELETE、UPDATE操作,并返回影响的行数

2.      ResultSet executeQuery(Stringsql)方法可以执行SELECT操作,返回的ResultSet对象中包含了查询结果的表

3.      boolean execute(String sql)方法可以执行任意的SQL语句,但通常用于交互式查询

ResultSet对象:ResultSet对象可以通过next方法迭代每一行,在每一行中都可以通过getXXX(int)(如getDouble(3)等)方法访问对应列(从1开始,而非0)的数据

Connection、Statement、ResultSet的关闭:用完它们时,应该调用close方法予以关闭

异常处理:通常采用如下的代码风格以保证异常的正确处理:

Connection conn = …;

try {

         …

} finally {

         conn.close();

}

预备语句:预备一个带有宿主变量的查询语句,每次查询的时候只需为该变量填入不同的字符串就可以反复多次地使用该语句;宿主变量用“?”表示;执行预备语句之前,必须使用相应的setXXX(int position, XXX value)方法将变量绑定到正确的值上(如setString(1,“CHN”)),其中position数值从1开始

预备语句PreparedStatement对象通过Connection的prepareStatement(Stringsql)方法获得;它也包含了无参的int executeUpdate()和ResultSet executeQuery()方法执行SQL操作

可滚动和可更新的结果集:查询对象和预备语句对象在被Connection创建时都有设置属性的重载方法(createStatement(int type, int concurrency)和prepareStatement(Stringsql, int type, int concurrency)),其中type设置结果集ResultSet可否滚动,concurrency设置结果集可否更新数据库;二者的取值都是ResultSet中的公有常量,如下:

type:TYPE_FORWARD_ONLY:默认,结果集不能滚动

         TYPE_SCROLL_INSENSITIVE:可滚动,但不随数据库变化而变化

         TYPE_SCROLL_SENSITIVE:可滚动,且随数据库变化而变化

concurrency:CONCUR_READ_ONLY:默认,修改结果集不能更新数据库

                            CONCUR_UPDATABLE:修改结果集可同步修改数据库

可滚动的结果集ResultSet除了有next方法向前遍历外,还有previous方法向后遍历,有relative(int)方法可以按当前位置向前(为正)或向后(为负)移动若干个,有absolute(int)方法指定移动到某行上,还有其它一些默认情况下不可用的方法(如first、last等)

可修改的结果集可以调用ResultSet类中提供的修改当前结果集对象内部数据的方法,如updateString、updateDouble等,但需要注意的是,必须指定列的名称或序号(从1开始),最后需要调用updateRow方法向数据库提交;同样的,insertRow和deleteRow也是向数据库提交插入行和删除行的操作

元数据:通过Connection的getMetaData方法可以获得DatabaseMetaData对象,该对象保存了数据库的结构信息,如全部表的名字(getTables方法获得)等,主要用于构建数据库管理工具

行集:结果集ResultSet使用期间,程序必须与数据库保持连接(即Connection不能close);一旦Connection关闭,ResultSet也随之不可用;行集与结果集用于同样的方法和功能,但不需要始终跟数据库保持连接;行集的具体类视实现的库而定,略

事务:一组SQL语句操作构建成一个事务;当所有的语句都顺利执行后,事务被提交;否则,一旦某个语句出现错误,事务必须回滚

JDBC默认下,数据库处于自动提交模式(即每条SQL执行后都立即自动commit),无法进行事务处理;进行事务处理的步骤是:

1.  调用Connection对象的setAutoCommit(false),关闭自动提交模式

2.  Statement对象或PreparedStatement对象执行若干次executeUpdate方法后

3.  调用Connection对象的commit方法,提交事务

4.  若期间出现错误或异常(SQLException),调用Connection对象的rollback方法进行回滚

通常情况下,回滚会从事务的开头开始,即上一次调用commit之后;可以通过设置保存点Savepoint对象来手动设置(通过Connection对象的setSavepoint方法创建)回滚开始的点(调用rollback(Savepoint)方法);操作结束后,需要调用Connection对象的releaseSavepoint(Savepoint)释放保存点

批处理:Statement对象的addBatch(String sql)方法可以将一组SQL语句逐一添加到一个批处理序列中去;添加完成后,调用executeBatch方法可以一次性执行该序列中所有的SQL语句,并返回一个整型数值以记录每条语句影响的行数;注意若是在批处理中执行SELECT操作将会抛出异常

高级连接管理:若数据库连接管理与JNDI集成在一起,则不再使用DataManager来创建数据库连接,Connection对象通过JDNI指定的资源类创建,如下例:

Context jndiConext = new InitialContext();

DataSource source = (DataSource)jndiContext.lookup(…);

Connection conn = source.getConnection();

常用于将数据库连接与JNDI集成在一起的JavaEE环境中

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值