多线程
线程:线程是操作系统能够进行运算调度的最小单位。他被包含在进程之中,是进程中的实际运作单位。
进程:进程是程序的基本执行实体
什么是多线程:有了多线程,我们就可以让程序同时做很多事情
多线程的作用:提高效率
多线程的应用场景:只要你想让多个事情同时运行就需要用到多线程
多线程的两个概念
并发:在同一时刻,有多个指令在单个CPU上交替执行
并行:在同一时刻,有多个指令在多个CPU上同时执行
多线程的实现方式
①继承Thread类的方式进行实现
多线程的第一种启动方式: 1.自己定义一个类继承Thread 2.重写run方法 3.创建子类对象,并启动线程
packagecom.cjj.threadcase1;
publicclassMyThreadextendsThread {
@Override
publicvoidrun() {
//书写线程要执行的代码
for (inti=0; i<100; i++) {
System.out.println("helloworld"+getName());
}
}
}
packagecom.cjj.threadcase1;
publicclassThreadDemo01 {
publicstaticvoidmain(String[] args) {
/*
多线程的第一种启动方式:
1.自己定义一个类继承Thread
2.重写run方法
3.创建子类对象,并启动线程
*/
MyThreadmt=newMyThread();
MyThreadmt2=newMyThread();
mt.setName("线程1");
mt2.setName("线程2");
mt.start();
mt2.start();
}
}
②实现Runnable接口的方式进行实现
多线程的第二种启动方式 1.自己定义一个类实现Runnable接口 2.重写里面的run方法 3.创建自己的类的对象 4.创建一个Thread类的对象,并开启多线程
packagecom.cjj.threadcase2;
publicclassMyRunimplementsRunnable{
@Override
publicvoidrun() {
//书写线程要执行的代码
for (inti=0; i<100; i++) {
//获取到当前线程的对象
//Thread t1 = Thread.currentThread();
System.out.println("Hellloword"+Thread.currentThread().getName());
}
}
}
packagecom.cjj.threadcase2;
publicclassThreadDemo01 {
publicstaticvoidmain(String[] args) {
/*
多线程的第二种启动方式
1.自己定义一个类实现Runnable接口
2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启多线程
*/
//创建MyRun对象
//表示多线程要执行的任务
MyRunmr=newMyRun();
//创建线程对象
Threadt1=newThread(mr);
Threadt2=newThread(mr);
//给线程设置名字
t1.setName("线程1");
t2.setName("线程2");
//开启线程
t1.start();
t2.start();
}
}
③利用Callable接口和Future接口方式实现
多线程的第三种实现方式 特点:可以获取到多线程执行的结果 1.创建一个类MyCallable实现Callable接口 2.重写call(有返回值,表示多线程运行的结果)
3.创建MyCallable的对象(表示多线程要执行的对象)
4.创建Future的对象(作用管理多线程运行的结果)
5.创建Thread类的对象,并启动(表示线程)
packagecom.cjj.threadcase3;
importjava.util.concurrent.Callable;
publicclassMyCallableimplementsCallable<Integer> {
@Override
publicIntegercall() throwsException {
//求1-100之间的和
intsum=0;
for (inti=0; i<=100; i++) {
sum=sum+i;
}
returnsum;
}
}
packagecom.cjj.threadcase3;
importjava.util.concurrent.ExecutionException;
importjava.util.concurrent.Future;
importjava.util.concurrent.FutureTask;
importjava.util.function.Function;
publicclassThreadDemo01 {
publicstaticvoidmain(String[] args) throwsExecutionException, InterruptedException {
/*
多线程的第三种实现方式
特点:可以获取到多线程执行的结果
1.创建一个类MyCallable实现Callable接口
2.重写call(有返回值,表示多线程运行的结果)
3.创建MyCallable的对象(表示多线程要执行的对象)
4.创建Future的对象(作用管理多线程运行的结果)
5.创建Thread类的对象,并启动(表示线程)
*/
//创建MyCallable的对象(表示多线程要执行的对象)
MyCallablemc=newMyCallable();
//创建Future的对象(作用管理多线程运行的结果)
FutureTask<Integer>ft=newFutureTask<>(mc);
//创建Thread类的对象,并启动(表示线程)
Threadt=newThread(ft);
t.start();
//获取多线程运行的结果
Integerresult=ft.get();
System.out.println(result);
}
}
多线程三种方式对比
优点 | 缺点 | |
继承Thread类 | 编程比较简单,可以直接使用Thead类中的方法 | 可以扩展性较差,不能再继承其他的类 |
实现Runnable接口 实现Callable接口 | 扩展性强,实现该接口的同时还可以继承其他类 | 编程相对复杂,不能直接使用Thread类中的方法 |
常见的成员方法
方法名称 | 说明 |
String getName() | 返回此线程的名称 |
void setName(String name) | 设置线程的名字(构造方法也可以设置名字) |
static thread currentThread() | 获取当前线程的对象 |
static void sleep(long time) | 让线程休眠的时间,单位为毫秒 |
setPriority(int newPriority) | 设置线程的优先级 |
final int getPriority() | 获得线程的优先级 |
final void setDaemon(boolean on) | 设置为守护线程p |
public static void yield() | 出让线程/礼让线程 |
public static void join() | 插入线程/插队线程 |
packagecom.cjj.threadmethod;
publicclassMyThreadextendsThread{
publicMyThread() {
}
publicMyThread(Stringname) {
super(name);
}
@Override
publicvoidrun() {
for (inti=0; i<100; i++) {
try {
Thread.sleep(1000);
} catch (InterruptedExceptione) {
thrownewRuntimeException(e);
}
System.out.println(getName()+"@"+i);
}
}
}
packagecom.cjj.threadmethod;
publicclassThreadMethodDemo01 {
publicstaticvoidmain(String[] args) throwsInterruptedException {
/*
String getName() 返回此线程的名称
void setName(String name) 设置线程的名字(构造方法也可以设置名字)
细节:
1.如果我们没有给线程设置名字,线程也是有默认名字的
格式:Thread-X(X序号,从0开始)
2.如果我们要给线程设置名字,可以用Set方法进行设置,也可以使用构造方法设置
static thread currentThread()获取当前线程的对象
细节:
当JVM虚拟机启动之后,会自动的启动多线程
其中有一条线程就叫做main线程
其他的作用就是去调用main方法,并执行里面的代码
在以前,我们写的代码,其实都是在运行在main线程当中
static void sleep(long time) 让线程休眠的时间,单位为毫秒
细节:
1.那条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2.方法的参数:就表示睡眠的时间,单位毫秒
1秒=1000毫秒
3.当时间到了之后,线程会自动的醒来,继续执行下面的其他代码
*/
/*MyThread mt = new MyThread("飞机");
MyThread mt2 = new MyThread("坦克");
mt.start();
mt2.start();*/
/* //哪条线程执行到这个方法,此时获取的就是那条线程的对象
Thread t = Thread.currentThread();
String name = t.getName();
System.out.println(name);
*/
System.out.println("1111111111111");
Thread.sleep(5000);
System.out.println("222222222222");
}
}
线程优先级
抢占式调度
随机
packagecom.cjj.threadmethod2;
publicclassMyThreadimplementsRunnable {
@Override
publicvoidrun() {
for (inti=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() +"---"+i);
}
}
}
packagecom.cjj.threadmethod2;
publicclassThreadMethodDemo {
publicstaticvoidmain(String[] args) {
/*
setPriority(int newPriority) 设置线程的优先级
final int getPriority() 获得线程的优先级
*/
//创建线程要执行的参数对象
MyThreadmt=newMyThread();
//2.创建线程对象
Threadt1=newThread(mt,"飞机");
Threadt2=newThread(mt,"坦克");
// System.out.println(t1.getPriority());
//System.out.println(t2.getPriority());
t1.setPriority(1);
t2.setPriority(10);
t1.start();
t2.start();
}
}
守护线程
packagecom.cjj.threadmethod3;
publicclassMythread1extendsThread {
@Override
publicvoidrun() {
for (inti=0; i<10; i++) {
System.out.println(getName() +"@"+i);
}
}
}
package com.cjj.threadmethod3;
public class Mythread2 extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
}
}
}
package com.cjj.threadmethod3;
public class ThreadDemo {
public static void main(String[] args) {
/*
final void setDaemon(boolean on)设置为守护线程
细节:
当其他的非守护线程执行完毕之后,守护线程会陆陆续续结束
通俗易懂:
当飞机线程结束了,那么坦克也没有存在的必要了
*/
Mythread1 mt1 = new Mythread1();
Mythread2 mt2 = new Mythread2();
mt1.setName("飞机");
mt2.setName("坦克");
mt2.setDaemon(true);
mt1.start();
mt2.start();
}
}
礼让线程
package com.cjj.threadmethod4;
public class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(getName() + "@" + i);
//表示出让当前CPU的执行权
Thread.yield();
}
}
}
packagecom.cjj.threadmethod4;
publicclassThreadDemo {
publicstaticvoidmain(String[] args) {
/*
public static void yield()出让线程/礼让线程
*/
MyThreadt1=newMyThread();
MyThreadt2=newMyThread();
t1.setName("飞机");
t2.setName("坦克");
t1.start();
t2.start();
}
}
插入线程
packagecom.cjj.threadmethod5;
publicclassMyThreadextendsThread {
@Override
publicvoidrun() {
for (inti=0; i<100; i++) {
System.out.println(getName() +"@"+i);
}
}
}
packagecom.cjj.threadmethod5;
publicclassThreadDemo {
publicstaticvoidmain(String[] args) throwsInterruptedException {
/*
public static void join()插入线程/插队线程
*/
MyThreadt=newMyThread();
t.setName("土豆");
t.start();
t.join();
//执行在main方法当中的
for (inti=0; i<10; i++) {
System.out.println("main线程"+i);
}
}
}
同步代码块
把操作共享数据的代码锁起来
//格式
synchronized(锁){
操作共享数据的代码
}
特点1:锁默认打开,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开
package com.cjj.threadsafe1;
public class MyThread extends Thread{
static int ticket = 0;//表示这个类的所有对象,都共享ticket数据
//锁对象,一定要是唯一的
static Object obj = new Object();
@Override
public void run() {
while(true){
//同步代码块
synchronized (obj){
if(ticket< 100){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
ticket++;
System.out.println(getName()+"正在卖"+ticket+"张票");
}else {
break;
}
}
}
}
}
package com.cjj.threadsafe1;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院正在上映国产大片,共100张票,三个窗口卖票,请设计程序模拟电影院卖票
*/
//创建线程对象
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
//起名字
t1.setName("售票处1");
t2.setName("售票处2");
t3.setName("售票处3");
//开启线程
t1.start();
t2.start();
t3.start();
}
}
同步方法
就是把synchronized关键字加到方法上
//格式
修饰符synchronized返回值类型方法名(方法参数){...}
特点1:同步方法是锁住方法里面所有的代码
特点2:锁对象不能自己指定 非静态:this
静态:当前类的字节码文件对象
packagecom.cjj.threadsafe2;
publicclassMyRunableimplementsRunnable {
intticket=0;
@Override
publicvoidrun() {
while (true) {
if (method()) break;
}
}
privatesynchronizedbooleanmethod() {
//3.判断共享数据是否到了末尾,
if (ticket==100) {
returntrue;
} else {
try {
Thread.sleep(10);
} catch (InterruptedExceptione) {
thrownewRuntimeException(e);
}
ticket++;
System.out.println(Thread.currentThread().getName() +"正在卖"+ticket+"张票");
}
returnfalse;
}
}
package com.cjj.threadsafe2;
public class ThreadDemo {
public static void main(String[] args) {
/*
需求:
某电影院正在上映国产大片,共100张票,三个窗口卖票,请设计程序模拟电影院卖票
利用同步方法完成
*/
MyRunable mr = new MyRunable();
Thread t1 = new Thread(mr);
Thread t2 = new Thread(mr);
Thread t3 = new Thread(mr);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
Lock锁
虽然我们可以理解为同步代码块和同步方法的锁对象问题,但是我们没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法
void lock():获得锁
void unlock():释放锁 手动上锁,手动释放锁
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化
ReentrantLock():创建一个ReentrantLock的实例
packagecom.cjj.threadsafe3;
importjava.util.concurrent.locks.Lock;
importjava.util.concurrent.locks.ReentrantLock;
publicclassMyThreadextendsThread{
staticintticket=0;
staticLocklock=newReentrantLock();
@Override
publicvoidrun() {
while (true) {
lock.lock();
try {
if(ticket==100) {
break;
} else {
Thread.sleep(100);
ticket++;
System.out.println(Thread.currentThread().getName() +"正在卖"+ticket+"张票");
}
} catch (InterruptedExceptione) {
thrownewRuntimeException(e);
}finally {
lock.unlock();
}
}
}
}
package com.cjj.threadsafe3;
public class ThreadDemo {
public static void main(String[] args) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
MyThread t3 = new MyThread();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
生产者和消费者(等待唤醒机制)
生产者和消费者模式是一个十分经典的多线程的模式
生产者:生产数据
消费者:消费数据
常见方法
方法名称 | 说明 |
void wait() | 当前线程等待,直到被其他线程唤醒 |
void notify() | 随机唤醒单个线程 |
void notifyAll() | 唤醒所有线程 |
packagecom.cjj.waitandnotify;
publicclassDesk {
/*
作用:控制生产者和消费者的执行
*/
publicstaticintfoodFlag=0;//判断是否有面条
//总个数
publicstaticintcount=10;
//锁对象
publicstaticObjectlock=newObject();
}
packagecom.cjj.waitandnotify;
publicclassFoodieextendsThread {
@Override
publicvoidrun() {
/*
1.循环
2.同步代码块
3.判断共享数据是否到了末尾(到了末尾)
4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count==0) {
break;
} else {
//先判断桌子上有没有面条
if (Desk.foodFlag==0) {
//如果没有就等待
try {
Desk.lock.wait();//让当前线程跟锁进行绑定
} catch (InterruptedExceptione) {
thrownewRuntimeException(e);
}
Desk.lock.notify();
} else {
//把吃的总数-1
Desk.count--;
//如果有,就开吃
System.out.println("吃货开始吃面条,还能再吃"+Desk.count+"碗");
//吃完之后,唤醒厨师继续做
Desk.lock.notifyAll();
//修改桌子的状态
Desk.foodFlag=0;
}
}
}
}
}
}
packagecom.cjj.waitandnotify;
publicclassCookextendsThread {
@Override
publicvoidrun() {
/*
1.循环
2.同步代码块
3.判断共享数据是否到了末尾(到了末尾)
4.判断共享数据是否到了末尾(没有到末尾,执行核心逻辑)
*/
while (true) {
synchronized (Desk.lock) {
if (Desk.count==0) {
break;
} else {
//判断桌子上有没有食物
if (Desk.foodFlag==1) {
//如果有就等等
try {
Desk.lock.wait();
} catch (InterruptedExceptione) {
e.printStackTrace();
}
} else {
//如果没有,就制作食物
System.out.println("厨师做了一碗面条");
//修改桌子上的食物状态
Desk.foodFlag=1;
//叫醒等待的消费者开吃
Desk.lock.notifyAll();
}
}
}
}
}
}
packagecom.cjj.waitandnotify;
publicclassThreadDemo {
publicstaticvoidmain(String[] args) {
/*
需求:完成生产者和消费者(等待唤醒机制)的代码
实现线程轮流交替执行的效果
*/
//创建线程对象
Cookc=newCook();
Foodief=newFoodie();
//给线程设置名字
c.setName("厨师");
f.setName("吃货");
c.start();
f.start();
}
}
阻塞队列方式实现
put数据时:放不进去,会等着,也叫做阻塞
take数据时:取出第一个数据时,取不到会等着,也叫做阻塞
接口:Iterable Collection Queue BlockingQueue
实现类:ArrayBlockingQueue:底层是数组,有界
LinkedBlockingQueue:底层是链表,无界但不是真正的无界,最大为int的最大值。
packagecom.cjj.waitandnotify02;
importjava.util.concurrent.ArrayBlockingQueue;
publicclassCookextendsThread{
ArrayBlockingQueue<String>queue;
publicCook(ArrayBlockingQueue<String>queue) {
this.queue=queue;
}
@Override
publicvoidrun() {
while(true){
//不断地把面条放到阻塞队列当中
try {
queue.put("面条");
System.out.println("厨师放了一碗面条");
} catch (InterruptedExceptione) {
e.printStackTrace();
}
}
}
}
packagecom.cjj.waitandnotify02;
importjava.util.concurrent.ArrayBlockingQueue;
publicclassFoodieextendsThread{
ArrayBlockingQueue<String>queue;
publicFoodie(ArrayBlockingQueue<String>queue) {
this.queue=queue;
}
@Override
publicvoidrun() {
while (true){
//不断从阻塞队列获取面条
try {
Stringfood=queue.take();
System.out.println(food);
} catch (InterruptedExceptione) {
e.printStackTrace();
}
}
}
}
packagecom.cjj.waitandnotify02;
importjava.util.concurrent.ArrayBlockingQueue;
publicclassThreadDemo01 {
publicstaticvoidmain(String[] args) {
/*
需求:利用阻塞队列完成生产者和消费者(等待唤醒机制)的代码
细节:
生产者和消费者必须使用同一个阻塞队列
*/
//1.创建阻塞队列的对象
ArrayBlockingQueue<String>queue=newArrayBlockingQueue<>(1);
//2.创建线程对象并把阻塞队列传递过去
Cookc=newCook(queue);
Foodief=newFoodie(queue);
//3.开启线程
c.start();
f.start();
}
}
线程的状态
线程状态。线程可以处于下列状态之一:
NEW 新建状态--->创建线程对象至今尚未启动的线程处于这种状态。
RUNNABLE 就绪状态--->start方法正在 Java 虚拟机中执行的线程处于这种状态。
BLOCKED 阻塞状态--->无法获得锁对象受阻塞并等待某个监视器锁的线程处于这种状态。
WAITING 等待状态--->wait方法无限期地等待另一个线程来执行某一特定操作的线程处于这种状态。
TIMED_WAITING 计时状态--->sleep方法等待另一个线程来执行取决于指定等待时间的操作的线程处于这种状态。
TERMINATED 结束状态--->全部代码运行完毕已退出的线程处于这种状态。
在给定时间点上,一个线程只能处于一种状态。这些状态是虚拟机状态,它们并没有反映所有操作系统线程状态。
线程池
以前写多线程的弊端:
弊端1:用到线程的时候就创建
弊端2:用完之后线程消失
线程池主要核心原理
1.创建一个池子,池子中是空的
2.提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复制已有的线程即可
3.但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待
线程池代码实现
Executors:线程池的工具类通过调用方法返回不同类型的线程池对象
方法名称 | 说明 |
public static ExecutorService newCachedThreadPool() | 创建一个没有上限的线程池 |
public static ExecutorService newFixedThreadPool(int nThreads) | 创建一个有上限的线程池 |
packagecom.cjj.mythreadpool;
importjava.util.concurrent.Executor;
importjava.util.concurrent.ExecutorService;
importjava.util.concurrent.Executors;
publicclassMyThreadPoolDemo {
publicstaticvoidmain(String[] args) {
/*
public static ExecutorService newCachedThreadPool() 创建一个没有上限的线程池
| public static ExecutorService newFixedThreadPool(int nThreads) 创建一个有上限的线程池
*/
//1.获取线程池对象
ExecutorServicepool1=Executors.newCachedThreadPool();
ExecutorServicepool2=Executors.newFixedThreadPool(3);
//2.提交任务
pool1.submit(newMyThread());
//3.销毁线程
pool1.shutdown();
}
}
自定义线程池(任务拒绝策略)
任务拒绝策略 | 说明 |
ThreadPoolExecutor.AbortPolicy | 默认策略:丢弃任务并抛出RejectedExecutionException异常 |
ThreadPoolExecutor.DiscardPolicy | 丢弃任务,但是不抛出异常这是不推荐的做法 |
ThreadPoolExecutor.DiscardOldestPolicy | 抛弃队列中等待最久的任务 然后把当前任务加入队列中 |
ThreadPoolExecutor.CallerRunsPolicy | 调用任务的run()方法绕过线程池直接执行 |
packagecom.cjj.mythreadpool02;
importjava.util.concurrent.*;
publicclassMyThreadPool {
publicstaticvoidmain(String[] args) {
/*
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor
(核心线程数量,最大线程数量,空闲线程最大存活空间,任务队列,创建线程工厂,任务的拒绝策略)
参数一:核心线程数量 不能小于0
参数二;最大线程数 不能小于等于0,最大数量>=核心线程数量
参数三:空闲线程最大存活时间 不能小于0
参数四:时间单位 用TimeUnit指定
参数五:任务队列 不能为null
参数六:创建线程工厂 不能为null
参数七:任务的拒绝策略 不能为null
*/
ThreadPoolExecutorpool=newThreadPoolExecutor(
3,//核心线程数量 不能小于0
6,//最大线程数 不能小于等于0,最大数量>=核心线程数量
60,//空闲线程最大存活时间 不能小于0
TimeUnit.SECONDS,//时间单位 用TimeUnit指定
newArrayBlockingQueue<>(3),//任务队列 不能为null
Executors.defaultThreadFactory(),//创建线程工厂 不能为null
newThreadPoolExecutor.AbortPolicy()//任务的拒绝策略 不能为null
);
}
}
小结:
① 创建一个空的池子
② 有任务提交时,线程池会创建线程区执行任务,执行完毕归还线程
不断地提交任务,会有以下三个临界点:
① 当核心线程满时,在提交任务就会排队
② 当核心线程满,队伍满时,会创建临时线程
③ 当核心线程满,队伍满,临时线程满时,会触发任务拒绝策略
线程池多大合适
CPU密集型运算 最大并行数 +1
I/O密集型运算 最大并行数乘期望CPU利用率乘(总时间(CPU计算时间+等待时间)/CPU计算时间)