多线程 & 线程经典案例 &锁 详细笔记
发布日期:2022-02-21 17:40:18 浏览次数:36 分类:技术文章

本文共 11025 字,大约阅读时间需要 36 分钟。

一.多线程

  1. 概述:
    为了提高程序的运行效率.
  2. 进程和线程的区别 :
  • 进程:是指 正在运行的程序 .
  • 线程:是指进程的实际运行单位.也是直接被操作系统调度
  • 一个软件的运行依赖一个或者多个进程,一个进程包含一个或者多个线程
  1. 并行和并发的区别
  • 并发: 是多个程序 抢占 CPU的执行权
  • 并行: 是多个CPU,对应多个程序,每个CPU执行一个程序,不用抢
  • 效率: 并发 > 并行
  1. 模拟多线程编程方式
  • 继承Thread:好处是可以使用父类的所有功能,坏处是单继承/强耦合
  • 实现Runnable接口:好处是解耦合,可以多继承多实现,坏处是??

二,继承Thread类

  1. 概述:
    线程 是程序中的执行线程,Java 虚拟机允许应用程序并发地运行多个执行线程。
  2. 创建对象
  • Thread()
    分配新的 Thread 对象。
  • Thread(Runnable target)
    分配新的 Thread 对象。
  • Thread(Runnable target, String name)
    分配新的 Thread 对象。
  • Thread(String name)
    分配新的 Thread 对象。
  1. 常用方法
  • 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() 已过时。

  1. 测试(模板代码)
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()的区别 } }
  1. 复杂测试
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接口

  1. 概述
    Runnable 接口应该由那些打算通过某一线程执行其实例的类来实现。类必须定义一个称 为 run 的无参数方法。
  2. 常用方法
void run()
  1. 测试
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 ); } } }
  1. 优劣势比较
  • 继承Thread类
    • 优势: 方法多,创建对象的方式多
    • 劣势: 强耦合,程序设计理念相对不灵活
  • 实现Runnable接口
    • 优势: 松耦合,方便程序设计.因为接口可以多继承多实现,还能继承时多实现
    • 劣势: 方法少只有一个run(),也不能new

四 售票案例

  1. 需求:设计4个售票窗口,总计售票100张。
  2. 继承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; } } } }
  1. 实现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; } } } }

五 同步锁

  1. 概述
  • 同步是指: 有钥匙的可以开锁,没钥匙的就只能等待…
  • 锁是指: 把程序中的 共享资源 加锁
  • 同步和异步的区别
    • 同步是指 排队等待的现象 ,是指 牺牲了效率提高了安全性
    • 异步是指 不排队 发生了 抢的现象,是指 提高了效率牺牲了安全
    • 效率上讲: 异步 > 同步
    • 安全性上讲: 同步 > 异步
  1. 用法

    • 使用关键字实现锁 synchronized
      • 用在方法上
        synchronized public void eat(){…}
      • 用在代码块上
        synchronized(锁对象){ 有问题的代码… }
  2. 改造售票案例

  • 实现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

扩展:

  1. 了解 线程池 技术
  2. 面试题
    • 进程和线程的区别
    • 并行和并发的区别
    • 同步和异步的区别
    • 锁的原理和用法
    • 线程状态
  3. 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); } }
  1. 了解JUC并发包
    • java.util.concurrent

转载地址:https://blog.csdn.net/weixin_40597409/article/details/113789093 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:JVM原理 | TLAB是什么
下一篇:红黑树 详细笔记

发表评论

最新留言

网站不错 人气很旺了 加油
[***.192.178.218]2024年04月10日 18时53分59秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章

boost::bad_function_call用法的测试程序 2019-04-26
boost::function_types::is_callable_builtin用法的测试程序 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
ES6-ES11新特性_ECMAScript_简单介绍---JavaScript_ECMAScript工作笔记001 2019-04-26
ES6-ES11新特性_ECMAScript相关名词介绍_---JavaScript_ECMAScript工作笔记002 2019-04-26
ES6新特性_let变量声明以及声明特性---JavaScript_ECMAScript_ES6-ES11新特性工作笔记003 2019-04-26
Sharding-Sphere,Sharding-JDBC_介绍_Sharding-Sphere,Sharding-JDBC分布式_分库分表工作笔记001 2019-04-26
Sharding-Sphere,Sharding-JDBC_分库分表介绍_Sharding-Sphere,Sharding-JDBC分布式_分库分表工作笔记002 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
AndroidStudio_java.util.ConcurrentModificationException---Android原生开发工作笔记237 2019-04-26