【笔记】Java多线程
2021/8/3 12:36:30
本文主要是介绍【笔记】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
这篇关于【笔记】Java多线程的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-28知识管理革命:文档软件的新玩法了解一下!
- 2024-11-28低代码应用课程:新手入门全攻略
- 2024-11-28哪些办公软件适合团队协作,且能够清晰记录每个阶段的工作进展?
- 2024-11-28全栈低代码开发课程:零基础入门到初级实战
- 2024-11-28拖动排序课程:轻松掌握课程拖动排序功能
- 2024-11-28如何高效管理数字化转型项目
- 2024-11-28SMART法则好用吗?有哪些项目管理工具辅助实现?
- 2024-11-28深度剖析:6 款办公软件如何构建设计团队项目可视化管理新生态?
- 2024-11-28HTTP缓存课程:新手入门指南
- 2024-11-28实战丨证券 HTAP 混合业务场景的难点问题应对