南昌市网站建设,数字营销的概念,建手机网站的平台,锦绣江南网站建设多线程介绍
进程#xff1a;进程指正在运行的程序。确切的来说#xff0c;当一个程序进入内存运行#xff0c;即变成一个进程#xff0c;进程是处于运行过程中的程序#xff0c;并且具有一定独立功能。 线程#xff1a;线程是进程中的一个执行单元#xff0c;负责当前进…多线程介绍
进程进程指正在运行的程序。确切的来说当一个程序进入内存运行即变成一个进程进程是处于运行过程中的程序并且具有一定独立功能。 线程线程是进程中的一个执行单元负责当前进程中程序的执行一个进程中至少有一个线程。一个进程中是可以有多个线程的这个应用程序也可以称之为多线程程序。
简而言之一个程序运行后至少有一个进程一个进程中可以包含多个线程 什么是多线程呢即就是一个程序中有多个线程在同时执行。
通过下图来区别单线程程序与多线程程序的不同
单线程程序即若有多个任务只能依次执行。当上一个任务执行结束后下一个任务开始执行。如去网吧上网网吧只能让一个人上网当这个人下机后下一个人才能上网。多线程程序即若有多个任务可以同时执行。如去网吧上网网吧能够让多个人同时上网。 程序运行原理
分时调度所有线程轮流使用 CPU 的使用权平均分配每个线程占用 CPU 的时间。抢占式调度优先让优先级高的线程使用 CPU如果线程的优先级相同那么会随机选择一个(线程随机性)Java使用的为抢占式调度。 抢占式调度详解
大部分操作系统都支持多进程并发运行现在的操作系统几乎都支持同时运行多个程序。比如现在我们上课一边使用编辑器一边使用录屏软件同时还开着画图板dos窗口等软件。此时这些程序是在同时运行”感觉这些软件好像在同一时刻运行着 实际上CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言某个时刻只能执行一个线程而 CPU的在多个线程间切换速度相对我们的感觉要快看上去就是在同一时刻运行。
其实多线程程序并不能提高程序的运行速度但能够提高程序运行效率让CPU的使用率更高。
线程的状态
线程可以处于下列状态之一
NEW至今尚未启动的线程处于这种状态RUNNABLE正在 Java 虚拟机中执行的线程处于这种状态BLOCKED受阻塞并等待某个监视器锁的线程处于这种状态WAITING无限期地等待另一个线程来执行某一特定操作的线程处于这种状态 TIMED_WATING等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。TERHINATED已退出的线程处于这秘状态
在给定时间点上一个线程只能处于一种状态。这些状态是虚拟机状态它们并没有反映所有操作系统线程状态 主线程
当我们在dos命令行中输入java空格类名回车后启动JVM并且加载对应的class文件。虚拟机并会从main方法开始执行我们的程序代码一直把main方法的代码执行结束。如果在执行过程遇到循环时间比较长的代码那么在循环之后的其他代码是不会被马上执行的。如下代码演示
class Demo{String name;Demo(String name){this.name name;}void show() {for (int i1;i10000 ;i ) {System.out.println(namename,ii);}}
}class TestDemo {public static void main(String[] args) {Demo d new Demo(小强);Demo d2 new Demo(旺财);d.show();d2.show();System.out.println(Hello World!);}
}若在上述代码中show方法中的循环执行次数很多这时在d.show();下面的代码是不会马上执行的并且在dos窗口会看到不停的输出name小强,i值这样的语句。为什么会这样呢
原因是jvm启动后必然有一个执行路径(线程)从main方法开始的一直执行到main方法结束这个线程在java中称之为主线程。当程序的主线程执行时如果遇到了循环而导致程序在指定位置停留时间过长则无法马上执行下面的程序需要等待循环结束后能够执行。
那么能否实现一个主线程负责执行其中一个循环再由另一个线程负责其他代码的执行最终实现多部分代码同时执行的效果
能够实现同时执行通过Java中的多线程技术来解决该问题
Thread类
如何创建线程呢通过API中搜索查到Thread类。通过阅读Thread类中的描述。Thread是程序中的执行线程。Java 虚拟机允许应用程序并发地运行多个执行线程。 创建新执行线程有两种方法。
将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。创建对象开启线程。run方法相当于其他线程的main方法。声明一个实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象传入到某个线程的构造方法中开启线程。
创建线程方式一继承Thread类
创建线程的步骤
定义一个类继承Thread重写run方法创建子类对象就是创建线程对象调用start方法开启线程并让线程执行同时还会告诉jvm去调用run方法
/*** 这是自定义线程类继承Thread类* 重写run方法*/
public class SubThread extends Thread {/*** 重写run方法完成该线程执行的逻辑*/Overridepublic void run() {for (int i0; i50; i) {System.out.println(getName()循环第i次);}}
}/*** 测试类创建和启动一个线程* 创建Theard子类对象* 子类对象调用方法start()让线程程序执行JVM调用线程中的run*/
public class ThreadDemo {public static void main(String[] args) {//创建自定义线程对象SubThread st new SubThread();//开启新线程st.start();//在主方法中执行for循环for (int i0; i50; i) {System.out.println(main循环第i次);}}
}线程对象调用 run方法和调用start方法区别 线程对象调用run方法不开启线程。仅是对象调用方法。线程对象调用start开启线程并让jvm调用run方法在开启的线程中执行。 1、我们为什么要继承Thread类并调用其的start方法才能开启线程呢 那是因为Thread类用来描述线程具备线程应该有功能。 2、为什么不直接创建Thread类的对象呢 Thread t1 new Thread(); t1.start(); 这样做没有错但是该start调用的是Thread类中的run方法而这个run方法没有做什么事情更重要的是这个run方法中并没有定义我们需要让线程执行的代码。对于之前所讲的主线程它的任务定义在main函数中。自定义线程需要执行的任务都定义在run方法中。Thread类run方法中的任务并不是我们所需要的只有重写这个run方法。既然Thread类已经定义了线程任务的编写位置run方法那么只要在编写位置run方法中定义任务代码即可。所以进行了重写run方法动作。 多线程执行时在栈内存中其实每一个执行线程都有一片自己所属的栈内存空间。进行方法的压栈和弹栈当执行线程的任务结束了线程自动在栈内存中释放了但是当所有的执行线程都结束了那么进程就结束了。 获取线程名称
开启的线程都会有自己的独立运行栈内存那么这些运行的线程的名字是什么呢该如何获取呢既然是线程的名字按照面向对象的特点是哪个对象的属性和谁的功能那么我们就去找那个对象就可以了。查阅Thread类的API文档发现有个方法是获取当前正在运行的线程对象。还有个方法是获取当前线程对象的名称。 /*** 这是自定义线程类继承Thread类* 重写run方法** 获取线程名字的方法 String getName()*/
public class SubThread extends Thread {/*** 重写run方法完成该线程执行的逻辑*/Overridepublic void run() {System.out.println(super.getName());}
}/*** 测试类创建和启动一个线程* 每个线程都有自己的名字* 运行方法main主线程默认名字就是main* 其他新建的子线程默认名字是Thread-x** JVM开启主线程运行方法main主线程也是线程必然也是继承于Thread类* Theard类当中静态方法static Thread currentThread()返回正在执行的线程对象*/
public class ThreadDemo {public static void main(String[] args) {//创建自定义线程对象SubThread st new SubThread();//开启新线程st.start();//通用获取线程名称的方式System.out.println(Thread.currentThread().getName());}
}设置线程名称
/*** 这是自定义线程类继承Thread类* 重写run方法** 设置线程名字的方法 setName(String name)*/
public class SubThread extends Thread {public SubThread(){}public SubThread(String threadName){setName(threadName);}/*** 重写run方法完成该线程执行的逻辑*/Overridepublic void run() {System.out.println(super.getName());}
}/*** 测试类创建和启动一个线程* 每个线程都有自己的名字* 运行方法main主线程默认名字就是main* 其他新建的子线程默认名字是Thread-x** JVM开启主线程运行方法main主线程也是线程必然也是继承于Thread类* Theard类当中静态方法static Thread currentThread()返回正在执行的线程对象*/
public class ThreadDemo {public static void main(String[] args) {//创建自定义线程对象SubThread st new SubThread(new-thread-01);//开启新线程st.start();//通用获取线程名称的方式System.out.println(Thread.currentThread().getName());}
}创建线程方式—实现Runnable接口
创建线程的另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后创建Runnable的子类对象传入到某个线程的构造方法中开启线程。
为何要实现Runnable接口Runable是啥玩意呢
查看Runnable接口说明文档Runnable接口用来指定每个线程要执行的任务。包含了一个 run 的无参数抽象方法需要由接口实现类重写该方法。 创建线程的步骤
定义类实现Runnable接口覆盖接口中的run方法创建Thread类的对象将Runnable接口的子类对象作为参数传递给Thread类的构造函数调用Thread类的start方法开启线程
/*** 实现线程程序的另一种方式接口实现* 实现接口Runnable,重写run方法*/
public class SubRunnable implements Runnable{Overridepublic void run() {for(int i0; i5; i) {System.out.println(i);}}
}/*** 测试类创建和启动一个线程* 创建Thread类对象在构造方法当中传递Runnable接口实现类* 调用Thread类方法start()启动*/
public class ThreadDemo {public static void main(String[] args) {SubRunnable sr new SubRunnable();new Thread(sr).start();}
}为什么需要定一个类去实现Runnable接口呢继承Thread类和实现Runnable接口有啥区别呢 实现Runnable接口避免了继承Thread类的单继承局限性。覆盖Runnable接口中的run方法将线程任务代码定义到run方法中。创建Thread类的对象只有创建Thread类的对象才可以创建线程。线程任务已被封装到Runnable接口的run方法中而这个run方法所属于Runnable接口的子类对象所以将这个子类对象作为参数传递给Thread的构造函数这样线程对象创建时就可以明确要运行的线程的任务。 实现Runnable的好处 第二种方式实现Runnable接口避免了单继承的局限性所以较为常用。实现Runnable接口的方式更加的符合面向对象线程分为两部分一部分线程对象一部分线程任务。继承Thread类线程对象和线程任务耦合在一起。一旦创建Thread类的子类对象既是线程对象有又有线程任务。实现runnable接口将线程任务单独分离出来封装成对象类型就是Runnable接口类型。Runnable接口对线程对象和线程任务进行解耦。 线程的匿名内部类使用
使用线程的内匿名内部类方式可以方便的实现每个线程执行不同的线程任务操作
/*** 使用匿名内部类实现多线程程序* 前提继承或者接口的实现* new 父类或者接口(){* 重写抽象的方法* }*/
public class ThreadDemo{public static void main(String[] args) {//继承方式 XXX extends Thread{public void run(){ ... }}new Thread(){Overridepublic void run() {System.out.println(getName());}}.start();//实现接口 XXX implements Runnable(){public void run(){ ... }}new Thread(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName());}}){}.start();}
}线程池
线程池概念
线程池其实就是一个容纳多个线程的容器其中的线程可以反复使用省去了频繁创建线程对象的操作无需反复创建线程而消耗过多资源。 我们详细的解释一下为什么要使用线程池
在java中如果每个请求到达就创建一个新线程开销是相当大的。在实际使用中创建和销毁线程花费的时间和消耗的系统资源都相当大甚至可能要比在处理实际的用户请求的时间和资源要多的多。除了创建和销毁线程的开销之外活动的线程也需要消耗系统资源。如果在一个jvm里创建太多的线程可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。为了防止资源不足需要采取一些办法来限制任何给定时刻处理的请求数目尽可能减少创建和销毁线程的次数特别是一些资源耗费比较大的线程的创建和销毁尽量利用已有对象来进行服务。
线程池主要用来解决线程生命周期开销问题和资源不足问题。通过对多个任务重复使用线程线程创建的开销就被分摊到了多个任务上了而且由于在请求到达时线程已经存在所以消除了线程创建所带来的延迟。这样就可以立即为请求服务使用应用程序响应更快。另外通过适当的调整线程中的线程数目可以防止出现资源不足的情况。
使用线程池方式–Runnable接口
通常线程池都是通过线程池工厂创建再调用线程池中的方法获取线程再通过线程去执行任务方法。
Executors线程池创建工厂类 public static ExecutorService newFixedThreadPool(int nThreads)返回线程池对象 ExecutorService线程池类 Future? submit(Runnable task)获取线程池中的某一个线程对象并执行 Future接口用来记录线程任务执行完毕后产生的结果。线程池创建与使用
使用线程池中线程对象的步骤
创建线程池对象创建Runnable接口子类对象提交Runnable接口子类对象关闭线程池
/*** JDK5的新特性 线程池技术* 实现一个线程池使用util包下的concurrent工厂类Executors中的静态方法创建线程对象并设定线程的个数* static ExecutorService newFixedThreadPool(int 个数) 返回线程池对象* 返回的是ExecutorService接口的实现类(线程池的对象)* 接口实现类对象调用方法submit(Runnable r)提交一个线程执行的任务*/
public class ThreadPoolDemo {public static void main(String[] args) {//调用工厂类的静态方法创建线程池对象//返回的是线程池对象即ExecutorService接口ExecutorService es Executors.newFixedThreadPool(2);//调用接口实现类对象es中的方法submit提交执行任务//将Runnable的实现类对象传递即可es.submit(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName()正在执行新任务);}});//销毁线程池的方法(一般不会使用)es.shutdown();}
}使用线程池方式—Callable接口
Callable接口与Runnable接口功能相似用来指定线程的任务。其中的call()方法用来返回线程任务执行完毕后的结果call方法可抛出异常。ExecutorService线程池类 Future submit(Callable task)获取线程池中的某一个线程对象并执行线程中的call()方法 Future接口用来记录线程任务执行完毕后产生的结果。线程池创建与使用
使用线程池中线程对象的步骤
创建线程池对象创建Callable接口子类对象提交Callable接口子类对象关闭线程池
import java.util.concurrent.*;/*** JDK5的新特性 线程池技术 实现Callable接口* 实现步骤* Executor工厂类静态方法newFixedThreadPool创建线程池对象* 线程池实现对象ExecutorService接口实现类调用submit方法提交线程* submit(Callable c)*/
public class ThreadPoolDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService es Executors.newFixedThreadPool(2);//提交线程任务的方法submit将返回一个Future接口的实现类FutureString f es.submit(/*** Callable接口实现类作为线程提交任务出现* 使用方法返回值*/new CallableString() {Overridepublic String call() throws Exception {return Thread.currentThread().getName()正在执行新任务;}});//获取返回值System.out.println(f.get());}
}案例多线程实现异步计算
import java.util.concurrent.Callable;/*** Callable接口的实现类*/
public class CountDemo implements CallableInteger {private int a;private int b;public CountDemo(int a, int b) {this.a a;this.b b;}Overridepublic Integer call() throws Exception {int result 0;for (int ia; ib; i){resultresulti;}return result;}
}import java.util.concurrent.*;/*** 使用多线程技术进行求和计算* 要求两个线程处理一个线程计算1...100的和另一个计算101...200的和*/
public class ThreadPoolDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {ExecutorService es Executors.newFixedThreadPool(2);//线程一计算1...100的和FutureInteger result1 es.submit(new CountDemo(1,100));//线程二计算101...200的和FutureInteger result2 es.submit(new CountDemo(101,200));int finalResult result1.get() result2.get();System.out.println(最终结果为 finalResult);}
}自定义线程池ThreadPoolExecutor 内置的线程池, 不推荐生产使用 Executors.newCachedThreadPool(); Executors.newFixedThreadPool(10); Executors.newScheduledThreadPool(10); Executors.newSingleThreadExecutor(); 上述的方法不推荐在生产当中使用在生产场景下必须使用ThreadPoolExecutor构建线程池这样可以明确线程池的运行规则创建符合自己业务场景需要的线程池避免资源耗尽的风险。以下是一个简单的示例
构造函数 corePoolSize指定了线程池中的线程数量它的数量决定了添加的任务是开辟新的线程去执行还是放到workQueue任务队列中去。maximumPoolSize指定了线程池中的最大线程数量这个参数会根据你使用的workQueue任务队列的类型决定线程池会开辟的最大线程数量。keepAliveTime当线程池中空闲线程数量超过corePoolSize时多余的线程会在多长时间内被摧毁。unitkeepAliveTime的单位。workQueue任务队列被添加到线程池中但尚未被执行的任务分为直接提交队列有界任务队列无界任务队列优先任务队列。threadFactory线程工厂用于创建线程。handler拒绝策略当任务太多来不及处理的时候会用这种策略拒绝任务。
corePoolSize和maximumPoolSize
ThreadPoolExecutor executorPool new ThreadPoolExecutor(5, //corePoolSize10, //maximumPoolSize3, //keepAliveTimeTimeUnit.SECONDS, //unitnew ArrayBlockingQueueRunnable(50));上边代码意思是有5个核心线程数10个最大线程数任务队列是50个线程。
在运行时JVM首先为前5个新任务创建新线程此时再来任务就放入任务队列中直到任务队列已放满此时再来新任务JVM就会创建新线程,直到此时线程池中达到10个线程了就停止创建即达到了最大线程数此时再来新任务就会使用配置的拒绝策略新任务的提交。
workQueue任务队列
分为直接提交队列有界任务队列无界任务队列优先任务队列。
直接提交队列
设置SynchronousQueue队列SynchronousQueue是一个特殊的BlockingEueue它没有容量每执行一个插入操作就会阻塞需要再执行一个删除操作才能被唤醒反之每一个删除操作也都要等待对应的插入操作。
import java.util.concurrent.*;
public class ThreadPool {private static ExecutorService pool;public static void main(String[] args) {pool new ThreadPoolExecutor(1, 2,1000,TimeUnit.MICROSECONDS,new SynchronousQueueRunnable(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i 0; i 3; i) {pool.execute(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName());}});}pool.shutdown();}
}------输出结果---------
pool-1-thread-2
pool-1-thread-1
Exception in thread main java.util.concurrent.RejectedExecutionException: Task com.if010.thread.ThreadPool$15e2de80c rejected from java.util.concurrent.ThreadPoolExecutor1d44bcfa[Running, pool size 2, active threads 1, queued tasks 0, completed tasks 0]at java.util.concurrent.ThreadPoolExecutor$AbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063)at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830)at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379)at com.if010.thread.ThreadPool.main(ThreadPool.java:16)从输出结果当中看到当任务队列为SynchronousQueue时创建的线程数量大于maximumPoolSize时直接执行了拒绝策略抛出异常。
使用SynchronousQueue队列提交的任务不会被保存会马上提交执行。如果用于执行任务的线程数量小于maximumPoolSize时则尝试创建新的线程如果达到maximumPoolSize设置的最大值则会根据设置的handler执行拒绝策略。
有界任务队列
使用ArrayBlockingQueue实现
import java.util.concurrent.*;
public class ThreadPool {private static ExecutorService pool;public static void main(String[] args) {pool new ThreadPoolExecutor(1, 2,1000,TimeUnit.MICROSECONDS,new ArrayBlockingQueueRunnable(10),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i 0; i 3; i) {pool.execute(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName());}});}pool.shutdown();}
}------输出结果-------
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1使用ArrayBlockingQueue有界任务队列当有新的任务需要执行的时候线程池会创建新的线程直到创建的线程数量达到corePoolSize时会将新的任务加入到等待队列中。当等待队列满了的时候也会继续创建线程直到线程数量达到maximumPoolSize设置的最大线程数量时则会执行拒绝策略。在这种情况下线程数量的上限与有界任务队列的状态有直接的关系如果有界队列初始容量较大或者没有达到超负荷的状态线程数将一直维持在corePoolSize以下反之当任务队列满了时则会以maximumPoolSize为最大线程数上限。
无界任务队列
使用LinkedBlockingQueue实现。
import java.util.concurrent.*;
public class ThreadPool {private static ExecutorService pool;public static void main(String[] args) {pool new ThreadPoolExecutor(1, 2,1000,TimeUnit.MICROSECONDS,new LinkedBlockingDequeRunnable(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i 0; i 3; i) {pool.execute(new Runnable() {Overridepublic void run() {System.out.println(Thread.currentThread().getName());}});}pool.shutdown();}
}------输出结果-------
pool-1-thread-1
pool-1-thread-1
pool-1-thread-1使用无界任务队列线程池的任务队列可以无限制的添加新任务而线程池创建的最大线程数量就是corePoolSize设置的数量也就是说此时maximumPoolSize参数是无效的。当有新的任务加入时则会直接进入等待队列所以你一定要注意任务提交与处理直接的协调要防止等待队列中的任务由于无法及时处理而一直增长导致资源耗尽。
优先任务队列
使用PriorityBlockingQueue实现。
import java.util.concurrent.*;
public class ThreadPool {private static ExecutorService pool;public static void main(String[] args) {pool new ThreadPoolExecutor(1, 2,1000,TimeUnit.MICROSECONDS,new PriorityBlockingQueueRunnable(),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());for (int i 0; i 10; i) {pool.execute(new ThreadTask(i));}pool.shutdown();}
}class ThreadTask implements Runnable, ComparableThreadTask {private int priority;public int getPriority() {return priority;}public void setPriority(int priority) {this.priority priority;}public ThreadTask() {}public ThreadTask(int priority) {this.priority priority;}//当前对象与其它对象比较当优先级大时返回-1优先级小时返回1//priority值越小优先级越高Overridepublic int compareTo(ThreadTask o) {return this.priority o.priority ? -1 : 1;}Overridepublic void run() {try {//阻塞线程使后续任务进入缓存队列Thread.sleep(1000);System.out.println(当前线程优先级 this.priority ,线程名字 Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}
}------输出结果-------
当前线程优先级0,线程名字pool-1-thread-1
当前线程优先级9,线程名字pool-1-thread-1
当前线程优先级8,线程名字pool-1-thread-1
当前线程优先级7,线程名字pool-1-thread-1
当前线程优先级6,线程名字pool-1-thread-1
当前线程优先级5,线程名字pool-1-thread-1
当前线程优先级4,线程名字pool-1-thread-1
当前线程优先级3,线程名字pool-1-thread-1
当前线程优先级2,线程名字pool-1-thread-1
当前线程优先级1,线程名字pool-1-thread-1除了第一个任务直接创建线程执行外其它的任务都被放入了优先任务队列按照优先级进行重新排序执行且线程池的线程数一直为corePoolSize也就是一个说明此时maximumPoolSize设置无效。
也就是说PriorityBlockingQueue是一个特殊的无界队列无论添加了多少个任务线程池创建的线程数也不会超过corePoolSize设置的数量。
threadFactory
线程池中线程是通过ThreadPoolExecutor中的ThreadFactory线程工厂创建。通过自定义ThreadFactory可以按需要对线程池中创建的线程进行一些特殊设置比如命名优先级。
import java.util.concurrent.*;
public class ThreadPool {private static ExecutorService pool;public static void main(String[] args) {pool new ThreadPoolExecutor(2, 4,1000,TimeUnit.MICROSECONDS,new ArrayBlockingQueueRunnable(5),new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {System.out.println(线程 r.hashCode() 创建);//线程命名Thread th new Thread(r, 线程名字 r.hashCode());return th;}},new ThreadPoolExecutor.CallerRunsPolicy());for (int i 0; i 10; i) {pool.execute(new Runnable() {Overridepublic void run() {//输出执行线程的名称System.out.println(执行中的线程名字 Thread.currentThread().getName());}});}pool.shutdown();}
}------输出结果-------
线程 1627674070 创建
线程 1360875712 创建
执行中的线程名字线程名字 1627674070
线程 1625635731 创建
执行中的线程名字线程名字 1360875712
执行中的线程名字线程名字 1627674070
执行中的线程名字线程名字 1360875712
执行中的线程名字线程名字 1627674070
执行中的线程名字线程名字 1627674070
执行中的线程名字线程名字 1625635731
执行中的线程名字线程名字 1360875712
执行中的线程名字线程名字 1625635731
执行中的线程名字线程名字 1627674070handler
为防止资源被耗尽任务队列都会选择创建有界任务队列但是这种模式下如果出现任务队列已满并且线程池创建的线程数已达到最大线程数时就需要指定ThreadPoolExecutor的RejectedExecutionHandler参数提供拒绝策略来处理线程池超载情况。
ThreadPoolExecutor自带的拒绝策略如下
AbortPolicy策略该策略直接抛出异常阻止系统正常工作CallerRunsPolicy策略如果线程池的线程数量达到上限该策略会把任务队列中的任务放在调用者线程中运行DiscardOledestPolicy策略该策略会丢弃任务队列中最老的一个任务也就是当前任务队列中最先被添加进去的DiscardPolicy策略该策略会丢弃无法处理的任务不予任何处理
以上内置的策略都实现了RejectedExecutionHandler接口当然也可以自定义拒绝策略
import java.util.concurrent.*;
public class ThreadPool {private static ExecutorService pool;public static void main(String[] args) {pool new ThreadPoolExecutor(1, 2,1000,TimeUnit.MICROSECONDS,new ArrayBlockingQueueRunnable(5),Executors.defaultThreadFactory(),new RejectedExecutionHandler() {Overridepublic void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {System.out.println(r.toString() 执行了拒绝策略);}});for (int i 0; i 10; i) {pool.execute(new Runnable() {Overridepublic void run() {try {//让线程阻塞使后续任务进入后续队列Thread.sleep(1000);//输出执行线程的名称System.out.println(执行中的线程名字 Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}});}pool.shutdown();}
}------输出结果-------
com.if010.thread.ThreadPool$260e53b93执行了拒绝策略
com.if010.thread.ThreadPool$25e2de80c执行了拒绝策略
com.if010.thread.ThreadPool$21d44bcfa执行了拒绝策略
执行中的线程名字pool-1-thread-1
执行中的线程名字pool-1-thread-2
执行中的线程名字pool-1-thread-1
执行中的线程名字pool-1-thread-2
执行中的线程名字pool-1-thread-2
执行中的线程名字pool-1-thread-1
执行中的线程名字pool-1-thread-2当任务加入了休眠阻塞执行需要花费一定时间导致会有一定的任务被丢弃从而执行自定义的拒绝策略。
ThreadPoolExecutor扩展
beforeExecute线程池中的任务运行前执行afterExecute线程池中的任务运行完毕后执行terminated线程池退出后执行
通过这三个接口可以监控每个任务的开始和结束时间
import java.util.concurrent.*;
public class ThreadPool {private static ExecutorService pool;public static void main(String[] args) {pool new ThreadPoolExecutor(2, 4,1000,TimeUnit.MICROSECONDS,new ArrayBlockingQueueRunnable(5),new ThreadFactory() {Overridepublic Thread newThread(Runnable r) {System.out.println(线程 r.hashCode() 创建);//线程命名Thread th new Thread(r, 线程 r.hashCode());return th;}},new ThreadPoolExecutor.CallerRunsPolicy()) {Overrideprotected void beforeExecute(Thread t, Runnable r) {System.out.println(准备执行 ((ThreadTask) r).getTaskName());}Overrideprotected void afterExecute(Runnable r, Throwable t) {System.out.println(执行完毕 ((ThreadTask) r).getTaskName());}Overrideprotected void terminated() {System.out.println(线程池退出);}};for (int i 0; i 10; i) {pool.execute(new ThreadTask(Task i));}pool.shutdown();}
}class ThreadTask implements Runnable {private String taskName;public String getTaskName() {return taskName;}public void setTaskName(String taskName) {this.taskName taskName;}public ThreadTask(String taskName) {this.taskName taskName;}Overridepublic void run() {//输出执行线程的名称System.out.println(任务名称 this.getTaskName() 线程名称 Thread.currentThread().getName());}
}------输出结果-------
线程 1627674070 创建
线程 1360875712 创建
准备执行 Task0
任务名称Task0 线程名称线程 1627674070
线程 1625635731 创建
准备执行 Task1
执行完毕 Task0
任务名称Task1 线程名称线程 1360875712
准备执行 Task2
线程 1580066828 创建
任务名称Task2 线程名称线程 1627674070
执行完毕 Task2
执行完毕 Task1
准备执行 Task8
任务名称Task8 线程名称线程 1580066828
执行完毕 Task8
准备执行 Task5
任务名称Task5 线程名称线程 1580066828
执行完毕 Task5
准备执行 Task7
任务名称Task7 线程名称线程 1625635731
执行完毕 Task7
准备执行 Task6
准备执行 Task4
准备执行 Task3
任务名称Task3 线程名称线程 1360875712
任务名称Task4 线程名称线程 1627674070
执行完毕 Task4
任务名称Task6 线程名称线程 1580066828
执行完毕 Task6
准备执行 Task9
任务名称Task9 线程名称线程 1625635731
执行完毕 Task9
执行完毕 Task3
线程池退出对于这三个方法的重写可以对线程池中线程的运行状态进行监控在其执行前后打印相关信息。使用shutdown方法可以比较安全的关闭线程池当调用该方法后线程池不再接受后续添加的任务但是此时线程池不会马上退出而是等到添加到线程池中的任务都已经完成处理后才会退出。
线程分配流程 线程安全
如果有多个线程在同时运行而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的而且其他的变量的值也和预期的是一样的就是线程安全的。 我们通过一个案例演示线程的安全问题 电影院要卖票我们模拟电影院的卖票过程。假设要播放的电影是 “功夫熊猫3”本次电影的座位共10个(本场电影只能卖10张票)。 /*** 模拟电影院的售票窗口实现多个窗口同时卖功夫熊猫3这场电影票(多个窗口一起卖这10张票)* 窗口线程对象来模拟* 票Runnable接口子类来模拟*/
public class MovieTestDemo {public static void main(String[] args) {//创建Runnable接口实现类对象Runnable t new Tickets();//创建3个Thread类对象传递Runnable接口实现类Thread t0 new Thread(t);Thread t1 new Thread(t);Thread t2 new Thread(t);t0.start();t1.start();t2.start();}
}class Tickets implements Runnable{//定义出10张票private int tickets 10;Overridepublic void run() {while (true){//对票数进行判断大于0则可以出售变量--操作if (tickets 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() 出售第 tickets-- 张票);} else {System.out.println(售票结束);break;}}}
}---------输出结果------
Thread-1 出售第10张票
Thread-0 出售第10张票
Thread-2 出售第9张票
Thread-0 出售第8张票
Thread-1 出售第7张票
Thread-2 出售第6张票
Thread-1 出售第5张票
Thread-0 出售第4张票
Thread-2 出售第3张票
Thread-1 出售第2张票
Thread-0 出售第1张票
售票结束
Thread-2 出售第0张票
售票结束
Thread-1 出售第-1张票
售票结束运行结果发现上面程序出现了问题
票出现了重复的票错误的票 0、-1
其实线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作而无写操作一般来说这个全局变量是线程安全的若有多个线程同时执行写操作一般都需要考虑线程同步否则的话就可能影响线程安全。
线程安全处理Synchronized
java中提供了线程同步机制它能够解决上述的线程安全问题。
线程同步的方式有两种
同步代码块同步方法 同步代码块
同步代码块: 在代码块声明上 加上synchronized
synchronized (锁对象) {可能会产生线程安全问题的代码
}同步代码块中的锁对象可以是任意的对象但多个线程时要使用同一个锁对象才能够保证线程安全。
使用同步代码块对电影院卖票案例中Ticket类进行如下代码修改
/*** 模拟电影院的售票窗口实现多个窗口同时卖功夫熊猫3这场电影票(多个窗口一起卖这10张票)* 窗口线程对象来模拟* 票Runnable接口子类来模拟*/
public class MovieTestDemo {public static void main(String[] args) {//创建Runnable接口实现类对象Runnable t new Tickets();//创建3个Thread类对象传递Runnable接口实现类Thread t0 new Thread(t);Thread t1 new Thread(t);Thread t2 new Thread(t);t0.start();t1.start();t2.start();}
}class Tickets implements Runnable{//定义出10张票private int tickets 10;Object lock new Object();Overridepublic void run() {while (true){synchronized (lock) {//对票数进行判断大于0则可以出售变量--操作if (tickets 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() 出售第 tickets-- 张票);} else {System.out.println(售票结束);break;}}}}
}同步方法
同步方法在方法声明上加上synchronized
public synchronized void method(){//可能会产生线程安全问题的代码
}同步方法中的锁对象是 this
public void method(){synchronized(this) {//可能会产生线程安全问题的代码}
}使用同步方法对电影院卖票案例中Ticket类进行如下代码修改
/*** 模拟电影院的售票窗口实现多个窗口同时卖功夫熊猫3这场电影票(多个窗口一起卖这10张票)* 窗口线程对象来模拟* 票Runnable接口子类来模拟*/
public class MovieTestDemo {public static void main(String[] args) {//创建Runnable接口实现类对象Runnable t new Tickets();//创建3个Thread类对象传递Runnable接口实现类Thread t0 new Thread(t);Thread t1 new Thread(t);Thread t2 new Thread(t);t0.start();t1.start();t2.start();}
}class Tickets implements Runnable{//定义出10张票private int tickets 10;Overridepublic void run() {while (true){if (buyTickets() false){System.out.println(售票结束);return;}}}public synchronized boolean buyTickets(){if (tickets 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() 出售第 tickets-- 张票);return true;} else {return false;}}
}静态同步方法: 在方法声明上加上static synchronized
public static synchronized void method(){//可能会产生线程安全问题的代码
}静态同步方法中的锁对象是 类名.class
public static void method(){synchronized(MovieTestDemo.class) {//可能会产生线程安全问题的代码}
}死锁
同步锁使用的弊端当线程任务中出现了多个同步(多个锁)时如果同步中嵌套了其他的同步。这时容易引发一种现象程序出现无限等待这种现象我们称为死锁。这种情况能避免就避免掉。 synchronzied(A锁){synchronized(B锁){//肯能产生线程安全问题的代码}
}实现死锁
public class TestLock {public static void main(String[] args) {LockDemo lockDemo new LockDemo();Thread t0 new Thread(lockDemo);Thread t1 new Thread(lockDemo);t0.start();t1.start();}
}class LockDemo implements Runnable{private int i0;Overridepublic void run() {while (true){if (i%2 0){//先拿A锁再那B锁synchronized (LockA.class){System.out.println(Thread.currentThread().getName()号玩家在 if 已拿到 A 锁);synchronized (LockB.class){System.out.println(Thread.currentThread().getName()号玩家在 if 已拿到 B 锁);}}} else {//先拿B锁再那A锁synchronized (LockB.class){System.out.println(Thread.currentThread().getName()号玩家在 else 已拿到 B 锁);synchronized (LockA.class){System.out.println(Thread.currentThread().getName()号玩家在 else 已拿到 A 锁);}}}i;}}
}class LockA{private LockA(){}public static final LockA locka new LockA();
}class LockB{private LockB(){}public static final LockB lockb new LockB();
}Lock接口
查阅API查阅Lock接口描述Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 Lock提供了一个更加面对对象的锁在该锁中提供了更多的操作锁的功能。
我们使用Lock接口以及其中的lock()方法和unlock()方法替代同步对电影院卖票案例中Ticket类进行如下代码修改
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** 使用JDK1.5之后的接口Lock替换同步代码块实现线程的安全性* Lock接口方法* lock() 获取锁* unlock() 释放锁* 实现类ReentrantLock*/
public class MovieTestDemo {public static void main(String[] args) {//创建Runnable接口实现类对象Runnable t new Tickets();//创建3个Thread类对象传递Runnable接口实现类Thread t0 new Thread(t);Thread t1 new Thread(t);Thread t2 new Thread(t);t0.start();t1.start();t2.start();}
}class Tickets implements Runnable{//定义出售的票源private int tickets 10;//在类成员位置创建Lock接口实现类对象private Lock lock new ReentrantLock();Overridepublic void run() {while (true){//调用Lock接口方法lock获取锁lock.lock();try {if (tickets 0) {try {Thread.sleep(10);} catch (InterruptedException e) {throw new RuntimeException(e);}System.out.println(Thread.currentThread().getName() 出售第 tickets-- 张票);} else {System.out.println(售票结束);return;}} catch (Exception e){} finally {//释放锁调用Lock接口方法unlocklock.unlock();}}}
}等待唤醒机制
在开始讲解等待唤醒机制之前有必要搞清一个概念——线程之间的通信多个线程在处理同一个资源但是处理的动作线程的任务却不相同。通过一定的手段使各个线程能有效的利用资源。而这种手段即—— 等待唤醒机制。
等待唤醒机制所涉及到的方法
wait :等待将正在执行的线程释放其执行资格 和 执行权并存储到线程池中。notify唤醒唤醒线程池中被wait的线程一次唤醒一个而且是任意的。notifyAll 唤醒全部可以将线程池中的所有wait() 线程都唤醒。
其实所谓唤醒的意思就是让 线程池中的线程具备执行资格。必须注意的是这些方法都是在 同步中才有效。同时这些方法在使用时必须标明所属锁这样才可以明确出这些方法操作的到底是哪个锁上的线程。
仔细查看JavaAPI之后发现这些方法 并不定义在 Thread中也没定义在Runnable接口中却被定义在了Object类中为什么这些操作线程的方法定义在Object类中
因为这些方法在使用时必须要标明所属的锁而锁又可以是任意对象。能被任意对象调用的方法一定定义在Object类中。 接下里我们先从一个简单的示例入手: 如上图说示输入线程向Resource中输入name ,sex , 输出线程从资源中输出先要完成的任务是
当input发现Resource中没有数据时开始输入输入完成后叫output来输出。如果发现有数据就wait();当output发现Resource中没有数据时就wait(); 当发现有数据时就输出然后叫醒input来输入数据。
下面代码模拟等待唤醒机制的实现
/*** 定义资源类有两个成员变量namesex* 同时有两个线程,对资源中的变量操作* 线程一对name,age赋值* 线程二对name,age输出*/
public class Resource {public String name;public String sex;public boolean flag false;
}/*** 输入的线程对资源对象Resource中的成员变量赋值*/
class Input implements Runnable{private Resource r;public Input(Resource r) {this.r r;}Overridepublic void run() {int i 0;while (true) {synchronized (r) {if (r.flag){try {r.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}} else {if (i % 2 0) {r.name 小王;r.sex 男;} else {r.name 小林;r.sex 女;}i;r.flag true;r.notify();}}}}
}/*** 输出线程对Resource的成员变量进行打印输出*/
class Output implements Runnable{private Resource r;public Output(Resource r) {this.r r;}Overridepublic void run() {while (true){synchronized (r) {if (r.flag) {System.out.println(r.name \t r.sex);r.flag false;r.notify();} else {try {r.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}}}}
}/*** 开启输入线程和输出线程*/
class TestDome{public static void main(String[] args) {Resource r new Resource();Input in new Input(r);Output out new Output(r);Thread t_in new Thread(in);Thread t_out new Thread(out);t_in.start();t_out.start();}
}这里要注意的是wait和notify需要使用锁对象进行调用否则会报以下错误
Exception in thread Thread-1 java.lang.IllegalMonitorStateExceptionat java.lang.Object.wait(Native Method)at java.lang.Object.wait(Object.java:502)at com.if010.thread.Output.run(Resource.java:73)at java.lang.Thread.run(Thread.java:750)