Java基础总结—多线程篇

1|0多线程【重点】

1|1一、基础知识

1、进程和线程区别

进程是资源分配的最小单位,线程是cpu调度的最小单位,一个进程中可以包含多个线程

2、线程的三种创建方式

  • 继承Thread类

    package com.sqx; /* * 创建线程方式一 */ public class CreatThread { public static void main(String[] args) { MyThread thread = new MyThread(); thread.start(); //开辟新的占空间,该方法执行完成后瞬间结束! for(int i = 0 ; i < 100 ; i++ ){ System.out.println("当前执行main线程----->" + i); } } } class MyThread extends Thread{ @Override public void run() { try { Thread.sleep(5000); //当前线程休眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } for(int i = 0 ; i < 100 ; i ++ ) { System.out.println("当前执行分支线程------>" + i); } } } /* 输出结果: 当前执行main线程----->62 当前执行分支线程------>0 当前执行分支线程------>1 当前执行分支线程------>2 当前执行main线程----->63 */
  • 实现Runnable接口

    /* * 创建线程方式二 【常用一些】 */ public class CreatThread2 { public static void main(String[] args) { /* MyRunnable runnable = new MyRunnable(); Thread thread = new Thread(runnable);*/ //格式2 //Thread thread = new Thread(new MyRunnable()); //格式1 Thread thread = new Thread(new Runnable() { //匿名内部类 public void run() { for(int i = 0 ; i < 100 ; i ++ ) { System.out.println("当前执行分支线程------>" + i); } Thread thread = Thread.currentThread(); //静态方法!获取当前线程对象 System.out.println("当前线程"+thread); //得到当前线程对象,当前线程Thread[Thread-0,5,main] } }); thread.start(); //开辟新的占空间,该方法执行完成后瞬间结束! for(int i = 0 ; i < 100 ; i++ ){ System.out.println("当前执行main线程----->" + i); } } } class MyRunnable implements Runnable{ public void run() { for(int i = 0 ; i < 100 ; i ++ ) { System.out.println("当前执行分支线程------>" + i); } } } /* 测试结果: 当前执行main线程----->84 当前执行分支线程------>33 当前执行main线程----->85 当前执行分支线程------>34 当前执行main线程----->86 * */
  • 实现Callable接口 (jDK8新特性)

    //FutureTask方式,实现Callable接口 ,优点:该线程可以有返回值! public class CreatThread3 { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask task = new FutureTask(new Callable() { public Object call() throws Exception { System.out.println("分支线程开始!"); return 123; } }); Thread thread = new Thread(task); thread.start(); System.out.println(task.get()); //主线程获取分支线程的返回值 } }

3、线程的生命周期

线程有时间片,才会运行,不然就抢夺时间片!

4、获取当前线程对象

Thread thread = Thread.currentThread(); //静态方法!获取当前线程对象 thread.getName() ; thread.setName() ;

5、线程休眠 Thread.sleep

package com.sqx; /* * 线程休眠,中断线程线程休眠 * */ public class ThreadSleep { public static void main(String[] args) { MyRunnable03 myRunnable03 = new MyRunnable03(); Thread t = new Thread(myRunnable03); t.start(); System.out.println("main线程开始"); try { Thread.sleep(1000*5); //main线程休眠5秒 } catch (InterruptedException e) { e.printStackTrace(); } // t.interrupt(); //方式一 : 中断分支线程,抛出异常sleep interrupted // t.stop(); //直接杀死线程!栈都回收了,造成丢失数据!(该方法已经过时) // 合理终止线程休眠(常用): 打个bool 标价,如果true就继续休眠,否则直接结束 myRunnable03.isSleep = false ; } } class MyRunnable03 implements Runnable{ boolean isSleep = true ; public void run() { for( int i = 0 ; i < 10 ; i ++ ) { if (isSleep == true){ System.out.println("分支线程 ---- >" + i); try { Thread.sleep(1000*1); } catch (InterruptedException e) { e.printStackTrace(); } }else{ System.out.println("分支线程 ---- >" + i); return ; } } } } //线程休眠会使得当前线程放弃之前抢夺的时间片,从而线程进入就绪状态!

了解

  1. 线程优先级

    public class ThreadPriority { public static void main(String[] args) { System.out.println(Thread.MAX_PRIORITY); //线程的最大优先级 10 System.out.println(Thread.MIN_PRIORITY); //线程的最小优先级 0 System.out.println(Thread.NORM_PRIORITY); //线程的默认优先级 5 System.out.println(Thread.currentThread().getPriority()); //获取当前线程的优先级 Thread.currentThread().setPriority(10); //设置当前线程的优先级为10 } }
  2. 线程合并

    Thread thread = new Thread(new MyRunnable05()); thread.start(); thread.join(); //讲thread线程合并到当前main线程中执行,栈不会改变仅仅是执行顺序!
  3. 线程让位

    Thread.yield(); //当前线程让一下,当前线程此刻不回去抢占时间片,下一刻继续抢占

需要注意:Java的线程调度是抢占式调度模型,优先级高的线程抢占时间片的概率大!

1|2二、线程安全 *

线程安全问题的产生需要满足:多线程并发、共享数据、共享数据有修改操作 ,如:两人同时取银行卡里的余额!

怎么解决线程安全问题?

  • 引入“线程同步机制”,也就是让线程排队,不再并发执行
  • 为了安全,可以牺牲一部分效率

了解 异步,同步编程模型

  • 异步编程模型 : 线程t1 ,t2各自执行各自的,t1不管t2,t2不管t1 ;谁也不需要等谁,本质就是多线程并发
  • 同步编程模型: 线程t1 ,t2需要满足 t2执行之前必须等待t1线程执行结束,两线程发生了等待关系 本质:线程排队执行

注意:异步就是并发,同步就是排队

Synchronized 同步代码块

//t1 、t2 两个线程同时向一个共享账户Account发起取款,如何避免线程的并发执行 ? 在我们的取款方法体上添加一层 public void withdraw(int count){ synchronized (需要排队的线程的共享对象){ //注:一个对象只有一把锁 方法体 } } 当我们的t1 、t2线程调用withdraw方法时,先遇到synchronized(共享对象)的线程,会直接拿走锁池(lock)中共享对象的对象锁,从而执行方法体,此时共享对象的锁已经被占用,我们的另外一个线程只能等待共享对象的锁的归还,也就是等待上一个线程线程执行结束该同步代码块,执行结束才会归还锁,我们未执行该方法的线程再去获取锁,去执行方法!

为什么非得传入共享对象?

因为传入一个共享对象,两个线程中共享的这一个对象的对象锁被拿走,另一个对象无法获取,因此无法进入同步代码块!但是如果传入的是非共享对象,则一个线程把这个非共享对象的锁拿走,并不影响另外一个线程任何操作! (共享可以看作,两线程共用这一个)

局部变量是线程安全的!因为存储在栈中,数据不共享,常量不可修改也是线程安全的!

静态变量和实例变量则是分别在方法区和队中,存在数据共享,可能会导致线程不安全问题

Synchrnoized三种写法

方式一:同步代码块

public void withdraw(int count){ synchronized (需要排队的线程的共享对象){ //优点:灵活,哪里需要加哪里 方法体 } }

方式二:实例方法上添加Synchronized

public synchronized void withdraw(int count){ //默认是方法体中的所有都需要同步,而且共享对象为this //优点:省代码 }

方式三:在类上添加Synchronized

//表示找类锁,一个类一个锁,即使创建100个对象也仍然只有1个锁!

扩展:对象锁保证的是实例变量的安全、类锁保证的是静态变量的安全!

1|3三、死锁

实现一个死锁,由于死锁不会报错,因此很难调试,我们只有会写死锁,以后才能注意 !

package com.sqx; /* * 实现一个死锁! * */ public class DeadLock { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); MyThread1 thread1 = new MyThread1(o1, o2); MyThread2 thread2 = new MyThread2(o1, o2); thread1.start(); thread2.start(); } } class MyThread1 extends Thread{ Object o1 ; Object o2 ; public MyThread1(Object o1 , Object o2){ this.o1 = o1 ; this.o2 = o2 ; } public void run(){ synchronized (o1){ synchronized (o2){ //等待o2锁的释放 } } } } class MyThread2 extends Thread{ Object o1 ; Object o2 ; public MyThread2(Object o1, Object o2){ this.o1 = this.o1; this.o2 = this.o2; } public void run(){ synchronized (o2){ //等待o1锁的释放 synchronized (o1){ } } } }

1|4四、守护线程

守护线程一般在默默运行,用户线程全部结束,守护线程也会自动结束!

线程分为两类:

  1. 用户线程
  2. 守护线程

创建一个线程,直接设置为守护线程即可

t.setDeamon(true) //讲当前t线程设置为守护线程!

1|5五、定时器

实际开发中,每隔多久执行一段特定的程序,这种需求是很常见的!

方式一:Thread.Sleep, 设置睡眠多长时间,执行任务,最原始的方式 ;

方法二:Java类库中写好的一个定时器java.util.Timer,可以直接用,但是用的少,框架一般都带自己的定时器,

方式三 : 使用最多的就是spring框架当中提供的springTask框架,只需简单的配置就可以完成定时器(底层还是方式二);

//方式二的实现原理 public class TimeTest { public static void main(String[] args) throws ParseException { Timer timer = new Timer(); //创建定时对象 //Timer timer = new Timer(true); Timer以守护线程的形式运行 //指定定时任务(要执行的任务,第一次执行时间,间隔多久执行一次) SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); Date FirstDate = dateFormat.parse("2021-9-28 17:40:00"); timer.schedule(new LogTask(),FirstDate,1000*10); //可以改为匿名内部类方式 } } class LogTask extends TimerTask{ public void run() { //定时执行的任务 System.out.println("到时间了!"); } }

1|6六、wait和notify

wait() 和 notify () 是Object 的方法 , 通常结合synchrnoized使用

  • wait()意思是说,我等会儿再用这把锁(对象锁!),CPU也让给你们,我先休息一会儿!
  • notify()意思是说,我用完了,你们谁用?

测试代码:

package com.sqx; public class WaitTest { public static void main(String[] args) { Object obj = new Object() ; MyThread01 t1 = new MyThread01(obj); MyThread02 t2 = new MyThread02(obj); t1.start(); t2.start(); } } class MyThread01 extends Thread{ Object object ; public MyThread01(Object object) { this.object = object ; } @Override public void run() { synchronized (object){ System.out.println("T1线程执行"); try { object.wait(); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("T1线程结束!"); } } } class MyThread02 extends Thread{ Object object ; public MyThread02(Object object) { this.object = object ; } @Override public void run() { synchronized (object){ System.out.println("T2线程执行"); object.notify(); System.out.println("T2线程结束!"); } } } /* 结果: T1线程执行 T2线程执行 T2线程结束! T1线程结束! 流程: T1启动,让出锁,让出CPU,T2获得CPU,启动,唤醒使用了object的休眠的线程, T1被唤醒后等待启动,T2继续执行,T2执行完,T1获得CPU后继续执行。 * */

生产者消费者模式


多线程基础了解到这里即可,我们接下来就是JUC


__EOF__

本文作者宋淇祥
本文链接https://www.cnblogs.com/qxsong/p/15837294.html
关于博主:评论和私信会在第一时间回复。或者直接私信我。
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!
声援博主:如果您觉得文章对您有帮助,可以点击文章右下角推荐一下。您的鼓励是博主的最大动力!
posted @   爪洼ing  阅读(23)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek “源神”启动!「GitHub 热点速览」
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 我与微信审核的“相爱相杀”看个人小程序副业
· C# 集成 DeepSeek 模型实现 AI 私有化(本地部署与 API 调用教程)
· spring官宣接入deepseek,真的太香了~
点击右上角即可分享
微信分享提示

喜欢请打赏

扫描二维码打赏

支付宝打赏