Preface:
1 线程和进程的区别联系
进程:应用程序的执行实例
线程:CPU调度和分派的基本单位
区别
联系
2 并发 交替占用 并行
3 创建线程的方式
1)extends java.lang.Thread (class)
2)implements java.lang.Runnable (interface)
4 线程状态5 state
5 线程调度的函数
multithreaded
可以同时运行一个以上线程的程序称为multithreaded
多进程与多线程的一些区别:
每个进程拥有自己的一套变量,线程共享数据
共享变量使线程之间的通信比进程之间的通信更有效、更容易
在有的操作系统中,线程相较于进程更“轻量级”,创建、撤销一个线程比启动新进程的开销要小得多
线程:
P638
java.lang.Thread 1.0
static void sleep(long millis)休眠给定的毫秒数
参数:millis 休眠的毫秒数
使用线程给其他任务提供机会
AWT的event dispatch thread
在一个单独的线程中执行一个任务的简单过程
1)将任务代码移到实现了Runnable接口的类的run方法中
public interface Runnable{
void run();
}
可以如下所示实现一个类:
class MyRunnable implements Runnable{
public void run(){
task code
}
}
2)创建一个类对象
Runnable r=new MyRunnable();
3)由Runnable创建一个Thread对象:
Thread t=new Thread(r);
4)启动线程
t.start();
想将球代码放在一个独立的线程中,只要实现一个类BallRunnable,然后将动画放在run中
class BallRunnable implements Runnable{
...
public void run(){
try{
for(int i=1;i<=STEPS;i++){
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);
}
}
catch(InterruptedException)
{
}
}
...
}
此外,需要捕获sleep方法可能抛出的异常InterruptedException
一般情况下,线程在中断时被终止。因此当发生InterruptedException时,run方法将结束执行
无论何时点击Start按钮,addBall方法都将启动一个新线程
Ball b=new Ball();
panel.add(b);
Runnable r=new BallRunnable(b,panel);
Thread t=new Thread(r);
t.start();
tips:也可以通过构建一个Thread类的子类定义一个线程
class MyThread extends Thread{
public void run(){
task code
}
}
然后,构建一个子类的对象,并调用start方法。但是,应该从运行机制上减少需要并行的任务数量,因此这种方法已不再推荐。
warning:不要调用Thread类或Runnable对象的run方法。直接调用run方法,指挥执行同一个线程中的任务,儿不会启动新线程。应调用Thread.start方法。这个方法将创建一个执行run方法的新线程
bounceThread/BounceThread.java
P645
package bounceThread;
import java.awt.*;
import java.awt.event.*;
import javax.swing.*;
/**Shows animated bouncing balls.
*/
public class BounceThread{
public static void main(String[] args){
EventQueue.invokeLater(new Runnable(){
public void run(){
JFrame frame=new BounceFrame();
frame.setTile("BounceThread");
frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
frame.setVisible(true);
}
});
}
}
/**A runnable that animates a bouncing ball.
*/
class BallRunnable implements Runnable{
private Ball ball;
private Component component;
public static final int STEPS=1000;
public static final int DELAY=5;
/**Constructs the runnable.
*/
public BallRunnable(Ball aBall,Component aComponent){
ball=aBall;
component=aComponent;
}
public void run(){
try{
for(int i=1;i<=STEPS;i++){
ball.move(component.getBounds());
component.repaint();
Thread.sleep(DELAY);
}
}
catch(InterruptedException e){
}
}
}
/**The frame with panel and buttons.
*/
class BounceFrame extends JFrame{
private BallComponent comp;
/**Constructs the frame with the component for showing the bouncing ball and Start and Close buttons
*/
public BounceFrame(){
comp=new BallComponent();
add(comp,BorderLayout.CENTER);
JPanel buttonPanel=new JPanel();
addButton(buttonPanel,"Start",new ActionListener(){
public void actionPerformed(ActionEvent event){
addBall();
}
});
addButton(buttonPanel,"Close",new ActionListener(){
public void cationPerforned(ActionEvent event){
System.exit(0);
}
});
add(buttonPanel,BorderLayout.SOUTH);
pack();
}
/**Adds a button to a container
*/
public void addButton(Container c,String title,ActionListener listener){
JButton button=new JButton(title);
c.add(button);
button.addActionListener(listener);
}
/**Adds a bouncing ball to the canvas and starts a thread to make it bounce
*/
public void addBall();
comp.add(b);
Runnable r=new BallRunnable(b,comp);
Thread t=new Thread(r);
t.start();
}
}
java.lang.Thread 1.0
Thread(Runnable target) 构造一个新线程,用于调用给定target的run()方法
void start() 启动这个线程,将引发调用run()方法,该方法将立即返回,并且新线程并行运行
void run() 调用关联Runnable的run方法
java.lang.Runnable 1.0
void run() 必须覆盖这个方法,并在这个方法中提供所要执行的任务指令
Case 1: Climbing
package synchronization;
//测试类
public class ClimbingTest {
public static void main(String[] args) {
Climbing young=new Climbing("年轻人", 100, 1);
Climbing old=new Climbing ("老年人", 300, 1);
System.out.println("******开始爬山******");
young.start();
old.start();
}
}
package synchronization;
//方法
public class Climbing extends Thread{
private int time;
public int num=0;
public Climbing(String name,int time,int kio) {
super(name);
this.time=time;
this.num=kio*1000/100;
}
public void run() {
while (num>0) {
try {
Thread.sleep(this.time);
} catch (InterruptedException e) {
// TODO: handle exception
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"爬完100米!");
num--;
}
System.out.println("***"+Thread.currentThread().getName()+"到达终点!***");
}
}
2-中断线程
interrupt方法可以用来终止线程
当对一个线程调用interrupt方法时,线程的中断状态将被置位。这是每个线程都具有的boolean标志,每个线程都应该不时检查这个标志,以判断线程是否被中断
想要知道中断状态是否被置位,首先调用静态Thread.currentThread方法获得当前线程,然后调用isInterrupted method
while(!Thread.currentThread().isInterrupted()&&more work to do){
do more work
}
如果线程被阻塞,就无法检测中断状态。这是产生InterruptedException异常的地方。当在一个被阻塞的线程(调用sleep或wait)上调用interrupt方法时,阻塞调用将会被InterruptedException异常中断(存在不能被中断的阻塞I/O调用,应该考虑选择可中断的调用)
没有任何语言方面的需求要求一个被中断的线程应该终止。中断一个线程不过是引起它的注意。被中断的线程可以决定如何响应中断。某些线程很重要,应在完成异常处理后继续执行,而不理会中断。更为普遍的情况是,线程将简单地中断作为一个终止的请求。这组线程的run方法具有如下形式:
public void run(){
try{
...
while(!Thread.currentThread().isInterrupter()&&more work to do){
do more work
}
}
catch(InterruptedException e){
//thread was interrupted during sleep
}
finally{
cleanup,if required
}
//exiting the run method terminates the thread
}
*在catch子句中调用Thread.currentThread().interrupt()来设置中断状态,调用者可以对其进行检测
void mySubTask(){
...
try{sleep(delay);}
catch(InterruptedException e){Thread.currentThread().interrupt();}
...
}
*或者,用throws InterruptedException标记方法,不采用try语句捕获异常
void mySubTask() throws InterruptedException{
...
sleep(delay);
...
}
void interrupt() 向线程发送中断请求。线程的中断状态将被设置为true。如果目前该线程被一个sleep调用阻塞,抛出InterruptedException
static boolean interrupted()
测试当前线程是否被中断。
attention:static method
这一调用会产生副作用——将当前线程的中断状态重置为false
boolean isInterrupted()
测试线程是否被终止。unlike static method,这一调用不改变线程的中断状态
static Thread currentThread()
返回代表当前执行线程的Thread对象
线程状态state 5
新建 就绪 运行 阻塞/死亡
New
Runnable
Blocked
Waiting
Timed waiting 计时等待
Terminated 被终止
确认一个线程的当前状态,调用getState方法
未捕获异常处理器
线程的run方法不能throw任何被检测的异常,but,不被检测的异常会导致线程终止,in this place,thread is dead
but,不需要任何catch子句来处理可以被传播的异常,相反,线程死亡之前,异常被传递到一个用于未捕获异常的处理器
该处理器必须属于一个 实现 Thread.UncaughtExceptionHandler接口的类,这个接口只有一个方法:
void uncaughtException(Thread t,Throwable e)
可以用setUncaughtExceptionHandler方法为任何线程安装一个处理器
也可以用Thread类的静态方法setDefaultUncaughtExceptionHandler为所有线程安装一个默认的处理器替换处理器可以使用日志API发送未捕获异常的报告到日志文件
如果不安装默认的处理器,默认处理器未空。but,如果不为独立的线程安装处理器,此时的处理器就是该线程的ThreadGroup对象
tips:线程组是一个可以统一管理的线程集合。默认下,创建的all threads属于相同的thread group,but,也可能会建立其他的group。现在引入了better的特性用于线程集合的操作,建议不要再自己的程序中使用thread group
java.lang.Thread 1.0
static void setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0
static Thread.UncaughtExceptionHandler getDefaultUncaughtExceptionHandler() 5.0 设置或获取未捕获异常的默认处理器
void setUncaughtExceptionHandler(Thread.UncaughtExceptionHandler handler) 5.0
Thread.UncaughtExceptionHandler getUncaughtExceptionHandler(); 5.0 设置或获取未捕获异常的处理器。如果没有安装处理器,则将thread group对象作为handler
java.lang.Thread.UncaughtExceptionHandler 5.0
void uncaughtException(Thread t,Throwable e)
当一个thread因uncaughtException而终止,按规定要将报告记录到之日中。
参数:t 由于uncaughtException而终止的thread
e uncaughtException对象
5-Synchronization
例:多线程实现网络购票,用户提交购票信息后,
1、网站修改网站车票数据
2、显示出票反馈信息to用户
多数多线程应用中,两个或以上的thread需要share对同一数据的存取。如果两个线程存取相同对象,且每个thread都调用了一个修改该对象state的方法,根据各thread访问数据的次序,可能会产生讹误的对象,this called "race condition"(竞争条件)
为了避免多线程引起的对共享数据的讹误,应同步存取
*同一资源:static、只有一个对象
如果有多个对象就不属于这样的情况
example:p656
需要确保thread在失去control前方法运行完成,那么银行账户对象的state就的讹误will never
5-3 锁对象
两种机制防止代码块受并发访问的干扰。java提供synchronized关键字达到这一目的,修饰方法或代码块,SE 5.0引入了ReentrantLock(可重入的互斥锁)类。synchronized自动提供一个lock以及相关的condition。java.util.concurrent框架为这些基础机制提供独立的类
用户ReentrantLock保护代码及的基本结构:
myLock.lock(); //a ReentrantLock Object
try{
critical section
}finally{
myLock.unlock(); //make sure the lock is unlocked even if an exception is thrown
}
这一结构确保任何时刻只有一个thread进入临界区。一旦一个thread block了lock对象,
其他任何thread都无法通过lock语句。当其他thread调用lock时,它们被阻塞,知道第一个thread释放lock对象
warning:把unlock操作括在finally子句之内is very important.如果在临界区的代码抛出异常,
lock必须非释放,否则其他thread将永远阻塞
tips:如果使用lock,就不能使用带资源的try语句*。首先,unlock方法名不是close。
不过,即使将它重命名,带资源的try语句也无法正常工作。它的首部希望声明一个新变量。but,if use a lock,你可能想使用多个thread共享的那个变量(而不是新变量)
* [1] try-with-resources statement
是指声明了一个或多个资源的try语句(statement,声明),其中的资源一般是指在程序执行完毕後需要关闭(close)的对象,而 try-with-resources 语句中声明的资源会在try块执行完毕後自动关闭。try-with-resources 语句中的资源只能是实现了java.lang.AutoCloseable接口的类实例,但是 Java SE 7 之後的所有实现了java.io.Closeable的类都实现了java.lang.AutoCloseable接口(该接口是在Java SE 7中才引入的),故都可以作为这里的资源。
try-with-resources 语句用法:在try关键字後面伴随一对小括号,并在括号中声明try块中要用到的资源。下例中利用 BufferedReader 来读取文件中的第一行,采用了 try-with-resources 的语法:
static String readFirstLineFromFile(String path) throws IOException {
try (BufferedReader br =
new BufferedReader(new FileReader(path))) {
return br.readLine();
}
}
这里的 BufferedReader 对象不必调用其close()方法进行关闭,而是会在退出try块(不论正常或异常退出)後自动关闭(後台会自动调用close()),释放资源。按 Java SE 7 之前的写法,BufferedReader 对象一般需要在try块後面的finally块中调用其close()方法关闭对象:
static String readFirstLineFromFileWithFinallyBlock(String path) throws IOException {
BufferedReader br = new BufferedReader(new FileReader(path));
try {
return br.readLine();
} finally {
if (br != null) br.close();
}
}
在上例中,如果readLine()与close()方法先後都产生了异常,那么在用finally块关闭BufferedReader对象时,由readLine()方法产生的(第一个)异常会被抑制(suppressed),只有close()产生的(第二个)异常会被向上传递;相反,若用 try-with-resources 语句的话,则只有try块中产生的异常被向上传递(这个异常的信息通常更有用),try-with-resources 语句中产生的异常则会被抑制。当然,在Java SE 7之後,被抑制的异常可以通过调用 Throwable.getSuppressed() 方法获取。
在 try-with-resources 语句中,也可以声明多个资源,各资源声明语句用分号隔开:
try (
ZipFile zf = new ZipFile(zipFileName);
BufferedWriter writer =
java.nio.file.Files.newBufferedWriter(outputFilePath, charset)
) {
// Enumerate each entry
}
当从try块中(以正常或异常方式)退出时,在 try-with-resources 语句中所声明资源的关闭方法都会被自动调用,并且是按与资源声明序相反的顺序调用其close()方法的。
最后,try-with-resources 语句可以像Java SE 7 之前的普通try语句一样带有catch块和finally块,但在执行catch块(若有必要)和finally块中的代码前,会先关闭 try-with-resources 语句中声明的资源。
package why;
import java.io.FileInputStream;
import java.io.PrintWriter;
import java.util.Scanner;
public class Xin {
public static void main(String[] args) throws Exception {
try(Scanner in = new Scanner(new FileInputStream(“d:\\haha.txt”));
PrintWriter out = new PrintWriter(“d:\\hehe.txt”)) {
while(in.hasNext()) {
out.println(in.next().toUpperCase());
}
}
}
}
让我们使用一个lock来保护Bank类的transfer方法:
public classBank{
private Lock bankLock=new ReentrantLock(); //ReentrantLock implements the Lock interface
...
public void transfer(int from,int to,int amount){
bankLock.lock();
try{
System.out.print(Thread.currentThread());
account[from] -=amount;
System.out.printf("%10.2f from %d to %d",amount,from,to);
account[to] +=amount;
System.out.printf("Total Balance: %10.2f%n",getTotalBalance());
}finally{
bankLock.unlock();
}
}
}
假定一个thread调用transfer,在执行结束前被剥夺了运行权。假定第二个线程也调用transfer,由于第二个线程不能get lock,将在调用lock方法时被阻塞。必须等待第一个thread完成transfer方法的执行后才能再度被激活。当第一个thread释放lock时,第二个thread才能开始运行
try this,添加加锁代码到transfer方法并再次运行程序,你可以永远运行它,而银行余额不会出现讹误。
注意每个Bank对象有自己的ReentrantLock对象。如果两个thread试图访问同一个Bank对象,那么lock以串行方式提供服务。but,如果两个thread访问不同的Bank对象,每个thread得到不同的lock对象,两个thread都不会发生阻塞。It should be,因为thread在操纵不同的Bank实例的时候,threads之间不会相互影响
lock时是reentrant可重入的,因为thread可以重复地get已经hold的lock。lock保持一个hold count(持有计数)来跟踪对lock方法的嵌套调用。thread在每一次调用lock都要调用unlock来释放lock。由于这一特性,被一个lock保护的代码可以调用另一个使用相同lock的方法
如,transfer方法调用getTotalBalance方法,这也会封锁bankLock对象。此时bankLock对象的hole count为2。当getTotalBalance方法退出的时候,hold count变回1。当transfer方法退出的时候,hold count变为0。thread释放lock
usually,可能想要保护 需n个操作来更新或检查share对象的代码块。要确保这些操作完成后,another thread 才能使用相同对象
warning:be careful the code in critical section,不要因为exception的thrown而跳出了critical section。如果抛出异常before critical section code end,finally子句将释放lock,但会使对象可能处于一种受损状态
java,util.concurrent.locks.Lock 5.0
void lock() 获取这个锁;如果锁同时被另一个线程拥有则发生阻塞
void unlock() 释放这个锁
java.util.concurrent.locks.ReentrantLock 5.0
ReentrantLock() 构建一个可以被用来保护临界区critical section的可重入锁
ReentrantLock(boolean fair) 构建一个带有公平策略的锁。一个fair lock prefer 等待时间最长的线程。but,这个fair的保证将大大降低性能。so,lock没有被强制为fair,in default condition.
warning:sounds like fair lock is more reasonable,but use fair lock is slower than normal.只有当你确实了解自己要做什么,并且对于你要解决的问题有一个特定理由必须use fair lock的时候,才使用公平锁。即使use it,也无法make sure 线程调度器时fair的。如果线程调取器选择ignore一个线程,而该线程为了这个锁已经等待了很长时间,那么就没有机会公平地处理这个lock了
5-4 conditional variable 条件对象
usually,thread enter critical section,却发现在某以条件满足后才能执行。要使用一个条件对象来管理那些已经get了一个lock但是不能做useful work的thread。
现在来细化Bank的模拟程序。我们避免选择没有足够资金的账户作为转出账户。注意不能不能使用下面这样的代码
if(bank.getBalance(from)>=amount)
bank.transfer(from,to,amount);
当前线程完全有可能在成功地完成测试,且在调用transfer方法之前将被中断
if(bank.getBalance(from)>=amount) //thread might be deactivated at this point
bank.transfer(from,to,amount);
在线程再次运行前,账户余额可能已经低于提款金额。必须确保没有其他线程在本检查余额与转账活动之间修改余额。通过使用锁来保护检查与转账动作来做到这一点:
public void transfer(int from,int to,int amount){
bankLock.lock();
try{
while(accounts[from]<amount){
//wait
...
}
//transfer funds
...
}finally{
bankLock.unlock();
}
}
java.util.concurrent.BlockingQueue<E> 5.0
void put(E element) 添加元素,必要时阻塞
E take() 移除并返回头元素,必要时阻塞
boolean offer(E element,long time,TimeUnit unit) 添加给定的元素,如果成功return true,必要时阻塞,直至元素已被添加后超时
E poll(long time,TimeUnit unit) 移除并返回头元素,必要时阻塞,直至元素可用或超时用完,失败时return null
6
void putFirst(E element)
void putLast(E element) 添加元素,必要时阻塞
E takeFirst()
E takeLast
(to be continued)
引用:
[1] 洪七公 ,Java SE 7 中带资源声明的 try 语句(try-with-resources statement)[OL],2016年02月05日 11:35:30,https://blog.youkuaiyun.com/clxjoseph/article/details/50637426