最详细的java多线程教程来了
2020-06-08 16:08:29来源:博客园 阅读 ()
最详细的java多线程教程来了
1.1 线程和进程
-
进程是处于运行过程中的程序,并且具有一定的独立功能
-
并发性:同一个时刻只能有一条指令执行,但多个进程指令被快速轮换执行
-
并行:多条指令在多个处理器上同时执行
-
线程是进程的执行单元
1.2 多线程的优势
-
进程之间不能共享内存,但线程之间非常容易
-
系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程效率更高
-
Java语言内置了多线程功能
2. 线程创建与启动
2.1 继承Thread
public class FirstThread extends Thread { private int i; ? @Override public void run() { for(i = 0; i < 50; i ++){ System.out.println(this.getName() + "" + i); } } ? public static void main(String[] args){ FirstThread ft = new FirstThread(); for(int i =0; i < 100;i ++){ System.out.println(Thread.currentThread().getName() + "" + i); if(i == 20) { ft.run(); } } } }
2.2 实现Runnable接口
public class FirstThread implements java.lang.Runnable { private int i; ? public void run() { for(i = 0; i < 50; i ++){ System.out.println(Thread.currentThread().getName()+ "" + i); } } ? public static void main(String[] args){ FirstThread ft = new FirstThread(); for(int i =0; i < 100;i ++){ System.out.println(Thread.currentThread().getName() + "" + i); if(i == 20) { ft.run(); } } } }
2.3 使用Callable和Future
-
Callable
接口提供了一个call()
方法可以作为线程执行体,call()
方法有返回值且可以声明抛出异常 -
Java5提供了
Future
接口来代表Callable
接口里call()
方法的返回值,并为Future
接口提供了一个FutureTask
实现类 -
Future
接口定义的方法:
方法名 | 作用 |
---|---|
boolean cancel(boolean mayInterruptIfRunning) |
试图取消该Future 里关联的Callable 任务 |
V get() |
返回Callable 任务里call 方法的返回值,该方法会造成线程阻塞,等子线程执行完才能获得 |
V get(long timeout, TimeUnit unit) |
返回Callable 任务里call 方法的返回值。该方法让程序最多阻塞timeout 和unit 指定的时间,如果经过指定时间Callable 任务还没有返回值则抛出TimeoutException 异常 |
boolean isCancelled() |
Callable 中的任务是否取消 |
boolean isDone() |
Callable 中的任务是否完成 |
public class CallableDemo { public static void main(String[] args){ FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)() -> { int i = 0; for( ; i < 100; i++){ System.out.println(i); } return i; }); new Thread(task).start(); try { System.out.println(task.get()); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
2.4 创建线程的三种方式对比
Runnable
和Callable
优劣势:
-
线程类只是实现了
Runnable
、Callable
接口,还可以继承其他类 -
Runnable和Callable情况下,多个线程可以共享同一个
target
对象,所以非常适合多个相同线程来处理同一份资源的情况 -
编程稍稍复杂,如果需要访问当前线程,则必须使用
Thread.currentThread()
Thread
优劣势:
-
线程类已经继承了
Thread
类,所以不能再继承其他父类 -
编写简单,如果需要访问当前线程,用
this
使用
3. 线程生命周期
3.1 新建和就绪状态
-
new
语句仅仅由Java虚拟机为其分配内存,并没有表现出任何线程的动态特征 -
如果直接调用继承类的
run
方法,则只会有MainActivity
,而且不能通过getName
获得当前执行线程的名字,而需用Thread.currentThread().getName()
-
调用了
run
方法后,该线程已经不再处于新建状态
3.2 运行和阻塞状态
-
当线程数大于处理器数时,存在多个线程在同一个CPU上轮换的现象
-
协作式调度策略:只有当一个线程调用了
sleep()
或yield()
方法才会放弃所占用的资源——即必须线程主动放弃所占用的资源 -
抢占式调度策略:系统给每个可执行的线程分配一个小的时间段来处理任务,当任务完成后,系统会剥夺该线程所占用的资源
-
被阻塞的线程会在合适的时候重新进入就绪状态
线程状态转换图
3.3 死亡状态
-
测试线程死亡可用
isAlive()
-
处于死亡的线程无法再次运行,否则引发
IllegalThreadStateException
异常
“大清亡于闭关锁国,学习技术需要交流和资料”。 在这里我给大家准备了很多的学习资料免费获取,包括但不限于java进阶学习资料、技术干货、大厂面试题系列、技术动向、职业生涯等一切有关程序员的分享.java进阶方法笔记,学习资料,面试题,电子书籍免费领取,让你成为java大神,追到自己的女神,走向人生巅峰
4. 控制线程
4.1 join线程
-
在
MainActivity
调用了A.join()
,则MainActivity
被阻塞,A线程执行完后MainActivity
才执行
4.2 后台线程(Daemon Thread)
-
如果所有的前台线程都死亡,后台线程会自动死亡
运行结果
4.3 线程睡眠sleep
try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); }
-
sleep
方法暂停当前线程后,会给其他线程执行机会,不会理会其他线程优先级;但yield
方法只会给优先级相同或更高的线程 -
sleep
方法将转入阻塞状态,直到经过阻塞时间才会转入就绪;yield
强制当前线程转入就绪状态 -
sleep
方法抛出了InterruptedException
,yield方法没抛出异常
4.4 改变线程优先级
-
优先级高的线程获得较多的执行机会,优先级低的线程获得较少的执行机会
-
setPriority
和getPriority
方法来设置和返回指定线程的优先级
5. 线程同步
-
run()
方法不具有同步安全性 -
*java*引入了同步监视器来解决多线程同步问题,
sychronized(obj)
中obj
就是共享资源
5.1 同步方法
-
同步方法就是使用
synchronized
来修饰某个方法 -
实例方法的同步监视器默认是
this
-
*Java*中不可变类总是线程安全的,可变类对象需要额外的方法来保证其线程安全
public class DaemonThread extends Thread { ? static int balance = 100; int drawAmount; String name; ? public DaemonThread(int drawAmount, String name){ this.drawAmount = drawAmount; this.name = name; } ? @Override public void run() { this.draw(drawAmount); } ? public synchronized void draw(int amount){ if(balance >= amount){ System.out.println(this.name + "取出了" + amount); try{ Thread.sleep(1); } catch (InterruptedException e){ e.printStackTrace(); } balance -= amount; System.out.println("\t余额为" + balance); ? } else{ System.out.println(this.name + "取现失败"); } } public static void main(String[] args){ new DaemonThread(50, "A").start(); new DaemonThread(100, "B").start(); } }
5.2 释放同步监视器的锁定
下列情况下,线程会释放对同步监视器的锁定
-
当前线程的同步方法、同步代码块执行结束
-
遇到了break、return
-
-
程序执行了同步监视器对象的
wait()
方法
下列情况下不会释放:
-
执行同步方法时,程序调用
Thread.sleep()
Thread.yield()
方法 -
其他线程调用了该线程的
suspend
方法
5.3 同步锁
-
*Java5*开始,提供了一种功能更强大的同步锁机制,可以通过显式定义同步锁对象来实现同步
-
Lock提供了比synchronized更广泛的锁定操作,并且支持多个相关的Condition对象
-
Lock类型: Lock ReadWriteLock ReentrantLock:常用,可以对一个加锁的对象重新加锁 ReentrantReadWriteLock StampedLock
方法名 | 作用 |
---|---|
lock |
加锁 |
unlock |
解锁 |
5.4 死锁
A等B,B等A
5.5 线程通信
5.5.1 传统的线程通信
方法名 | 作用 |
---|---|
wait |
导致当前线程等待,直到其他线程调用该同步监视器的notify() 或notifyAll() 方法 |
notify |
唤醒在此同步监视器等待的单个线程 |
notifyAll |
唤醒在此同步监视器等待的所有线程 |
-
wait()
必须在加锁的情况下执行
5.5.2 使用Condition
-
如果系统中不适用synchronized来保证线程同步,而使用Lock对象来保证同步,那么无法使用
wait
,notify
,notifyAll()
来进行线程通信 -
当使用
Lock
对象,*Java*提供Condition
保证线程协调 -
Condition
方法如下
方法名 | 作用 |
---|---|
await |
导致当前线程等待,直到其他线程调用该同步监视器的signal() 或signalAll() 方法 |
signal |
唤醒在此Lock对象的单个线程 |
signalAll |
唤醒在此Lock对象的所有线程 |
5.5.3 使用阻塞队列
-
*Java*提供了一个BlockingQueue接口
-
当生产者线程试图向
BlockingQueue
放入元素时,如果该队列已满,则该线程被阻塞;当消费者线程试图从BlockingQueue
取出元素时,如果该队列已空,则该线程被阻塞
方法名 | 作用 |
---|---|
put(E e) |
尝试把E元素放入BlockingQueue |
take() |
尝试从BlockingQueue 的头部取出元素 |
public class BlockingQueueThread extends Thread { ? private BlockingQueue<String> bq; ? public BlockingQueueThread(BlockingQueue<String> bq){ this.bq = bq; } ? @Override public void run() { String[] strColl = new String[]{ "Java", "Kotlin", "JavaScript" }; ? ? ? for(int i = 0; i < 1000; i ++){ try { System.out.println(getName() + "开始动工" + i); bq.put(strColl[i % 3]); } catch (InterruptedException e) { e.printStackTrace(); } } ? System.out.println(getName() + "工作结束"); } ? public static void main(String[] args){ BlockingQueue<String> bq = new ArrayBlockingQueue<>(5); new BlockingQueueThread(bq).start(); } }
结果展示
可以看到,当Thread-0运行到第6次时就已经被阻塞,不能往里添加内容
“大清亡于闭关锁国,学习技术需要交流和资料”。 在这里我给大家准备了很多的学习资料免费获取,包括但不限于java进阶学习资料、技术干货、大厂面试题系列、技术动向、职业生涯等一切有关程序员的分享.java进阶方法笔记,学习资料,面试题,电子书籍免费领取,让你成为java大神,追到自己的女神,走向人生巅峰
6. 线程组和未处理的异常
-
ThreadGroup
表示线程组,可以对一批线程进行分类管理 -
子线程和创建它的父线程在同一个线程组内
-
ThreadGroup
方法
方法名 | 作用 |
---|---|
int activeCount |
返回线程组中活动线程的数目 |
interrupt |
中断此线程组中所有活动线程的数目 |
isDaemon |
线程组是否是后台线程组 |
setDaemon |
设置后台线程 |
setMaxPriority |
设置线程组的最高优先级 |
7. 线程池
-
线程池在系统启动时即创建大量空闲的线程
-
程序将一个
Runnable
对象或Callable
对象传给线程池,线程池就会启动一个空闲线程来执行他们 -
线程结束不死亡,而是回到空闲状态
-
*Java8*之后新增了一个
Executors
工厂类来生产线程池
7.1 ThreadPool
public class ThreadPoolTest { ? public static void main(String[] args){ ExecutorService pool = Executors.newFixedThreadPool(2); ? java.lang.Runnable target = () -> { for (int i = 0; i < 100 ; i ++){ System.out.println(Thread.currentThread().getName() + "的i为" +i); } }; ? pool.submit(target); pool.submit(target); pool.shutdown(); } }
结果展示
7.2 ForkJoinPool
-
将一个任务拆分成多个小任务并行计算,再把多个小任务的结果合并成总的计算结果
-
ForkJoinPool
是ExecutorService
的实现类
public class PrintTask extends RecursiveAction { ? public static int THREADSH_HOLD = 50; ? private int start; ? private int end; ? public PrintTask(int start, int end){ this.start = start; this.end = end; } ? @Override protected void compute() { if(end - start < THREADSH_HOLD){ for(int i = start; i < end; i ++){ System.out.println(Thread.currentThread().getName() + "的i为" + i); } } else { PrintTask left = new PrintTask(start, (start + end) / 2); PrintTask right = new PrintTask((start + end) / 2 , end); left.fork(); right.fork(); } } ? public static void main(String[] args) throws InterruptedException { PrintTask printTask = new PrintTask(0 , 300); ForkJoinPool pool = new ForkJoinPool(); pool.submit(printTask); pool.awaitTermination(2, TimeUnit.SECONDS); pool.shutdown(); ? } }
原文链接:https://www.cnblogs.com/coderjava/p/13068889.html
如有疑问请与原作者联系
标签:
版权申明:本站文章部分自网络,如有侵权,请联系:west999com@outlook.com
特别注意:本站所有转载文章言论不代表本站观点,本站所提供的摄影照片,插画,设计作品,如需使用,请与原作者联系,版权归原作者所有
- 国外程序员整理的Java资源大全(全部是干货) 2020-06-12
- 2020年深圳中国平安各部门Java中级面试真题合集(附答案) 2020-06-11
- 2020年java就业前景 2020-06-11
- 04.Java基础语法 2020-06-11
- Java--反射(框架设计的灵魂)案例 2020-06-11
IDC资讯: 主机资讯 注册资讯 托管资讯 vps资讯 网站建设
网站运营: 建站经验 策划盈利 搜索优化 网站推广 免费资源
网络编程: Asp.Net编程 Asp编程 Php编程 Xml编程 Access Mssql Mysql 其它
服务器技术: Web服务器 Ftp服务器 Mail服务器 Dns服务器 安全防护
软件技巧: 其它软件 Word Excel Powerpoint Ghost Vista QQ空间 QQ FlashGet 迅雷
网页制作: FrontPages Dreamweaver Javascript css photoshop fireworks Flash