【笔记】Java多线程
遇见狂神说的Java多线程教学笔记
线程的创建
三种创建方式
-
Thread
class- 自定义线程类继承
Thread
class - 重写run()方法,编写线程执行体
- 创建线程对象,调用start()方法启动线程 注意:线程开启不一定立即执行,由 CPU 调度执行
不建议使用:OOP单继承局限性
public class TestThread extends Thread{ @Override public void run() { for (int i = 0; i < 200; i++) { System.out.println("线程运行"+i); } } public static void main(String[] args) { TestThread thread = new TestThread(); thread.start(); for (int i = 0; i < 1000; i++) { System.out.println("主线程运行"+i); } } }
- 自定义线程类继承
-
Runnable
接口- 自定义
MyRunnable
类实现Runnable
接口
- 自定义
-
实现
run()
方法,编写线程执行体- 通过线程对象代理
new Thread(myRunnable).start()
推荐使用:避免单继承局限性、方便同一个对象被多个线程使用
public class TestThread4 implements Runnable{ private int ticketNums = 10; @Override public void run() { while (true) { if(ticketNums < 0) { break; } try { Thread.sleep(200); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+" got "+ticketNums--+"ticket"); } } public static void main(String[] args) { TestThread4 ticket = new TestThread4(); new Thread(ticket,"ming").start(); new Thread(ticket,"lao").start(); new Thread(ticket,"huang").start(); //涉及数据紊乱问题,通过加锁解决 } }
- 通过线程对象代理
-
Callable
接口- 实现
Callable
接口,需要返回值类型 - 重写
call
方法,需要抛出异常 - 创建目标对象
- 创建执行服务:
ExecutorService ser = Executors.newFixedThreadPool(1);//线程池里的线程数为1
- 提交执行:
Future<Boolean> result1 = ser.submit(t1);//t1是目标对象
- 获取结果:
boolean r1 = result1.get();
- 关闭服务:
ser.shutdownNow();
可以定义返回值,抛出异常
public class TestCallable implements Callable<Boolean> { private String name; public TestCallable(String name) { this.name = name; } @Override public Boolean call() throws Exception { System.out.println(name); return true; } public static void main(String[] args) throws ExecutionException, InterruptedException { TestCallable t1 = new TestCallable("ming"); TestCallable t2 = new TestCallable("hong"); ExecutorService ser = Executors.newFixedThreadPool(2); Future<Boolean> result1 = ser.submit(t1); Future<Boolean> result2 = ser.submit(t2); boolean rs1 = result1.get(); boolean rs2 = result2.get(); System.out.println(rs1); System.out.println(rs2); ser.shutdownNow(); } }
- 实现
停止线程
不使用已废弃的stop()、destroy()
方法。
使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。
public class TestStop implements Runnable{
private boolean flag = true;
@Override
public void run(){
while(flag){
//自己的代码
}
}
public void stop() {
this.flag = false;
}
}
线程休眠
Thread.sleep(long millis)
- sleep时间到达后线程进入就绪状态
- 可以模拟网络延时、倒计时等
- 每个对象都有一个锁,sleep不会释放锁
//实现倒计时或时钟显示
public class TestSleep {
public static void main(String[] args) throws InterruptedException {
// TestSleep.tenDown();
//时钟显示
Date startTime = new Date(System.currentTimeMillis());
while (true){
try {
Thread.sleep(1000);
System.out.println(new SimpleDateFormat("HH:mm:ss").format(startTime));
startTime = new Date(System.currentTimeMillis());
}catch (Exception e){
e.printStackTrace();
}
}
}
//倒计时
public static void tenDown() throws InterruptedException {
int num = 10;
while (true){
Thread.sleep(1000);
System.out.println(num--);
if(num<=0){
break;
}
}
}
}
线程礼让
-
Thread.yield();
-
礼让线程,让当前正在执行的线程暂停,但不阻塞
-
将线程从运行态转为就绪态
-
让CPU重新调度,礼让不一定成功(可能还会调用暂停的那个线程)
public class TestYield {
public static void main(String[] args) {
MyYield myYield = new MyYield();
new Thread(myYield,"a").start();
new Thread(myYield,"b").start();
}
}
class MyYield implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" start");
Thread.yield();
System.out.println(Thread.currentThread().getName()+" stop");
}
}
Join方法
join在线程里面意味着“插队”,哪个线程调用join代表哪个线程插队先执行——但是插谁的队是有讲究了,不是说你可以插到队头去做第一个吃螃蟹的人,而是插到在当前运行线程的前面,比如系统目前运行线程A,在线程A里面调用了线程B.join()
方法,则接下来线程B会抢先在线程A面前执行,等到线程B全部执行完后才继续执行线程A
public class TestJoin implements Runnable{
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("vip"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 500; i++) {
if(i==200){
thread.join();
}
System.out.println("main"+i);
}
}
}
线程优先级
Java 提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
线程的优先级用数字表示,范围从1-10。
-
getPriority()
获取优先级 -
setPriority(int p)
设置优先级 -
优先级的设置应在
start()
之前;
守护线程(daemon)
setDaemon(true)
注:在start()
之前设置;- 线程分为用户线程和守护线程
- 只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作;只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作, 因为没有了被守护者,Daemon也就没有工作可做了,也就没有继续运行程序的必要了。Daemon的作用是为其他线程的运行提供便利服务。
- 守护线程不要用来执行 I/O 操作或计算逻辑等,因为一旦非守护线程全部退出,守护线程也会随之退出,假如这时候还有 I/O 操作或计算逻辑未完成,那将是毁灭性的打击。
如:后台记录操作日志、监控内存、垃圾回收等
线程同步
解决多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象,这时候就需要线程同步。线程同步其实就是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待池形成队列,等待前面的线程使用完毕,下一个线程再使用。
synchronized
关键字
synchronized
是Java中解决并发问题的一种最常用最简单的方法 ,它可以确保线程互斥的访问同步代码。
synchronized
应用:
- 普通同步方法,锁是当前实例对象。
- 同步代码块,锁是括号中的对象。
- 静态同步方法,锁是当前类的class对象。
public class TestSynchronized {
SyncObject syncObject;
//既是对象锁也是方法锁
public synchronized void method1(){}
// 对象锁:形式2(代码块形式)
public void method2(){
synchronized(syncObject){
// 访问或修改被锁保护的共享状态
}
}
//类锁:形式1 :锁静态方法
public static synchronized void method3(){}
// 类锁:形式2 :锁静态代码块
public void method4() {
synchronized (TestSynchronized.class) {}
}
}
注:
-
synchronized
修饰方法时存在缺陷:若修饰1个大的方法,将会大大影响效率 -
若使用
synchronized
关键字修饰 线程类的run()
,由于run()
在线程的整个生命期内一直在运行,因此将导致它对本类任何synchronized
方法的调用都永远不会成功解决方案:使用
synchronized
关键字声明代码块,该解决方案灵活性高:可针对任意代码块 & 任意指定上锁的对象
更多有关synchronized的知识可通过该篇博文学习https://blog.csdn.net/carson_ho/article/details/82992269
Lock(锁)
-
Lock是一个类,通过这个类可以实现同步访问。
-
Lock和synchronized有一点非常大的不同,采用synchronized不需要用户去手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用;而Lock则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
-
采用Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用Lock必须在try{}catch{}块中进行,并且将释放锁的操作放在finally块中进行,以保证锁一定被被释放,防止死锁的发生。
-
ReentrantLock
是唯一实现了Lock接口的类
class A{
private final Lock lokc = new ReenTrantLock();
public void method(){
lock.lock();
try{
}catch(Exception e){
}finally{
lock.unlock();
}
}
}
更多https://www.cnblogs.com/dolphin0520/p/3923167.html
ReentrantReadWriteLock
有两把锁,读锁和写锁- 多个线程可以同时进行读操作,此时不能进行写操作
- 只能有一个线程进行写操作,此时不能有任何别的线程进行读写操作
class RWLDemo{
private final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
private final Lock writeLock = rwl.writeLock();
private final Lock readLock = rwl.readLock();
public void m1() {
//上读锁,其他线程只能读不能写
readLock.unlock();
try {
System.out.println("get read lock");
} catch (Exception e) {
} finally {
readLock.unlock();
}
//上写锁,其他线程既不能读也不能写
writeLock.lock();
try {
System.out.println("get write lock");
}catch (Exception e){
}finally {
writeLock.unlock();
}
//先读再写的情况,请先释放读锁,再上写锁,ReentrantReadWriteLock不支持读锁升级为写锁
readLock.lock();
try {
System.out.println("get read lock");
}catch (Exception e){
}finally {
readLock.unlock();
}
writeLock.lock();
try {
System.out.println("get write lock");
}catch (Exception e){
}finally {
writeLock.unlock();
}
/**
先写再读的情况,可以在上写锁的情况,再上读锁,解锁的时候先解读锁再解写锁(ReentrantReadWriteLock支持锁降级,因此这段代码不会造成死锁)
**/
writeLock.lock();
try {
System.out.println("get write lock");
readLock.lock();
try {
System.out.println("get read lock");
}catch (Exception e){
}finally {
readLock.unlock();
}
}catch (Exception e){
}finally {
writeLock.unlock();
}
}
}
- 想了解
ReentrantReadWriteLock
的使用的可以--->https://blog.csdn.net/weixin_33779515/article/details/92575574
线程通信
Java 提供了几个方法解决线程之间的通信问题:
wait()
表示线程一直等待,直到其他线程通知,与sleep不同,会释放锁wait(long timeout)
指定等待的毫秒数notify()
唤醒一个处于等待状态的线程notifyAll()
唤醒同一个对象上所有调用wait()
方法的线程,优先级高的线程优先调度
均是Object类的方法,都只能在同步方法或者同步代码块中使用,否则会抛出异常。
深入实践可以去了解同步问题:生产者-消费者问题、读者-写作问题、哲学家进餐问题、吸烟者问题等
生产者消费者问题(管道法)
public class TestPC {
public static void main(String[] args) {
SynContainer container = new SynContainer();
new Productor(container).start();
new Consumer(container).start();
}
}
//生产者
class Productor extends Thread{
SynContainer container;
public Productor(SynContainer container) {
this.container = container;
}
//生产
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.push(new Procduct(i));
System.out.println("生产了"+i+"件产品");
}
}
}
//消费者
class Consumer extends Thread{
SynContainer container;
public Consumer(SynContainer container) {
this.container = container;
}
//消费
@Override
public void run() {
for (int i = 0; i < 100; i++) {
container.pop();
}
}
}
//产品
class Procduct{
int id;
public Procduct(int id) {
this.id = id;
}
}
//缓存区
class SynContainer{
//容器大小
Procduct[] procducts = new Procduct[10];
int count = 0;
//生产者放入产品
public synchronized void push(Procduct procduct){
//如果容器满了,等待消费者消费
if (count == procducts.length){
//通知消费者消费,生产等待
try {
System.out.println("生产等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没满就需要丢入产品
procducts[count++] = procduct;
this.notifyAll();
//可以通知消费者消费了
}
//消费者消费产品
public synchronized Procduct pop(){
//判断能否消费
if (count == 0){
//等待生产者生产,消费者等待
try {
System.out.println("消费者等待");
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//消费
count--;
Procduct procduct = procducts[count];
System.out.println("消费了"+procduct.id+"件产品");
//通知生产者生产
this.notifyAll();
return procduct;
}
}
生产者-消费者问题(信号灯法)
public class TestPC2 {
public static void main(String[] args) {
TV tv = new TV();
new Player(tv).start();
new Watcher(tv).start();
}
}
//生产者--演员
class Player extends Thread{
TV tv;
public Player(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
if (i%2==0){
this.tv.play("快乐大本营");
}else {
this.tv.play("天天向上");
}
}
}
}
//消费者--观众
class Watcher extends Thread{
TV tv;
public Watcher(TV tv) {
this.tv = tv;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
tv.watch();
}
}
}
//产品--节目
class TV{
//演员表演,观众等待
//观众观看,演员等待
String voice; //表演的节目
boolean flag = true;
//表演
public synchronized void play(String voice){
if (!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Player played"+voice);
//通知观众观看
this.voice = voice;
this.notifyAll();
this.flag = !this.flag;
}
//观看
public synchronized void watch(){
if (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("watched"+voice);
this.notifyAll();
this.flag = !this.flag;
}
}
线程池
提前创建好多个线程,放入线程池中,使用时直接获取,使用完后放回池中。可以避免频繁地创建销毁、实现重复利用,减少对系统性能的影响。
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中的线程,不需要每次都创建)
- 便于线程管理
corePoolSize
核心池的大小maixmunPoolSize
最大线程数keepAliveTime
线程无任务时最多保持多长时间后会终止
public class TestTP {
public static void main(String[] args) {
ExecutorService service = Executors.newFixedThreadPool(10);
service.execute(new MyRunnable());
service.execute(new MyRunnable());
service.execute(new MyRunnable());
service.shutdownNow();
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
阿里提供的Java规范明确表明了线程池不允许使用 Executors
去创建,而是通过 ThreadPoolExecutor
的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。 说明:Executors
返回的线程池对象的弊端如下: 1. FixedThreadPool
和 SingleThreadPool
: 允许的请求队列长度为 Integer.MAX_VALUE
,可能会堆积大量的请求,从而导致 OOM。 2.CachedThreadPool
和 ScheduledThreadPool
: 允许的创建线程数量为 Integer.MAX_VALUE
,可能会创建大量的线程,从而导致 OOM。
自定义线程池:
public class TestThreadPoolExecutor{
public static void main(String[] args) {
//等待队列
BlockingQueue<Runnable> bq = new ArrayBlockingQueue<Runnable>(10);
//创建一个带名字的线程池生产工厂
NamingThreadFactory factory = new NamingThreadFactory("--name--");
//3个核心线程,6个最大线程,1000毫秒线程空闲存活时间
ThreadPoolExecutor tpe = new ThreadPoolExecutor(3,6,1000, TimeUnit.MILLISECONDS,bq,factory);
tpe.execute(new MyRunnable());
tpe.execute(new MyRunnable());
tpe.execute(new MyRunnable());
tpe.shutdown();
}
}
自定义线程工厂
public final class NamingThreadFactory implements ThreadFactory {
private final AtomicInteger threadNum = new AtomicInteger();
private final String name;
/**
* 创建一个带名字的线程池生产工厂
*/
public NamingThreadFactory(String name) {
this.name = name;
}
@Override
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setName(name + threadNum.incrementAndGet());
return t;
}
}
更多了解https://juejin.cn/post/6882992964709122055
本文来自博客园,作者:hzyuan,转载请注明原文链接:https://www.cnblogs.com/hzyuan/p/15093798.html
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)
2020-08-03 fastjson学习笔记