多线程是什么
- 多线程是指从软硬件上实现多条执行流程的技术
- 多线程:
- 继承Thread类
- 实现Runnable接口
- 不要把主线程任务放在子线程之前,要不然就是单一线程
方式一:
- 继承Thread类; 重写run方法; 创建线程对象; 调用start方法启动
- 优缺点:优点编码简单 缺点:存在单继承的局限性,线程类继承Thread后不能继承其他类,不便于扩展。
启动线程调用start();调用run就是普通调用
//线程的开始public class ThreadTest {public static void main(String[] args) {//创建线程对象Thread ct = new CreateThread();//调用start 启动线程ct.start();for (int i = 0; i < 10; i++) {System.out.println("主线程"+i);}}}/** 多线程的创建方式之一:继承Thread类实现* */public class CreateThread extends Thread{/** 重写run方法,里面是定义线程以后要干啥* */@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println("子线程"+i);}}}
多线程的实现方案二:实现Runnable接口
- 定义一个线程任务类MyRunnable实现Runnable接口,实现run()方法
- 创建线程任务对象MyRunnable
- 把MyRunnable任务对象交给Thread处理
- 调用线程对象的start()方法启动线程
Runnable接口:
- 优缺点:
- 优:线程任务类只是实现接口,可以继承类和实现接口,扩展性强。
- 缺:编程多一层对象包装,如果线程执有执行结果是不可以直接返回的
- 第一种和第二种都是没有返回结果的 ```java package com.h.runnable; /*
- 第二种线程创建方式
- */
public class Test {
public static void main(String[] args) {
} } 实现的接口 package com.h.runnable;//创建一个任务对象Runnable ctr = new CreatThreadRunnable();//把任务对象交给线程对象new Thread(ctr).start();for (int i = 0; i < 5; i++) {System.out.println("主线程"+i);}
/实现Runnable接口/ public class CreatThreadRunnable implements Runnable{ /*
* 重写run方法** */@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println("子线程"+i);}}
}
实现Runnable(匿名内部类)<br />写法:```javapublic class InnerRunnable {public static void main(String[] args) {Runnable target = new Runnable(){public void run(){for (int i = 0; i < 5; i++) {System.out.println("匿名内部类线程"+i);}}};new Thread(target).start();for (int i = 0; i < 5; i++) {System.out.println("主线程"+i);}}}简化public class InnerRunnable {public static void main(String[] args) {new Thread(new Runnable(){public void run(){for (int i = 0; i < 5; i++) {System.out.println("匿名内部类线程"+i);}}}).start();for (int i = 0; i < 5; i++) {System.out.println("主线程"+i);}}}//使用Lambda表达式简化new Thread(()->{for (int i = 0; i < 5; i++) {System.out.println("匿名内部类线程"+i);}}).start();
方式三:
JDK5.0提供:Callable和FutureTask来实现
- 前两种方式重写run方法均不能直接返回结果
- 不适合需要返回线程执行结果的业务场景
实现方案三:
- 得到任务对象
- 定义类实现Callable接口,重写call方法,封装要做的事情。
- 用FutureTask把Callable对象封装成线程任务对象
- 把线程任务对象交给Thread处理
- 调用Thread的start方法启动线程,执行任务
线程执行完毕后、通过FutureTask的get方法去获取任务执行的结果
线程返回值需要怎么做:
创建任务对象MyCallable并实现Callable
接口 泛型自己需要什么类型返回申明什么类型 - 重写call方法 并返回你所需要的值
- 到主线程创建Callable任务对象 Callable
call = new MyCallable(100); - 把Callable任务对象交给FutureTask对象
- FutureTask对象实现了RunnableFuture接口
- RunnableFuture继承了Runnable接口
- 所以Thread类构造器中可以放FutureTask对象
- FutureTask可以使用get方法来获取Callable所返回的值
- 把FutureTask交给Thread
- Thread类调用start方法启动线程
- FutureTask对象调用get()方法获取call()方法返回的值
- get方法返回两个结果一个是正常结果,另一个是异常结果所以得使用try{}catch(){}
- get()方法有一个监测就是看call方法是否跑完没有如果没有跑完,代码执行到调用get()方法的时候就会等待,等待跑完后才会提取结果
FutureTask的API:
public FutureTask<>(Callable call);把Callable对象封装成FutureTask对象;
public V get() throws Exception; 获取线程执行call方法返回的结果。如果线程没有执行完,就会等待
优点:
- 线程任务类只是实现接口,可以继续继承类和实现接口,扩展性强
- 可以在线程执行完毕后去获取线程执行结果
- 缺点
- 编码复杂
代码:
package com.h.callable;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;/** 学会线程创建方式三、实现Callable接口,结合FutureTask完成* */public class CallableTest {public static void main(String[] args) {//创建Callable任务对象Callable<String> mc = new MyCallable(100);//把Callable任务对象,封装给FutureTask对象//为什么要交给FutureTask对象因为// 作用1、FutureTask是Runnable对象(实现了Runnable接口),可以交给Thread了// 作用2、可以在线程执行完毕之后通过调用其get方法得到线程执行完完成的结果FutureTask<String> ft = new FutureTask<>(mc);Thread th = new Thread(ft);th.start();try{String str = ft.get();System.out.println("第一个结果"+str);}}}/** 1、定义一个任务类,实现Callable类 应该申明线程任务执行完毕后的结果的数据类型* */class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}/*重写call方法 线程任务方法*/@Overridepublic String call() throws Exception {int sum = 0;for (int i = 0; i <= n; i++) {sum+=i;}return "子线程的结果是" +sum;}}
Thread常见方法
给线程设置名字
- 使用Thread.setName();
- 使用构造器来给线程设置名字
获取线程名字
- 使用Thread.getName();
获取当前线程的方法:
谁调用的,谁就是当前线程
- Thread.currentThread();
使用Runnable的时候进行设置名称
- new Thread(Runnable runnable,String name);
线程休眠:
- Thread.sleep(long time);当前线程休眠指定的时间后再继续执行,单位为毫秒 ```java
public class MyThreadTest { public static void main(String[] args) { Thread t1 =new MyThread(“构造器1号”); //给线程取名字 t1.setName(“1号”); t1.start();
//拿到当前线程对象Thread m = Thread.currentThread();m.setName("主线程");for (int i = 0; i < 5; i++) {System.out.println(m.getName()+""+i);}}
}
public class MyThread extends Thread{ //使用构造器给线程设置名字 public MyThread(String name) { super(name); }
@Overridepublic void run() {for (int i = 0; i < 5; i++) {//获取线程名字System.out.println(Thread.currentThread().getName()+"子线程");}}
}
<a name="UoKwb"></a>#### 线程安全问题:- **多个线程同时操作同一个共享资源**的时候可能会出现业务安全问题,称为线程安全问题- 同步代码块代码:```javapackage com.h.h_account;public class Account {private String cardId;private double money;public Account() {}public Account(double money) {this.money = money;}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}public void drawMoney(double money) {//获取当前线程是谁在调用String name = Thread.currentThread().getName();//判断账户余额是否足够if(this.money>= money){//取钱System.out.println(name + "取钱:"+money);this.money -= money;System.out.println("当前剩余余额:"+this.money);}else {System.out.println("余额不足");}}}package com.h.h_account;/** 取钱的线程* */public class DrawThread extends Thread{private Account acc;public DrawThread(Account acc,String name) {super(name);this.acc = acc;}@Overridepublic void run() {//同时取钱acc.drawMoney(100);}}package com.h.h_account;public class AccountTest {public static void main(String[] args) {/*小明与小红共同使用一个账户*/Account ac = new Account(100);Thread t1 = new DrawThread(ac ,"小明");Thread t2 = new DrawThread(ac,"小红");t1.start();t2.start();}}同步锁解决问题//在这加锁的 然后当两个线程到这的时候只能有一个线程能抢到,抢到以后另一个线程就等待synchronized ("hh") {if (this.money >= money) {//取钱System.out.println(name + "取钱:" + money);this.money -= money;System.out.println("当前剩余余额:" + this.money);} else {System.out.println("余额不足");}}
解决上面线程安全问题:线程同步
线程同步核心思想
- 加锁,把共享资源进行上锁,每次只能一个线程进入访问完毕以后解锁,然后其他线程才能进来
- 作用:把出现线程安全问题的核心代码给上锁
- 原理:每次只能一个线程进入,执行完毕后自动解锁,其他线程才可以进来执行
synchronized(同步锁对象){
操作共享资源的代码(核心代码)
}
锁对象要求
- 理论上:锁对象只要对于当前同时执行的线程来说是同一个对象即可
锁对象用任意唯一的对象不好
- 会影响其他无关线程的执行
同步锁解决问题//在这加锁的 然后当两个线程到这的时候只能有一个线程能抢到,抢到以后另一个线程就等待synchronized (this) {if (this.money >= money) {//取钱System.out.println(name + "取钱:" + money);this.money -= money;System.out.println("当前剩余余额:" + this.money);} else {System.out.println("余额不足");}}
锁对象的规范要求
- 规范上:建议使用共享资源作为锁对象。
- 对于实例方法建议使用this作为锁对象
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象。
静态方法
class Acc{//100个线程来也只有一个人能拿到public static void run(){synchronized(Acc.class)}}
synchronized (“hh”)这样写是唯一性所有线程都必须从他这拿锁
synchronized (this)这样写是每个对象拿自己的那把锁,其他线程不受干扰
为什么写this呢
因为this是那个类所调用的那个方法传过来的对象
问题:
- 同步代码块是如何实现线程安全的?
- 对出现问题的核心代码使用synchronzed()进行加锁
- 每次只能一个线程占锁进入访问
- 同步代码块的同步锁对象有什么要求?
- 对于实例化方法建议使用this作为锁对象
- 对于静态方法建议使用字节码(类名.class)对象作为锁对象
END
同步方法:
同步锁可以作为修饰符加上
同步方法的底层原理
同步方法与同步代码块原理一样 只是不需要你自己添加this或类名.class
class Acc{//同步方法public synchronized void run(){//核心代码块}}
性能方面:同步代码块可以选择你所想锁的代码 同步方法是将整个方法锁起来,性能方面差别微乎其微。
同步代码块 与 同步方法原理一样
实际开发中同步方法使用较多因为代码简单LOCK锁:
创建方式:
把锁写在类里面,每次new类的时候都会创建出一个锁Lock lock = new ReentrantLock();
- Lock是接口ReentrantLock是Lock的实现类
- lock.lock();//上锁
- lock.unlock();//解锁
看代码:
package com.h.h_lock;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;public class Account {private String cardId;private double money;//自创锁 唯一不可替换锁 非常专业 每次创建对象都有一个锁private final Lock lock = new ReentrantLock();public Account() {}public Account(double money) {this.money = money;}public Account(String cardId, double money) {this.cardId = cardId;this.money = money;}public String getCardId() {return cardId;}public void setCardId(String cardId) {this.cardId = cardId;}public double getMoney() {return money;}public void setMoney(double money) {this.money = money;}//在核心代码块上调用lock方法就上锁public void drawMoney(double money) {//获取当前线程是谁在调用String name = Thread.currentThread().getName();//为了代码的强健性加上finallylock.lock(); //上锁try {if (this.money >= money) {//取钱System.out.println(name + "取钱:" + money);this.money -= money;System.out.println("当前剩余余额:" + this.money);} else {System.out.println("余额不足");}}finally {//加锁核心代码出现异常,而不会影响解锁lock.unlock();//解锁}}}
线程池(重点)
谁代表线程池
- ExecutorService接口
线程池概述:
- 线程池就是一个可以复用线程的技术,反复使用;
不使用线程池的问题
- 如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务又来了又要创建新线程,而这样创建开销是很大的,这样会严重影响系统性能,所以我们需要用线程池。
如何得到线程池
- 方式一:使用ExecutorService的实现类ThreadPoolExecutor创建一个线程池
- 方式二:使用Executors(线程池的工具类)调用方法返回不同的特点的线程池对象
线程池常见面试题:
临时线程什么时候创建啊?
- 新任务提交时发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,此时才会创建临时线程。
- 什么是还可以创建临时线程
- 当线程池给的最大线程数还小于等于当前线程的时候还可以创建
什么时候会开始拒绝任务
- 核心线程与临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始任务拒绝。
Runnable添加进线程池:
package com.h.h_threadpool;import java.util.concurrent.*;/** 自定义线程池对象,并测试特性* */public class ThreadPoolDemo1 {//1、创建线程池对象/*int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler*/public static void main(String[] args) {ExecutorService es = new ThreadPoolExecutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//2、给任务线程池处理Runnable runnable = new MyRunnable();//把线程丢入线程池 核心线程3个es.execute(runnable);es.execute(runnable);es.execute(runnable);//新任务排队5个es.execute(runnable);es.execute(runnable);es.execute(runnable);es.execute(runnable);es.execute(runnable);//临时线程es.execute(runnable);es.execute(runnable);//不创建 拒绝策略被触发// es.execute(runnable);//关闭线程池 开发中一般不会使用// es.shutdownNow(); //立即关闭 即使任务没有完成// es.shutdown(); //等待任务完成以后才能关闭}}Runnable线程package com.h.h_threadpool;public class MyRunnable implements Runnable {public void run() {for (long i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName()+"输出了Hello World"+i);}try {Thread.sleep(100000);} catch (InterruptedException e) {e.printStackTrace();}}}
Callable添加进线程池
package com.h.h_threadpool;import java.util.concurrent.*;/** 自定义线程池对象,并测试特性* */public class ThreadPoolDemo2 {//1、创建线程池对象/*int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler*/public static void main(String[] args) throws Exception {ExecutorService es = new ThreadPoolExecutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//直接把MyCallable对象扔进线程池里面 可以通过返回值 拿到对象Future<String> f1 = es.submit(new MyCallable(100));Future<String> f2 = es.submit(new MyCallable(200));Future<String> f3 = es.submit(new MyCallable(300));Future<String> f4 = es.submit(new MyCallable(400));System.out.println(f1.get());System.out.println(f2.get());System.out.println(f3.get());System.out.println(f4.get());}}Callable:class MyCallable implements Callable<String> {private int n;public MyCallable(int n) {this.n = n;}/*重写call方法 线程任务方法*/@Overridepublic String call() throws Exception {int sum = 0;for (int i = 0; i <= n; i++) {sum+=i;}return "子线程的结果是" +sum;}}
可以直接把Callable放入线程池中
Executors工具类:
Executors的底层其实也是基于线程池的实现类ThreadPoolExecutors创建线程池对象
- Executors工具类底层是基于什么方式实现的线程池对象?
- 线程池ExecutorService的实现类:ThreadPoolExecutor
- Executors是否合适大型互联网场景的线程池方案?
- 不适合
- 建议使用ThreadPoolExecutor来指定线程池参数,这样可以明确的运行规则,规避资源耗尽的风险。
定时器:
- 定时器是一种控制任务延时调用,或者周期调用技术
定时器的实现方式
- 方式一、Timer
- 构造器:public Timer();//创建定时器对象
- 方式二、ScheduuledExecutorService
Timer定时器的特点和存在的问题
- Timer是单线程,处理多个任务按照顺序执行,存在延时与设置定时器的时间有出入
- 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续任务执行
是单线程,出现异常程序就会死掉
单线程定时器: ```java package com.h.h_timer;
import java.util.Timer; import java.util.TimerTask;
/使用定时器/
public class TimerDemo { public static void main(String[] args) { //创建定时器 Timer t = new Timer(); //调用方法处理定时任务 //每隔3秒触发一次,每隔两秒执行一次 //每隔3秒触发一次是这个任务的触发,每隔两秒是再次执行这个任务 t.schedule(new TimerTask() { @Override public void run() { //定时器本身就是一个线程 System.out.println(Thread.currentThread().getName()+”执行一次”);
}},3000,2000);}
}
**ScheduledExecutorService**- 是为了弥补Timer的缺陷- Executors的方法:public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize);//得到线程池对象- ScheduleExecutorService的方法:public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initialDelay,long period,TimerUnit unit)**ScheduledExecutorService的优点 **1. 基于线程池,某个任务的执行情况不会影响其他定时任务的执行其他线程挂掉的时候别的线程不会挂掉<br />互不干扰线程池定时器:```javapackage com.h.h_timer;import java.util.Timer;import java.util.TimerTask;import java.util.concurrent.Executors;import java.util.concurrent.ScheduledExecutorService;import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.TimeUnit;/*使用定时器*/public class TimerDemo2 {public static void main(String[] args) {//1、创建ScheduledExecutorService线程池,做定时器//以后使用这种定时器进行开发ScheduledExecutorService ses = Executors.newScheduledThreadPool(1);//2、开启定时任务//参数列表 1:定时器 2:触发时间 3:周期时间 4:单位 秒?毫秒?分?时?ses.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {System.out.println("定时任务1");}}, 0, 2, TimeUnit.SECONDS);}}
并发与并行
- 正在运行的程序(软件)就是一个,线程是属于进程的,多个线程其实是并发与并行同时进行
并发理解
- CPU同时处理线程的数量有限
- CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程是同时执行,这就是并发。
- 并发:同一时间段,抢占CPU执行自己
- 并行:同一时刻同时执行

新的线程知识
如何让两个线程依次输出结果
例如:线程1 ABCDE 线程2: 12345
使用最新技术:
park是叫醒 unpark阻塞
LockSupport.park();与LockSupport.unpark();
notify()是按特定算法去叫醒某一个线程没法精确叫醒某一个线程
使用notifyAu()
char aI = {1,2,3,4,5,6};char aI=C = {"a","b","c","d","e"};TransferQueue<Character> queue = new LinkedTransferQueue<~>();TransferQueue 相当于一个套筒 一个人把手伸进去 另一个也把手伸进去 一个人交出东西 另一个接收东西new Thread(() -> {try{for(char c : aI){System.out.print(queue.take());//传一个东西进行queue.transfer(c);}}catch(InterruptedException e){e.printStackTrace();}},"t1").start;new Thread(() ->{try{for(char c : aC){//拿一个东西出来queue.transfer(c);System.out.print(queue.take());}}catch(InterruptedException e){e.printStackTrace();}},"t2").start;})

