(java technic)multithreaded部分笔记整理及Case(being revised)

本文详细介绍了Java多线程中的关键概念,包括线程与进程的区别、线程的创建方式、线程状态以及线程调度。重点讨论了多线程间的中断、同步问题,如`interrupt`方法、同步机制以及锁对象的使用,以防止竞争条件。同时,还探讨了如何处理未捕获异常和线程状态的检查。通过对银行转账场景的模拟,展示了如何在实践中确保线程安全。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值