多线程 & 线程经典案例 &锁 详细笔记
发布日期:2022-02-21 17:40:18
浏览次数:36
分类:技术文章
本文共 11025 字,大约阅读时间需要 36 分钟。
一.多线程
- 概述: 为了提高程序的运行效率.
- 进程和线程的区别 :
- 进程:是指 正在运行的程序 .
- 线程:是指进程的实际运行单位.也是直接被操作系统调度
- 一个软件的运行依赖一个或者多个进程,一个进程包含一个或者多个线程
- 并行和并发的区别
- 并发: 是多个程序 抢占 CPU的执行权
- 并行: 是多个CPU,对应多个程序,每个CPU执行一个程序,不用抢
- 效率: 并发 > 并行
- 模拟多线程编程方式
- 继承Thread:好处是可以使用父类的所有功能,坏处是单继承/强耦合
- 实现Runnable接口:好处是解耦合,可以多继承多实现,坏处是??
二,继承Thread类
- 概述: 线程 是程序中的执行线程,Java 虚拟机允许应用程序并发地运行多个执行线程。
- 创建对象
- Thread() 分配新的 Thread 对象。
- Thread(Runnable target) 分配新的 Thread 对象。
- Thread(Runnable target, String name) 分配新的 Thread 对象。
- Thread(String name) 分配新的 Thread 对象。
- 常用方法
- static Thread currentThread() 返回对当前正在执行的线程对象的引用。
- long getId() 返回该线程的标识符。
- String getName() 返回该线程的名称。
- void run()
- void setName(String name) 改变线程名称,使之与参数 name 相同。
- static void sleep(long millis)
- void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。
- void stop() 已过时。
- 测试(模板代码)
package cn.chen.thread; //测试 Thread工具类 public class Test1_Thread { public static void main(String[] args) throws InterruptedException { //1,创建对象 Thread t = new Thread(); //2,调用方法 System.out.println( t.getId() );//获取线程的标志 t.setName("懂王");//设置线程名称 System.out.println( t.getName() );//获取线程名称 Thread-0 -> 懂王 t.run();//执行任务 t.start();//开启线程 t.stop();//结束线程 Thread.sleep(10);//让程序睡10ms //获取正在执行任务的 线程对象 Thread t2 = Thread.currentThread(); System.out.println(t2.getName());//获取线程名称 System.out.println( Thread.currentThread().getName() ); //TODO 面试题:run() 和 start()的区别 } }
- 复杂测试
package cn.chen.thread; //模拟多线程编程 -- 继承Thread类 public class Test2_Thread { public static void main(String[] args) { //第二步:::创建对象测试 MyThread t = new MyThread();//1,创建线程 -- 新建状态 // t.run();//只是普通的方法的调用过程,根本不是多线程. t.start();//2, 开启线程 -- 可运行状态 //2,模拟多线程 MyThread t2 = new MyThread(); // t2.run(); t2.start(); // TODO 创建100个线程 for (int i = 0; i < 10; i++) { new MyThread().start();//创建线程并启动 } /* 3,多线程的随机性,,因为程序的执行交给了CPU去调度,,我们无法控制 Thread-0===1 Thread-1===1 Thread-0===2 Thread-1===2 Thread-0===3 Thread-0===4 Thread-0===5 */ } } //第一步:::自定义线程类 //1, 继承Thread类 class MyThread extends Thread{ //2, 把业务 写在重写的run() //重写::子类的方法声明必须和父类一样 + 有权限 @Override public void run() { //运行状态 //打印100次线程名称 for (int i = 0; i < 100; i++) { //3,子类可以通过super关键字 调用 父类的 功能 System.out.println( super.getName()+"==="+i); } }//终止状态 }
三 实现Runnable接口
- 概述 Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称 为 run 的无参数方法。
- 常用方法
void run()
- 测试
package cn.chen.thread; //测试 Runnable接口 public class Test3_Runnable { public static void main(String[] args) { //第二步:测试 MyRunnable target = new MyRunnable(); Thread t = new Thread(target);//1,创建线程对象 t.start(); //2,启动线程 start() ....Thread //3,模拟多线程程序 Thread t2 = new Thread(target); t2.start(); /*4, 多线程结果的随机性 Thread-1~~55 Thread-1~~56 Thread-0~~11 Thread-0~~12 Thread-0~~13 Thread-0~~14 Thread-0~~15 Thread-1~~57 Thread-1~~58 */ } } //第一步:自定义多线程类 //1,实现 Runnable 接口 class MyRunnable implements Runnable{ @Override public void run(){ //2,把业务放在 重写的 run()里 //3, 打印100次线程名称 Thread....getName() for (int i = 0; i < 100; i++) { //Thread.currentThread()获取当前正在执行任务的线程对象 //.getName()获取线程对象的名字 System.out.println( Thread.currentThread().getName()+"~~"+i ); } } }
- 优劣势比较
- 继承Thread类
- 优势: 方法多,创建对象的方式多
- 劣势: 强耦合,程序设计理念相对不灵活
- 实现Runnable接口
- 优势: 松耦合,方便程序设计.因为接口可以多继承多实现,还能继承时多实现
- 劣势: 方法少只有一个run(),也不能new
四 售票案例
- 需求:设计4个售票窗口,总计售票100张。
- 继承Thread类
package cn.chen.thread; //模拟 多线程售票 //设计4个售票窗口,总计售票100张 public class Test4_Ticket { public static void main(String[] args) { //TODO 问题1 : 4个线程,卖了400张票 TODO 为什么??解决方案??? //原因: tickets是成员变量,每次new都会初始化一份,new了四次,就四份,总共400张 //解决方案: 把tickets变成 共享资源(被多个对象共享,同一份资源) -- 静态 MyTickets t1 = new MyTickets(); t1.start();//启动线程 //TODO 模拟多线程售票 MyTickets t2 = new MyTickets(); t2.start(); MyTickets t3 = new MyTickets(); t3.start(); MyTickets t4 = new MyTickets(); t4.start(); /* 多线程结果的 随机性: Thread-0===58 Thread-0===57 Thread-1===100 Thread-0===56 Thread-1===99 Thread-0===55 Thread-1===98 Thread-0===54 Thread-1===97 Thread-0===53 Thread-1===96 */ } } //实现方式1::::继承Thread类 class MyTickets extends Thread{ //1,定义变量,记录票数 static int tickets = 100 ; //2,开始卖票 -- 放在重写的run() @Override public void run() { //3,一直卖票 while (true){ //死循环,,,必须设置出口,,找地方结束循环break if(tickets>0){ //有票才卖 //TODO 问题2:: 超卖: 卖出了0 -1 -2号票 //TODO 问题3:: 重卖: 同一张票卖了3次 try { Thread.sleep(10);//TODO 让程序休眠一会儿 } catch (InterruptedException e) { e.printStackTrace(); } //重卖的原因: 同一张票卖了4次 //假设 tickets=100 , t1 t2 t3 t4 , 四个人准备卖票 //假设t1先醒,开始卖票了, 执行tickets-- ,输出100,,,,还没来得及变没自减变成99,,, //假设t3醒了,开始卖票了, tickets还是100,执行tickets-- ,输出100,,,,还没来得及变没自减变成99 //假设t4醒了,开始卖票了,tickets还是100,执行tickets-- 输出100,,,,还没来得及变没自减变成99 //假设t2醒了,开始卖票了,tickets还是100,执行tickets-- 输出100,,,,并且自减变成99 //超卖的原因: 卖出了0 -1 -2号票 //假设 tickets=1 , t1 t2 t3 t4 , 四个人准备卖票 //假设t1先醒,开始卖票了, 执行tickets-- ,输出1,,,然后自减变成 0 //假设t3醒了,开始卖票了, 此时tickets=0,执行tickets--,输出0,,,然后自减变成-1 //假设t4醒了,开始卖票了, 此时tickets=-1,执行tickets--,输出-1,,,然后自减变成-2 //假设t2醒了,开始卖票了, 此时tickets=-2,执行tickets--,输出-2,,,然后自减变成-3 System.out.println(super.getName()+"==="+tickets--); }else{ //没票就结束 break; } } } }
- 实现Runnable接口
package cn.chen.thread; //模拟 多线程售票 --实现Runnable接口 //TODO 为啥没有问题1 ?? public class Test5_Ticket { public static void main(String[] args) { MyTickets2 target = new MyTickets2(); Thread t1 = new Thread(target); Thread t2 = new Thread(target); Thread t3 = new Thread(target); Thread t4 = new Thread(target); t1.start(); t2.start(); t3.start(); t4.start(); } } //实现方式2::::实现Runnable接口 class MyTickets2 implements Runnable{ //1,定义变量,记录票数 int tickets = 100 ; //2,开始卖票 -- 放在重写的run() @Override public void run() { //3,一直卖票 while (true){ //死循环,,,必须设置出口,,找地方结束循环break if(tickets>0){ //有票才卖 //TODO 问题2:: 超卖: 卖出了0 -1 -2号票 //TODO 问题3:: 重卖: 同一张票卖了3次 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"==="+tickets--); }else{ //没票就结束 break; } } } }
五 同步锁
- 概述
- 同步是指: 有钥匙的可以开锁,没钥匙的就只能等待…
- 锁是指: 把程序中的 共享资源 加锁
- 同步和异步的区别
- 同步是指 排队等待的现象 ,是指 牺牲了效率提高了安全性
- 异步是指 不排队 发生了 抢的现象,是指 提高了效率牺牲了安全
- 效率上讲: 异步 > 同步
- 安全性上讲: 同步 > 异步
-
用法
- 使用关键字实现锁 synchronized
- 用在方法上 synchronized public void eat(){…}
- 用在代码块上 synchronized(锁对象){ 有问题的代码… }
- 使用关键字实现锁 synchronized
-
改造售票案例
- 实现Runnable接口
package cn.chen.thread; //同步锁改造 售票案例 public class Test1_Synch { public static void main(String[] args) { MyTickets2 target = new MyTickets2(); Thread t1 = new Thread(target); Thread t2 = new Thread(target); Thread t3 = new Thread(target); Thread t4 = new Thread(target); t1.start(); t2.start(); t3.start(); t4.start(); } } class MyTickets2 implements Runnable{ int tickets = 100 ; Object o = new Object() ; String a ="abc"; @Override //TODO 1, 在方法上加锁 -- 相当于,同一时刻只能有一个线程来访问方法,没人抢占 // synchronized public void run() { public void run() { while (true){ //TODO 2,在代码块上加锁 -- 考虑两个问题:锁的位置 + 锁对象 //锁的位置--合理位置就是从共享资源第一次被操作开始,用完结束 //锁对象--可以任意,但是,必须是 同一个锁对象 // synchronized (new Object()){ //不是同一个锁对象 // synchronized (o){ //同一个对象 // synchronized (a){//也是同一个对象 synchronized (this){ //也是同一个对象 if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "===" + tickets--); } else { break; } } } } }
- 继承Thread类
package cn.chen.thread;public class Test2_Synch { public static void main(String[] args) { MyTickets t1 = new MyTickets(); t1.start(); MyTickets t2 = new MyTickets(); t2.start(); MyTickets t3 = new MyTickets(); t3.start(); MyTickets t4 = new MyTickets(); t4.start(); } } class MyTickets extends Thread { static int tickets = 100; //TODO 给方法上锁:会自动分配锁对象 //给 普通方法 分配的锁对象是 this //给 静态方法 分配的锁对象是 类名.class @Override // synchronized public void run() {//默认的锁对象是this public void run() { //默认的锁对象是this while (true) { //TODO 同步代码块--考虑:锁的位置和锁对象 //锁对象 是谁?? //如果锁的是 普通的资源,锁对象可以任意,但是保证同一个对象就性 //如果锁的是 静态的资源,锁对象 必须是固定的 类名.class // synchronized (this){//不行,静态资源的对象不能随意! synchronized (MyTickets.class){ //锁静态资源必须是类名.class if (tickets > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(super.getName() + "===" + tickets--); } else { break; } } } } }
1,面试题: --同步和异步的区别 --锁的原理和用法2,了解JUC并发包 --java.util.concurrent
扩展:
- 了解 线程池 技术
- 面试题
- 进程和线程的区别
- 并行和并发的区别
- 同步和异步的区别
- 锁的原理和用法
- 线程状态
- Map的复杂数据结构
Map> map2 = new HashMap<>(); //准备value List list = new ArrayList<>(); //向list里添加多个数据 Collections.addAll(list,"tony","tommy","jerry"); //存入map map2.put(1,list); //根据key获取value List value = map2.get(1); //获取每个value for(String s : value){ //如果是tony就替换成 杨幂 if(s.equals("tony")){ String str = s.replace("tony","杨幂"); System.out.println(str); } }
- 了解JUC并发包
- java.util.concurrent
转载地址:https://blog.csdn.net/weixin_40597409/article/details/113789093 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!
发表评论
最新留言
网站不错 人气很旺了 加油
[***.192.178.218]2024年04月10日 18时53分59秒
关于作者
喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!
推荐文章
boost::bad_function_call用法的测试程序
2019-04-26
微信公众号介绍_以及注册订阅号---微信公众号开发工作笔记0001
2019-04-26
Vue模板语法---vue工作笔记0003
2019-04-26
Vue计算属性之基本使用---vue工作笔记0004
2019-04-26
Vue监视---vue工作笔记0005
2019-04-26
Vue条件渲染---vue工作笔记0008
2019-04-26
Vue事件处理_vue的事件处理超级方便_功能强大---vue工作笔记0011
2019-04-26
Vue表单数据自动收集---vue工作笔记0012
2019-04-26
Vue生命周期---vue工作笔记0013
2019-04-26
C++_类和对象_对象特性_构造函数的分类以及调用---C++语言工作笔记041
2019-04-26
C++_类和对象_对象特性_拷贝构造函数调用时机---C++语言工作笔记042
2019-04-26
C++_类和对象_对象特性_构造函数调用规则---C++语言工作笔记043
2019-04-26
C++_类和对象_对象特性_深拷贝与浅拷贝---C++语言工作笔记044
2019-04-26