导图社区 编程开发多线程知识框架笔记
线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强。 缺点: 编程多一层对象包装,如果线程有执行结果是不可以直接返回的。 多线程的实现方案三:Callable和FutureTask 前2种...
编辑于2022-11-14 14:03:48 广东编程开发多线程知识框架笔记
概念
什么是进程?
运行着的程序叫做进程
什么是线程?
一个进程内部可能并发运行着多个线程,线程是进程内部的一条执行路径,同一类的线程可以共享数据和代码. 线程是轻量级的进程,切换代价较小.
线程和进程的区别
每个进程都有独立的代码和数据空间(进程上下文),进程间的切换开销大
线程: 同一进程内的线程共享代码和数据空间,线程切换的开销小
多进程: 在操作系统中能同时运行多个任务(程序)
多线程: 在同一进程有多个并发任务在执行
创建线程的两种方式:
第一种:
(1)实现Runnable接口,重写run(),创建该实现类的对象;
(2)再创建Thread类的对象,包装Runnable实现类的对象;
(3)调用Thread对象的start()使该线程处于"可执行/可运行"状态,等待虚拟机来调度执行
第二种:
(1)继承Thread类,重写run();创建该子类的对象;
(2)调用start()使该线程处于"可执行/可运行"状态,等待虚拟机来调度
停止(中断)线程的两种方法
第一种:
在线程类中设置一个标志位,通过在其他线程(例如主线程)中修改该标志位的值,来达到停止线程的目的.一般在子线程的run()的循环中需要加上退出条件if(flag){....break;}
第二种:
线程对象调用interrupt()方法
(1)要被中断的线程阻塞在sleep()/wait()/join()上,会引发一个 InterruptedException,需要在catch语句中进行处理;
(2)不是上述情况下,可以用Thread.interrupted()来获取是否中断的状态,如果是被中断了,则break出来,不再执行后续语句;注意:该方法调用后,会清除"中断标志",下次再调用时,将得到false
线程的基本使用方法:
public void start()
使该线程开始执行
public static Thread currentThread()
返回对当前正在执行的线程对象的引用
public final boolean isAlive()
测试线程是否处于活动状态
public final String getName()
返回该线程的名称
public final void setName(String name)
改变线程名称
public final void setDaemon(boolean on)
将该线程标记为守护线程或用户线程
线程的调度:
线程睡眠:
Thread.sleep(long millis) throws InterruptedException 方法,使线程转到阻塞状态。millis 参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。
线程让步:
Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
线程加入/合并/加塞:
join() throws InterruptedException 方法 在当前线程中调用另一个线程的 join()方法,则当前线程转入WAITING状态,直到另一个线程运行结束,当前线程再由阻塞转为就绪状态。
线程等待:
Object 类中的wait() throws InterruptedException 方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。
线程唤醒:
Object 类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的。
Object类中的notifyAll()方法,唤醒在此对象监视器上等待的所有线程。
线程的状态转换:
要想实现多线程,必须在主线程中创建新的线程对象。任何线程都具有五种状态:创建、就绪、运行、阻塞、终止。
新建状态(New): 新创建了一个线程对象。 就绪状态(Runnable): 线程对象创建后,其他线程调用了该对象的start()方法。该状态的线程位于可运行线程池中,变得可运行,等待获取CPU的使用权。 获取了CPU,执行程序代码时也属于就绪状态。 运行状态(Running): 执行run()。 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种: 同步阻塞:若该同步锁被别的线程占用,则JVM会把该线程放入锁池中。运行的线程在获取对象的同步锁时,其他需要该锁的线程将进入同步阻塞状态. 等待阻塞:运行的线程执行wait()方法,JVM会把该线程放入等待池中。 其他阻塞:运行的线程执行sleep()或join()方法,或者发出了I/O请求时,JVM会把该线程置为阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。 结束状态(Dead): 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
调整线程的优先级:
Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程。优先级高的线程会获得较多的运行机会。
Java 线程的优先级用整数表示,取值范围是 1~10,Thread 类有以下三个静态常量:
static int MAX_PRIORITY 线程可以具有的最高优先级,取值为 10。
static int MIN_PRIORITY 线程可以具有的最低优先级,取值为 1。
static int NORM_PRIORITY 分配给线程的默认优先级,取值为 5。
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
synchronized使用方法:
同步代码块:
synchronized放在对象前面限制一段代码的执行 synchronized(this){ //this对象在进入该代码块之后,不能由其他线程访问其同步方法,或执行同步语句块 需要同步的代码; }
synchronized(obj){ //obj对象在进入该代码块之后,不能由其他线程访问其同步方法,或执行同步语句块 需要同步的代码; }
同步普通方法:
synchronized放在普通方法声明中,表示整个方法为同步方法 public synchronized void sale(){ //进入同步方法后,方法的调用者(this对象)被锁定,在方法执行过程中,不能再用this的同一个对象执行其他同步方法或语句块 需要同步的代码; }
同步静态方法:
synchronized放在静态方法声明中,表示整个方法为同步方法 public synchronized static void sale(){ //进入同步静态方法后,代表当前类的Class对象将被锁定,在方法执行过程中,其他线程不能在进入任何本类中的静态同步方法 需要同步的代码; }
什么时候需要同步:
1.多线程并发
2.有至少一个线程在修改共享数据的值,可能造成数据的值的不稳定性,影响程序正常安全的运行.
多线程安全问题:
当run()方法体内的代码操作到了成员变量(共享数据)时,就可能会出现多线程安全问题(线程不同步问题)。
在方法中尽量少操作成员变量,多使用局部变量
容器类的线程安全:
容器类大多数默认都没有考虑线程安全问题, 程序必须自行实现同步以确保共享数据在多线程下存取不会出错:
1.可以用synchronized来锁住这个对象
synchronized(list){ list.add(…); }
2.可以使用java.uitl.Collections的synchronizedXXX()方法来返回一个同步化的容器对象
List list = Collections.synchronizedList(new ArrayList());
3.可以直接使用线程安全的集合容器类:Vector,Hashtable
与线程控制的有关方法
方法 说明
start() 新建的线程进入Runnable状态
run() 线程进入Running 状态,程序员不要调用该方法,由系统调用
wait() 线程进入等待状态,等待被notify,这是一个对象方法,而不是线程方法
notify()/notifyAll() 唤醒其他的线程,这是一个对象方法,而不是线程方法
static void yield() 线程放弃执行,使其他优先级不低于此线程的线程有机会运行,它是一个静态方法
getPriority()/setPriority() 获得/设置线程优先级1-10 5 5
interrupt() 打断线程执行
static boolean interrupted() 查看线程是否被打断
static void sleep() 线程睡眠指定的一段时间
join() 调用这个方法的主线程,会等待加入的子线程完成
多线程编程一般规则:
如果两个或两个以上的线程都修改一个对象,那么把执行修改的方法定义为被同步的,如果对象更新影响到只读方法,那么只读方法也要定义成同步的。
不要滥用同步。如果在一个对象内的不同的方法访问的不是同一个数据,就不要将方法设置为synchronized的。
如果一个线程必须等待一个对象状态发生变化,那么他应该在对象内部等待,而不是在外部。他可以通过调用一个被同步的方法,并让这个方法调用wait()。
每当一个方法返回某个对象的锁时,它应当调用notifyAll()来让等待队列中的其他线程有机会执行。
记住wait()和notify()/notifyAll()是Object类方法,而不是Thread类的方法。仔细查看每次调用wait()方法,都要有相应的notify()/notifyAll()方法,且它们均作用于同一个对象。
后台线程(守护线程)Daemon Thread
在后台运行的线程,为其他的线程提供服务
可以用setDaemon(boolean b)来把一个线程设置成后台线程
经常用于任务结束时的善后处理
典型的后台线程是定时器Timer线程
后台线程的优先级比其他线程的低
如果没有“用户线程”在运行,JVM将退出,守护线程将被自动终止
回调
主线程启动了子线程,子线程为主线程准备数据,准备完成之后,反过来调用主线程的方法.因为主线程把this对象给子线程的构造方法传过去了,所以子线程能用这个对象去调用主线程的方法.
主线程创建子线程时,给子线程的构造方法传入某个接口的匿名实现类的对象,并重写方法;子线程在完成了一些工作后,利用这个接口类型的变量进行虚方法调用,调用的是传入构造方法的实参匿名内部类对象里面重写后的方法