多线程
线程与进程
进程
是指一个内存中运行的应用程序,每个进程都有一个独立的内存空间
线程
是进程中的一个执行路径,共享一个内存空间,线程之间可以自由切换,并发执行. 一个进程最少
有一个线程
线程实际上是在进程基础之上的进一步划分,一个进程启动之后,里面的若干执行路径又可以划分
成若干个线程
同步与异步
同步:排队执行 , 效率低但是安全.
异步:同时执行 , 效率高但是数据不安全.
并发与并行
并发:指两个或多个事件在同一个时间段内发生。
并行:指两个或多个事件在同一时刻发生(同时发生)。
Thread
public class MyThread extends Thread {
//run方法就是线程要执行的任务方法
@Override
public void run() {
//这里的代码就是一条新的执行路径
//这个执行路径是触发方式,不是调用run方法,而是通过thread对象的start方法来启动任务
for (int i = 0; i < 10; i++) {
System.out.println("锄禾日当午"+i);
}
}
}
Runnable
实现Runnable 与 继承Thread相比有如下优势:
1.通过创建任务,然后给线程分配的方式来实现的多线程,更适合多个线程同时执行相同任务的情况。
2.可以避免单继承所带来的局限性。
3.任务与线程 本身是分离的,提高了程序的健壮性。
4.线程池技术,接收Runnable类型的任务,不接收Thread类型的线程。
//实现Runnable`
//1. 创建一个任务对象
MyRunnable r = new MyRunnable();
//2. 创建一个线程,并为其分配一个任务
Thread t = new Thread(r);
//3. 执行这个线程
t.start();`
for (int i = 0; i < 10; i++) {
System.out.println("锄禾日当午"+i);
}
Thread类
常用方法
getname() 获取线程的名称
getId() 获取线程的标识符
setPriority(int newPriority)` 更改线程的优先级
currentThread()` 表示当前正在执行的线程
start() 线程启动
interrupt()` 线程中断
sleep(long millis)`/sleep(long millis(毫秒), int nanos(纳秒)) 暂时停止线程执行(指定时间)
setDaemon(boolean on)` 标记线程是否为守护线程或用户线程
守护线程与用户线程
Daemon线程(守护线程)
↓(依附于 即人在塔在的关系)
用户线程(全部死亡,程序结束)
//用户线程:当一个进程不包含任何的存活的用户线程时,进行结束
//守护线程:守护用户线程的,当最后一个用户线程结束时,所有守护线程自动死亡。
设置和获取线程的名称
public static void main(String[] args) {
//如何获取线程的名称
System.out.println(Thread.currentThread().getName());
//两种设置线程名称的方式
Thread t = new Thread(new MyRunnable());
t.setName("wwww");
t.start();
new Thread(new MyRunnable(),"锄禾日当午").start();
//不设置的有默认的名字
new Thread(new MyRunnable()).start();
}
static class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
线程的休眠
Thread.sleep(1000); //1000毫秒
线程不安全
解决方案1、同步代码块(synchronized)
格式 synchronized(){}
↑(传参:锁对象)
解决方案2、同步方法(synchronized关键词修饰方法)
举例:public synchronized boolean sale(){}
解决方案3、显示锁,Lock ,子类 ReentrantLock
自己创建一把锁,自己上锁,自己解锁
static class Ticket implements Runnable{
//总票数
private int count = 10;
//参数为true表示公平锁 默认是false 不是公平锁
private Lock l = new ReentrantLock(true);
@Override
public void run() {
while (true) {
l.lock();//当线程开始执行时,进行上锁
if (count > 0) {
//卖票
System.out.println("正在准备卖票");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println(Thread.currentThread().getName()+"卖票结束,余票:" + count);
}else {
break;
}
l.unlock();//线程结束,解锁
}
}
}
公平锁:先到先得,排队流程。
不公平锁:先抢先得,手快有,手慢无。
线程死锁
个人理解:
前提:锁1调用锁2。
发生死锁:锁1线程执行一半,需要调用锁2线程方法,于是申请调用锁2,但是与此同时锁2线程也执行到一半,需要调用锁1线程方法。于是就发生了死锁。即1在等待2结束然后调用2的方法结束自己,同时2在等待1的结束然后调用1的方法结束自己。
前提:小明借了小黑一把指甲刀
小黑:小明借我一下“你的指甲刀”
小明:我的指甲刀在你那啊。
小黑:那你给我你的指甲刀啊。
小明:你不还我指甲刀,我怎么借你指甲刀啊。
小黑:你不借我指甲刀,我怎么还你啊。
八十年后,小明小黑卒。
如何避免:在任何一个有可能产生锁的方法里,不要调用另一个有可能产生锁的方法。
Callable
Runnable 与 Callable的相同点
都是接口
都可以编写多线程程序
都采用Thread.start()启动线程
Runnable 与 Callable的不同点
Runnable没有返回值;Callable可以返回执行结果
Callable接口的call()允许抛出异常;Runnable的run()不能抛出
接口定义
//Callable接口
public interface Callable<V> {
V call() throws Exception;
}
//Runnable接口
public interface Runnable {
public abstract void run();
}
Callable获取返回值
Callalble接口支持返回执行结果,需要调用FutureTask.get()得到,此方法会阻塞主进程的继续往下执
行,如果不调用不会阻塞。
Callable使用步骤
1. 编写类实现Callable接口 , 实现call方法
class XXX implements Callable<T> {
@Override
public <T> call() throws Exception {
return T;
}
}
2. 创建FutureTask对象 , 并传入第一步编写的Callable类对象
FutureTask<Integer> future = new FutureTask<>(callable);
3. 通过Thread,启动线程
new Thread(future).start();
常用方法
get() 获取线程执行的结果。
isDone() 判断线程是否执行完毕。
线程池 Executors
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程
就会大大降低 系统的效率,因为频繁创建线程和销毁线程需要时间. 线程池就是一个容纳多个线程的容
器,池中的线程可以反复使用,省去了频繁创建线程对象的操作,节省了大量的时间和资源。
线程池的好处
降低资源消耗。
提高响应速度。
提高线程的可管理性。
Java中的四种线程池 . ExecutorService
1.缓存线程池
//缓存线程池
//无限制长度
//任务加入后的执行流程
//1判断线程池是否存在空闲线程
2存在则使用
3不存在则创建线程并使用
//向线程池中加入新的任务
2.定长线程池
长度是指定的线程池
加入任务后的执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
3.单线程线程池
单线程线程池
执行流程
1 判断线程池的那个线程是否空闲
2 空闲则使用
3 不空闲则等待它空闲后再使用
4.周期任务 定长线程池
执行流程
1 判断线程池是否存在空闲线程
2 存在则使用
3 不存在空闲线程 且线程池未满的情况下 则创建线程 并放入线程池中 然后使用
4 不存在空闲线程 且线程池已满的情况下 则等待线程池的空闲线程
周期性任务执行时
定时执行 当某个任务触发时 自动执行某任务
public static void main(String[] args) {
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(2);
//定时执行一次
//参数1:定时执行的任务
//参数2:时长数字
//参数3:2的时间单位 Timeunit的常量指定
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5, TimeUnit.SECONDS); //5秒钟后执行*/
周期性执行任务
参数1:任务
参数2:延迟时长数字(第一次在执行上面时间以后)
参数3:周期时长数字(没隔多久执行一次)
参数4:时长数字的单位
* **/
scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"锄禾日当午");
}
},5,1,TimeUnit.SECONDS);
}
}
以上就是我经过学习和请教后梳理的多线程知识。如有重复,纯属水平有限[捂脸]。
本人只是java学习中的小学生,有更好的想法请说明并解释,不然我怕我看不懂[捂脸]。
本文深入探讨Java多线程的概念,包括线程与进程的区别,同步与异步、并发与并行的特性,以及线程的创建和管理。详细解析Thread类和Runnable接口的使用,线程安全问题及解决方案,线程池的原理与应用,Callable接口的异同,以及线程死锁的避免策略。
1187

被折叠的 条评论
为什么被折叠?



