厦门网站优化建设,河南网站建设工作室,做电影网站用的云盘,网页设计app软件多线程
一、相关概念
并发与并行
并行#xff08;parallel#xff09;#xff1a;指多个事件任务在同一时刻发生#xff08;同时发生#xff09;。 并发#xff08;concurrency#xff09;#xff1a;指两个或多个事件在同一个微小的时间段内发生。程序并发执行可以…多线程
一、相关概念
并发与并行
并行parallel指多个事件任务在同一时刻发生同时发生。 并发concurrency指两个或多个事件在同一个微小的时间段内发生。程序并发执行可以在有限条件下充分利用CPU资源。 单核CPU只能并发 多核CPU并行并发
线程与进程 程序为了完成某个任务和功能选择一种编程语言编写的一组指令的集合。 软件1个或多个应用程序相关的素材和资源文件等构成一个软件系统。 进程是对一个程序运行过程创建-运行-消亡的描述系统会为每个运行的程序建立一个进程并为进程分配独立的系统资源比如内存空间等资源。 线程线程是进程中的一个执行单元负责完成执行当前程序的任务一个进程中至少有一个线程。一个进程中是可以有多个线程的这时这个应用程序也可以称之为多线程程序。多线程使得程序可以并发执行充分利用CPU资源。 面试题进程是操作系统调度和分配资源的最小单位线程是CPU调度的最小单位。不同的进程之间是不共享内存的。进程之间的数据交换和通信的成本是很高。不同的线程是共享同一个进程的内存的。当然不同的线程也有自己独立的内存空间。对于方法区堆中中的同一个对象的内存线程之间是可以共享的但是栈的局部变量永远是独立的。
多线程的优点与应用场景
主要优点 充分利用CPU空闲时间片用尽可能短的时间完成用户的请求。也就是使程序的响应速度更快 。 应用场景 多任务处理。多个用户请求服务器服务端程序可以开启多个线程分别处理每个用户的请求互不影响。单个大任务处理。下载一个大文件可以开启多个线程一起下载减少整体下载时间。
线程调度
指CPU资源如何分配给不同的线程。常见的两种线程调度方式 分时调度 所有线程轮流使用 CPU 的使用权平均分配每个线程占用 CPU 的时间。 抢占式调度 优先让优先级高的线程使用 CPU如果线程的优先级相同那么会随机选择一个(线程随机性)Java采用的是抢占式调度方式。
二、线程的创建与启动
继承Thread类
通过继承Thread类来创建并启动多线程的步骤:
定义Thread类的子类并重写该类的run()方法该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体。创建Thread子类的实例即创建了线程对象调用线程对象的start()方法来启动该线程
多线程执行情况分析 注意事项 手动调用run方法不是启动线程的方式只是普通方法调用。 start方法启动线程后run方法会由JVM调用执行。 不要重复启动同一个线程否则抛出异常IllegalThreadStateException 不要使用Junit单元测试多线程不支持主线程结束后会调用System.exit()直接退出JVM;
实现Runnable接口
定义Runnable接口的实现类并重写该接口的run()方法该run()方法的方法体同样是该线程的线程执行体。创建Runnable实现类的实例并以此实例作为Thread的target来创建Thread对象该Thread对象才是真正的线程对象。调用线程对象的start()方法来启动线程。
两种创建线程方式比较 Thread类本身也是实现了Runnable接口的run方法都来自Runnable接口run方法也是真正要执行的线程任务。 public class Thread implements Runnable {}因为Java类是单继承的所以继承Thread的方式有单继承的局限性但是使用上更简单一些。 实现Runnable接口的方式避免了单继承的局限性并且可以使多个线程对象共享一个Runnable实现类线程任务类对象从而方便在多线程任务执行时共享数据。
匿名内部类对象创建线程
匿名内部类对象的方式创建线程并不是一种新的创建线程的方式只是在线程任务只需执行一次的情况下我们无需单独创建线程类可以采用匿名对象的方式
new Thread(新的线程){Overridepublic void run() {for (int i 0; i 10; i) {System.out.println(getName()正在执行i);}}
}.start();new Thread(new Runnable(){Overridepublic void run() {for (int i 0; i 10; i) {System.out.println(Thread.currentThread().getName() i);}}
}).start();三、Thread类
构造方法
public Thread() :分配一个新的线程对象。public Thread(String name) :分配一个指定名字的新的线程对象。public Thread(Runnable target) :分配一个带有指定目标新的线程对象。public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字。
线程使用基础方法 public void run() :此线程要执行的任务在此处定义代码。 public String getName() :获取当前线程名称。 public static Thread currentThread() :返回对当前正在执行的线程对象的引用。 public final boolean isAlive()测试线程是否处于活动状态。如果线程已经启动且尚未终止则为活动状态。 public final int getPriority() 返回线程优先级 public final void setPriority(int newPriority) 改变线程的优先级 每个线程都有一定的优先级优先级高的线程将获得较多的执行机会。每个线程默认的优先级都与创建它的父线程具有相同的优先级。Thread类提供了setPriority(int newPriority)和getPriority()方法类设置和获取线程的优先级其中setPriority方法需要一个整数并且范围在[1,10]之间通常推荐设置Thread类的三个优先级常量MAX_PRIORITY10最高优先级MIN _PRIORITY 1最低优先级NORM_PRIORITY 5普通优先级默认情况下main线程具有普通优先级。
线程控制常见方法 public void start() :导致此线程开始执行; Java虚拟机调用此线程的run方法。 public static void sleep(long millis) :线程睡眠使当前正在执行的线程以指定的毫秒数暂停暂时停止执行。 public static void yield()线程礼让yield只是让当前线程暂时失去执行权让系统的线程调度器重新调度一次希望优先级与当前线程相同或更高的其他线程能够获得执行机会但是这个不能保证完全有可能的情况是当某个线程调用了yield方法暂停之后线程调度器又将其调度出来重新执行。 void join() 加入线程当前线程中加入一个新线程等待加入的线程终止后再继续执行当前线程。 void join(long millis) 等待该线程终止的时间最长为 millis 毫秒。如果millis时间到将不再等待。 void join(long millis, int nanos) 等待该线程终止的时间最长为 millis 毫秒 nanos 纳秒。 public final void stop()强迫线程停止执行。 该方法具有不安全性已被弃用最好不要使用。 调用 stop() 方法会立刻停止 run() 方法中剩余的全部工作包括在 catch 或 finally 语句中的并抛出ThreadDeath异常(通常情况下此异常不需要显示的捕获)因此可能会导致一些清理性的工作的得不到完成如文件数据库等的关闭。调用 stop() 方法会立即释放该线程所持有的所有的锁导致数据得不到同步出现数据不一致的问题。 public void interrupt()中断线程实际上是给线程打上一个中断的标记并不会真正使线程停止执行。 public static boolean interrupted()检查线程的中断状态调用此方法会清除中断状态标记。 public boolean isInterrupted()检查线程中断状态不会清除中断状态标记 public void setDaemon(boolean on)将线程设置为守护线程。必须在线程启动之前设置否则会报IllegalThreadStateException异常。 守护线程主要为其他线程服务当程序中没有非守护线程执行时守护线程也将终止执行。JVM垃圾回收器也是守护线程。 public boolean isDaemon()检查当前线程是否为守护线程。 volatile的作用是确保不会因编译器的优化而省略某些指令volatile的变量是说这变量可能会被意想不到地改变每次都小心地重新读取这个变量的值而不是使用保存在寄存器里的备份这样编译器就不会去假设这个变量的值了。
线程生命周期
传统线程模型的五种线程状态
JDK定义的六种线程状态 在java.lang.Thread类内部定义了一个枚举类用来描述线程的六种状态 public enum State {NEW,RUNNABLE,BLOCKED,WAITING,TIMED_WAITING,TERMINATED;}说明当从WAITING或TIMED_WAITING恢复到Runnable状态时如果发现当前线程没有得到监视器锁那么会立刻转入BLOCKED状态。
线程安全
当我们使用多个线程访问同一资源可以是同一个变量、同一个文件、同一条记录等的时候但是如果多个线程中对资源有读和写的操作就会出现前后数据不一致问题这就是线程安全问题。
线程安全问题引出
局部变量不能共享局部变量是每次调用方法都是独立的那么每个线程的run()的数据是独立的不是共享数据。不同对象的实例变量不共享 不同的实例对象的实例变量是独立的。静态变量是共享的同一个对象的实例变量共享
总结线程安全问题的出现因为具备了以下条件
多线程执行共享数据多条语句操作共享数据
线程安全问题解决方式 同步方法synchronized 关键字直接修饰方法表示同一时刻只有一个线程能进入这个方法其他线程在外面等着。
public synchronized void method(){可能会产生线程安全问题的代码
}同步代码块synchronized 关键字可以用于某个区块前面表示只对这个区块的资源实行互斥访问。
synchronized(同步锁){需要同步操作的代码
}锁对象选择
首选this其次是类.class也可以是 空字符串对象
同步锁对象
锁对象可以是任意类型。多个线程对象 要使用同一把锁。
同步代码块的锁对象
静态代码块中使用当前类的Class对象非静代码块中习惯上先考虑this但是要注意是否同一个this 锁的范围太小不能解决安全问题要同步所有操作共享资源的语句。 锁的范围太大因为一旦某个线程抢到锁其他线程就只能等待所以范围太大效率会降低不能合理利用CPU资源。 单例设计模式的线程安全问题
1、饿汉式没有线程安全问题
饿汉式上来就创建对象
2、懒汉式线程安全问题
public class Singleton {private static Singleton ourInstance;public static Singleton getInstance() {//一旦创建了对象之后再次获取对象都不会再进入同步代码块提升效率if (ourInstance null) {//同步锁锁住判断语句与创建对象并赋值的语句synchronized (Singleton.class) {if (ourInstance null) {ourInstance new Singleton();}}}return ourInstance;}private Singleton() {}
}
等待唤醒机制
在一个线程满足某个条件时就进入等待状态wait()/wait(time) 等待其他线程执行完他们的指定代码过后再将其唤醒notify();或可以指定wait的时间等时间到了自动唤醒在有多个线程进行等待时如果需要可以使用 notifyAll()来唤醒所有的等待线程。wait/notify 就是线程间的一种协作机制。
wait线程不再活动不再参与调度进入 wait set 中因此不会浪费 CPU 资源也不会去竞争锁了这时的线程状态即是 WAITING或TIMED_WAITING。它还要等着别的线程执行一个特别的动作也即是“通知notify”或者等待时间到在这个对象上等待的线程从wait set 中释放出来重新进入到调度队列ready queue中notify则选取所通知对象的 wait set 中的一个线程释放notifyAll则释放所通知对象的 wait set 上的全部线程。
注意 被通知线程被唤醒后也不一定能立即恢复执行因为它当初中断的地方是在同步块内而此刻它已经不持有锁所以她需要再次尝试去获取锁很可能面临其它线程的竞争成功后才能在当初调用 wait 方法之后的地方恢复执行。
总结如下
如果能获取锁线程就从 WAITING 状态变成 RUNNABLE可运行 状态否则线程就从 WAITING 状态又变成 BLOCKED等待锁 状态
调用wait和notify方法需要注意的细节
wait方法与notify方法必须要由同一个锁对象调用。因为对应的锁对象可以通过notify唤醒使用同一个锁对象调用的wait方法后的线程。wait方法与notify方法是属于Object类的方法的。因为锁对象可以是任意对象而任意对象的所属类都是继承了Object类的。wait方法与notify方法必须要在同步代码块或者是同步函数中使用并且必须要通过锁对象调用这2个方法。
释放锁操作与死锁
1、释放锁的操作 当前线程的同步方法、同步代码块执行结束。 当前线程在同步代码块、同步方法中出现了未处理的Error或Exception导致当前线程异常结束。 当前线程在同步代码块、同步方法中执行了锁对象的wait()方法当前线程被挂起并释放锁。
2、死锁
不同的线程分别锁住对方需要的同步监视器对象不释放都在等待对方先放弃时就形成了线程的死锁。一旦出现死锁整个程序既不会发生异常也不会给出任何提示只是所有线程处于阻塞状态无法继续。
3、面试题sleep()和wait()方法的区别
1sleep()不释放锁wait()释放锁
2sleep()指定休眠的时间wait()可以指定时间也可以无限等待直到notify或notifyAll
3sleep()在Thread类中声明的静态方法wait方法在Object类中声明
因为我们调用wait方法是由锁对象调用而锁对象的类型是任意类型的对象。那么希望任意类型的对象都要有的方法只能声明在Object类中。
练习
要求两个线程同时打印字母每个线程都能连续打印3个字母。两个线程交替打印一个线程打印字母的小写形式一个线程打印字母的大写形式但是字母是连续的。当字母循环到z之后回到a。 public class PrintLetterDemo {public static void main(String[] args) {// 2、创建资源对象PrintLetter p new PrintLetter();// 3、创建两个线程打印new Thread(小写字母) {public void run() {while (true) {p.printLower();try {Thread.sleep(1000);// 控制节奏} catch (InterruptedException e) {e.printStackTrace();}}}}.start();new Thread(大写字母) {public void run() {while (true) {p.printUpper();try {Thread.sleep(1000);// 控制节奏} catch (InterruptedException e) {e.printStackTrace();}}}}.start();}
}// 1、定义资源类
class PrintLetter {private char letter a;public synchronized void printLower() {for (int i 1; i 3; i) {System.out.println(Thread.currentThread().getName() - letter);letter;if (letter z) {letter a;}}this.notify();try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}public synchronized void printUpper() {for (int i 1; i 3; i) {System.out.println(Thread.currentThread().getName() - (char) (letter - 32));letter;if (letter z) {letter a;}}this.notify();try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}
}