当前位置: 首页 > news >正文

Java基础之多线程JUC全面学习笔记

目录

  • 初识多线程
  • 多线程的实现方式
  • 常见的成员方法
  • 线程安全的问题
  • 死锁
  • 生产者和消费者
  • 线程池
    • 自定义线程池

初识多线程

什么是多线程?
线程
线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中,是进程中的实际运作单位。

简单理解:应用软件中互相独立,可以同时运行的功能

那什么是进程呢?
进程
进程是程序的基本执行实体,比如,打开任务管理器,可以看到其中有很多的进程

多线程作用:提高效率

多线程的两个概念

并发
并发:在同一时刻,有多个指令在单个CPU上交替执行

并行
并行:在同一时刻,有多个指令在多个CPU上同时执行


多线程的实现方式

①继承Thread类的方式进行实现

多线程的第一种启动方式:

1.自己定义一个类继承Thread
2.重写run方法
3.创建子类的对象,并启动线程

	public class MyThread extends Thread{@Overridepublic void run() {//书写线程要执行代码for (int i = e; i < 100; i++) {system.out.println(getName( ) + "Helloworld" );}}}
		public static void main( String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread() ;t1.setName("线程1");t2.setName("线程2");t1.start();t2.start();}

②实现Runnable接口的方式进行实现

多线程的第二种启动方式:
1.自己定义一个类实现Runnable接口2.重写里面的run方法
3.创建自己的类的对象
4.创建一个Thread类的对象,并开启线程

    public static void main(String[] args) {//创建实现Runnable接口的类的对象RunnableInf run = new RunnableInf();//创建线程对象Thread t1 = new Thread(run);Thread t2 = new Thread(run);//给线程设置名字t1.setName("线程一");t2.setName("线程二");//开启线程t1.start();t2.start();}
public class RunnableInf implements Runnable{@Overridepublic void run() {//书写线程执行代码for (int i = 0; i < 100; i++){System.out.println(Thread.currentThread().getName()+"hello world");}}
}

③利用Callable接口和Future接口方式实现

多线程的第三种实现方式:

特点:可以获取到多线程运行的结果

步骤如下
1.创建一个类MyCallable实现callable接口
2.重写call (是有返回值的,表示多线程运行的结果)
3.创建MyCallable的对象(表示多线程要执行的任务)
4.创建FutureTask的对象(作用管理多线程运行的结果)
5.创建Thread类的对象,并启动(表示线程)

    public static void main(String[] args) throws ExecutionException, InterruptedException {//创建Mycallable的对象(表示多线程要执行的任务)MyCallable mc = new MyCallable();//创建FutureTask的对象(作用管理多线程运行的结果)FutureTask<Integer> ft = new FutureTask<>(mc ) ;//创建线程的对象Thread t1 = new Thread(ft);//启动线程t1.start();//获取线程运行结果Integer result = ft.get();System.out.println(result);}
public class MyCallable implements Callable<Integer> {@Overridepublic Integer call() throws Exception {//求100之间的和int sum = 0;for (int i = 0; i <= 100; i++ ){sum += i;}return sum;}
}

三种实现方式的对比

在这里插入图片描述


常见的成员方法

在这里插入图片描述

方法细节点

void setName( string name)
设置线程的名字(构造方法也可以设置名字)
细节:
1、如果我们没有给线程设置名字,线程也是有默认的名字的
格式:Thread-X(X序号,从0开始的)
2、如果我们要给线程设置名字,可以用set方法进行设置,也可以构造方法设置(记得继承Thread类的构造方法,快捷键Alt+Ins)


static Thread currentThread( )
获取当前线程的对象
细节:
当JVM虚拟机启动之后,会自动的启动多条线程其中有一条线程就叫做main线程
他的作用就是去调用main方法,并执行里面的代码,在以前,我们写的所有的代码,其实都是运行在main线程当中


static void sleep( long time)
让线程休眠指定的时间,单位为毫秒
细节:
1、哪条线程执行到这个方法,那么哪条线程就会在这里停留对应的时间
2、方法的参数:就表示睡眠的时间,单位毫秒;1秒= 1000毫秒
3、当时间到了之后,线程会自动的醒来,继续执行下面的其他代码


线程调度两种方式

  • 抢占式调度 最大特点是随机,根据优先级来抢占cpu资源,优先级越高,抢占概率越大
  • 非抢占式调度 特点:你一次,我一次,有规律
    在这里插入图片描述

优先级是0档到10档,默认优先级都是5
从Thread源码中可以看到

在这里插入图片描述


final void setDaemon( boolean on)
设置为守护线程细节:
当其他的非守护线程执行完毕之后,守护线程会陆续结束,不是立刻结束

通俗易懂:照下图案例
当女神线程结束了,那么备胎也没有存在的必要了

女神线程
在这里插入图片描述
备胎线程
在这里插入图片描述

main方法中
在这里插入图片描述

最后结果就是当线程1循环10次结束后,守护线程陆续结束(不会循环完,也不会立刻停止)


守护线程使用场景
比如QQ聊天,传输文件
在这里插入图片描述


出让线程解释

在这里插入图片描述


插入线程演示

    public static void main(String[] args) throws InterruptedException {//创建实现Runnable接口的类的对象RunnableInf run = new RunnableInf();//创建线程对象Thread t1 = new Thread(run);//Thread t2 = new Thread(run);//给线程设置名字t1.setName("线程一");    //开启线程t1.start();        //把t1线程插入到当前线程之前t1.join();  //这个代码运行在哪个线程上,就插入在哪个线程前for (int i = 0; i < 10; i++){System.out.println("main线程"+i);}}

线程的生命周期

在这里插入图片描述


线程安全的问题

由于线程抢占cpu资源是随机的,所以在执行业务操作时,可能同一个业务,多条线程并行执行,而造成数据错乱或丢失;比如买票的经典案例,有可能当一个线程进去执行买票逻辑时,另一个线程也进去了,结果导致卖出了两张同样编号的票。经典问题就是超卖,一票多卖,有票没卖。

解决方法自然就是给执行的业务代码加上锁,保证原子性


方法之一:同步代码块

把操作共享数据的代码锁起来

在这里插入图片描述

特点1:锁默认打卉,有一个线程进去了,锁自动关闭
特点2:里面的代码全部执行完毕,线程出来,锁自动打开

同步代码块使用如下,卖100张票的案例

public class MyThread extends Thread{//表示这个类的所有对象共享ticket数据static int ticket = 0;//创建锁对象,一定要唯一static Object object = new Object();@Overridepublic void run() {while (true){//同步代码块synchronized (object){if (ticket < 100){ticket++;System.out.println(getName()+"正在卖第" +ticket +"张票");}else {break;}}}}
}
public class Treaddemo {public static void main(String[] args) {MyThread t1 = new MyThread();MyThread t2 = new MyThread();t1.setName("窗口1");t2.setName("窗口2");t1.start();t2.start();}
}

方法之二:同步方法
在这里插入图片描述
就是把synchronized关键字加到方法上

特点1:同步方法是锁住方法里面所有的代码

特点2:锁对象不能自己指定
非静态: this
静态: 当前类的字节码文件对象


卖100张票的案例

public class MyRunnable implements Runnable{int ticket = 0;//由于MyRunnalbe方法只new了一个对象,可以不用把ticket设置为共享变量@Overridepublic void run() {while (true){if (method()) break;}}private synchronized boolean method() {synchronized (MyRunnable.class){if ( ticket == 100 ){return true;}else {try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}ticket++;System.out.println(Thread.currentThread().getName()+ "正在卖第"+ ticket +"张票");}}return false;}
}
public class Threaddemo {public static void main(String[] args) {MyRunnable mr = new MyRunnable();Thread t1 = new Thread(mr);Thread t2 = new Thread(mr);Thread t3 = new Thread(mr);t1.setName("窗口一");t2.setName("窗口二");t3.setName("窗口三");t1.start();t2.start();t3.start();}
}

方法之三:同步方法

为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock,手动上锁、手动释放锁

Lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作
Lock中提供了获得锁和释放锁的方法

void lock():获得锁
void unlock():释放锁

Lock是接口不能直接实例化,通常采用它的实现类ReentrantLock来实例化ReentrantLock的构造方法

ReentrantLock():创建一个ReentrantLock的实例

还是以买100张票为案例,使用锁的方式代码如下

public class LockThread extends Thread{static int ticket = 0;static Lock lock = new ReentrantLock();public LockThread() {}public LockThread(String name) {super(name);}@Overridepublic void run() {while (true){//这次换用锁的方式lock.lock();//上锁try {if (ticket < 100){Thread.sleep(100);ticket++;System.out.println(getName()+"正在卖第" +ticket +"张票");}else {break;}} catch (InterruptedException e) {throw new RuntimeException(e);} finally {lock.unlock();}}}
}
public class MainThread {public static void main(String[] args) {LockThread t1 = new LockThread("窗口一");LockThread t2 = new LockThread("窗口二");LockThread t3 = new LockThread("窗口三");t1.start();t2.start();t3.start();}
}

死锁

通俗点说就是锁嵌套了,线程1拿了A锁,就在线程1拿A锁时,线程2入了B锁,而线程1中间的操作还要再入B锁才能执行业务并结束;而线程2中间操作还要再入A锁才能执行业务并结束。最终二者都在等待对方释放锁,造成了死锁情况


生产者和消费者

等待唤醒机制
等待唤醒机制会让两个线程轮流执行,标准的你一次我一次

在这里插入图片描述

细节点:notify()是随机唤醒一个线程,不容易控制,一般使用的是notifyAll()方法

下面是经典的消费者和生产者案例代码,消费者是食客,生产者是厨师,中间控制是桌子,控制线程执行

中间控制者桌子

public class Desk {/*** 控制消费者和生产者的执行*///是否有面条 0:没有面条  1:有面条public static int foodFlag = 0;//总个数,也就是消费者需要的总个数public static int count = 10;//锁对象public static Object lock = new Object();
}

食客

public class Eater extends Thread{/*** 1.循环* 2.同步代码块* 3.判断共享数据是否到了末尾(到了末尾执行的逻辑)* 4.判断共享数据是否到了末尾(没到末尾执行的逻辑)*/@Overridepublic void run() {while (true){if (Desk.count == 0){break;}synchronized (Desk.lock){if (Desk.foodFlag == 0){//先看是否有产品,没有就等待try {Desk.lock.wait();//让当前线程跟锁绑定} catch (InterruptedException e) {throw new RuntimeException(e);}}else {Desk.count--;System.out.println("消费者正在消费产品,还需要消费数量:"+ Desk.count);//消费完唤醒生产者继续做Desk.lock.notifyAll();//修改桌子状态Desk.foodFlag = 0;}}}}
}

厨师

public class Cooker extends Thread{@Overridepublic void run() {while (true){if (Desk.count == 0){break;}synchronized (Desk.lock){if (Desk.foodFlag == 1){try {Desk.lock.wait();} catch (InterruptedException e) {throw new RuntimeException(e);}}else {//修改桌上食物状态Desk.foodFlag = 1;System.out.println("生产者生产了面条");//唤醒消费者Desk.lock.notifyAll();}}}}
}

main方法

public class TestDemo {public static void main(String[] args) {Eater e = new Eater();Cooker c = new Cooker();e.setName("消费者");c.setName("生产者");e.start();c.start();}
}

注意点
Desk. lock. wait(); //让当前线程跟锁进行绑定
Desk.Lock.notifyAll(); //唤醒跟这把锁绑定的所有线程
这里之所以用锁对象调用,是避免notifyAll()方法唤醒所有线程(包括方法外的比如系统线程等)用对象调用,可以指明唤醒的是哪个线程
(这里用Desk类中的lock对象调用,是应为lock对象是唯一的,只是在Desk类中new了一次)


线程池

在这里插入图片描述
用线程池作为容器存放线程
①创建一个池子,池子中是空的

②提交任务时,池子会创建新的线程对象,任务执行完毕,线程归还给池子,下回再次提交任务时,不需要创建新的线程,直接复用已有的线程即可

③但是如果提交任务时,池子中没有空闲线程,也无法创建新的线程,任务就会排队等待

Executors:线程池的工具类通过调用方法返回不同类型的线程池对象。
在这里插入图片描述

在这里插入图片描述


自定义线程池

线程池工作流程,它会有核心线程数量,临时线程数量,队列长度,空闲时间这个几个因素影响决定

当任务数量超过核心线程数量时,就会让多余任务在队列排队
当任务数量超过核心线程数量,且队列排满的情况,才会使用临时线程来处理任务
当任务数量超过核心线程数量,最大排队数量以及临时线程数量的总和,全负载时,多余任务会根据线程池的拒绝策略来丢弃或处理

临时线程的空闲时间在超过线程池初始化规定的时间就会销毁
而核心线程只有在销毁线程池时才会销毁


创建线程池会发现有7个参数

在这里插入图片描述

最后任务拒绝策略有以下几种

在这里插入图片描述

创建自定义线程池代码如下

在这里插入图片描述


最后感谢您的阅览,希望这篇文章能为您解除疑惑

相关文章:

Java基础之多线程JUC全面学习笔记

目录初识多线程多线程的实现方式常见的成员方法线程安全的问题死锁生产者和消费者线程池自定义线程池初识多线程 什么是多线程? 线程 线程是操作系统能够进行运算调度的最小单位。线程被包含在进程之中&#xff0c;是进程中的实际运作单位。 简单理解:应用软件中互相独立&…...

13.CSS文本样式

文本样式 h1 {color: blue; }● 回顾上一节的内容&#xff0c;我们让h1标题的文字变成了蓝色&#xff0c;注意如果html中有多个h1标签&#xff0c;那我们这种写法所有的h1标签都会变成蓝色&#xff0c;除了颜色&#xff0c;本节我们将学习更多的CSS属性 文字大小font-size h…...

西恩科技更新招股书:IPO前大手笔分红“套现”, 赵志安为实控人

2月14日&#xff0c;上海西恩科技股份有限公司&#xff08;下称“西恩科技”&#xff09;更新了招股书&#xff08;申报稿&#xff09;。据贝多财经了解&#xff0c;西恩科技于2022年8月12日递交上市申请材料&#xff0c;准备在创业板上市&#xff0c;此次是西恩科技第二次更新…...

【CentOS】有关时间的设置

目录环境信息date语法信息查看时间设置时间设置日期tzselecttimedatectl语法显示当前及所有时区修改时区hwclock语法读取硬件时钟使用硬件时钟设置系统时间使用系统时间设置硬件时钟如何理解硬件时钟和系统时钟环境信息 CentOS 7 date 语法信息 date --help用法&#xff1a…...

OpenCV制作Mask图像掩码

一、掩膜&#xff08;mask&#xff09; 在有些图像处理的函数中有的参数里面会有mask参数&#xff0c;即此函数支持掩膜操作&#xff0c;首先何为掩膜以及有什么用&#xff0c;如下&#xff1a; 数字图像处理中的掩膜的概念是借鉴于PCB制版的过程&#xff0c;在半导体制造中&am…...

C++STL剖析(九)—— unordered_map和unordered_multimap的概念和使用

文章目录1. unordered_map的介绍和使用&#x1f351; unordered_map的构造&#x1f351; unordered_map的使用&#x1f345; insert&#x1f345; operator[ ]&#x1f345; find&#x1f345; erase&#x1f345; size&#x1f345; empty&#x1f345; clear&#x1f345; sw…...

Android无菜单键,如何触发onCreateOptionsMenu(Menu menu)

文章目录小结问题及解决无法触发onCreateOptionsMenu(Menu menu)修改配置文件解决使用一个按钮来触发其它办法参考小结 现在的Android有三个键&#xff1a; 任务键&#xff0c;Home键&#xff0c;返回键&#xff0c;也就是没有菜单键了&#xff0c;那么如何如何触发onCreateOp…...

“黑洞”竟是外星人的量子计算机?

宇宙中的黑洞可以用作终极量子计算机&#xff0c;我们可以从中探索它们的特征。&#xff08;图片来源&#xff1a;网络&#xff09;我们完全有理由怀疑生命在我们的宇宙中很常见&#xff0c;但是为什么我们从未发现过其他生命存在的迹象&#xff1f;这个问题几乎自现代天文学诞…...

计算机网络入门

一&#xff0c;计算机网络在信息时代中的作用 21世纪的一些重要特征就是数字化&#xff0c;网络化和信息化&#xff0c;它是一个以网络为核心的信息时代。有三类大家很熟悉的网络&#xff0c;即电信网络&#xff0c;有线电视网络和计算机网络。按照最初的服务分工&#xff0c;…...

网络安全-内网DNS劫持-ettercap

网络安全-内网DNS劫持-ettercap 前言 一&#xff0c;我也是初学者记录的笔记 二&#xff0c;可能有错误的地方&#xff0c;请谨慎 三&#xff0c;欢迎各路大神指教 四&#xff0c;任何文章仅作为学习使用 五&#xff0c;学习网络安全知识请勿适用于违法行为 学习网络安全知识请…...

synchronized和Lock的区别

synchronized和lock的区别 synchronized和Lock&#xff0c;我已经通过源码级别的介绍过了&#xff0c;下面我们来总结下他们的区别 区别&#xff1a; 1.synchronized是关键字,Lock是接口&#xff0c;synchronized是JVM层实现&#xff0c;Lock是JDK中JUC包下的实现&#xff1b;…...

SpringBoot 指标监控 Actuator

Spring Boot Actuator为 Micrometer 提供了依赖管理和自动配置&#xff0c;Micrometer是一个支持 众多监控系统 的应用程序指标接口 该功能与&#xff1a;java\jdk\bin 下的 Jconsole 功能雷同 1、pom文件中引入依赖&#xff08;使用的springboot是2.7.2&#xff09; <dep…...

面试浅谈之十大排序算法

面试浅谈之十大排序算法 HELLO&#xff0c;各位博友好&#xff0c;我是阿呆 &#x1f648;&#x1f648;&#x1f648; 这里是面试浅谈系列&#xff0c;收录在专栏面试中 &#x1f61c;&#x1f61c;&#x1f61c; 本系列将记录一些阿呆个人整理的面试题 &#x1f3c3;&…...

LeetCode-1250. 检查「好数组」【数论,裴蜀定理】

LeetCode-1250. 检查「好数组」【数论&#xff0c;裴蜀定理】题目描述&#xff1a;解题思路一&#xff1a;裴蜀定理是&#xff1a;a*xb*y1。其中a,b是数组中的数&#xff0c;x,y是任意整数。如果a,b互质那么一定有解。问题即转换为寻找互质的数。解题思路二&#xff1a;简化代码…...

【Linux】NTP时间同步服务与NFS网络文件共享存储服务器(配置、测试)

一、NTP时间同步服务1、NTP介绍NTP服务器【Network Time Protocol&#xff08;NTP&#xff09;】是用来使计算机时间同步化的一种协议&#xff0c;它可以使计机对其服务器或时钟源&#xff08;如石英钟&#xff0c;GPS等等)做同步化&#xff0c;它可以提供高精准度的时间校正&a…...

windows下php连接oracle安装oci8扩展报错(PHP Startup: Unable to load dynamic library ‘oci8_11g‘)

记录一下php7.29安装oci8的艰苦过程&#xff0c;简直就是唐僧西天取经历经九九八十一难。 使用的是phpstudy_pro安装的ph扩展wnmp环境下&#xff1b; 1 、安装oralce Instant Client 首先&#xff0c;安装oci8和pdo_oci扩展依赖的Oracle client。了解到需要连接的Oracle版…...

TensorRT的功能

TensorRT的功能 文章目录TensorRT的功能2.1. C and Python APIs2.2. The Programming Model2.2.2. The Runtime Phase2.3. Plugins2.4. Types and Precision2.5. Quantization2.6. Tensors and Data Formats2.7. Dynamic Shapes2.8. DLA2.9. Updating Weights2.10. trtexec本章…...

433MHz无线通信--模块RXB90

1、接收模块RXB90简介 两个数据输出是联通的。 2、自定义一个编码解码规则 组数据为“0x88 0x03 0xBD 0xB6”。 3、发射模块 如何使用示波器得到捕捉一个周期的图像&#xff1f; 通过date引脚连接示波器CH1&#xff0c;以及示波器探针的接地端接芯片的GND&#xff0c;分…...

Seata源码学习(三)-2PC核心源码解读

Seata源码分析-2PC核心源码解读 2PC提交源码流程 上节课我们分析到了GlobalTransactionalInterceptor全局事务拦截器&#xff0c;一旦执行拦截器&#xff0c;我们就会进入到其中的invoke方法&#xff0c;在这其中会做一些GlobalTransactional注解的判断&#xff0c;如果有注解…...

IO流概述

&#x1f3e1;个人主页 &#xff1a; 守夜人st &#x1f680;系列专栏&#xff1a;Java …持续更新中敬请关注… &#x1f649;博主简介&#xff1a;软件工程专业&#xff0c;在校学生&#xff0c;写博客是为了总结回顾一些所学知识点 目录IO流概述IO 流的分类总结流的四大类字…...

【node.js】node.js的安装和配置

文章目录前言下载和安装Path环境变量测试推荐插件总结前言 Node.js是一个在服务器端可以解析和执行JavaScript代码的运行环境&#xff0c;也可以说是一个运行时平台&#xff0c;仍然使用JavaScript作为开发语言&#xff0c;但是提供了一些功能性的API。 下载和安装 Node.js的官…...

Python优化算法—遗传算法

Python优化算法—遗传算法一、前言二、安装三、遗传算法3.1 自定义函数3.2 遗传算法进行整数规划3.3 遗传算法用于旅行商问题3.4 使用遗传算法进行曲线拟合一、前言 优化算法&#xff0c;尤其是启发式的仿生智能算法在最近很火&#xff0c;它适用于解决管理学&#xff0c;运筹…...

数据埋点(Data buried point)的应用价值剖析

一、什么是数据埋点&#xff1f;数据埋点指在应用中特定的流程中收集一些信息&#xff0c;用来跟踪应用使用的状况&#xff0c;后续用来进一步优化产品或是提供运营的数据支撑。比如访问数&#xff08;Visits&#xff09;&#xff0c;访客数(Visitor&#xff09;&#xff0c;停…...

一文弄懂硬链接、软链接、复制的区别

复制 命令&#xff1a;cp file1 file2 作用&#xff1a;实现对file1的一个拷贝。 限制&#xff1a;可以跨分区&#xff0c;文件夹有效。 效果&#xff1a;修改file1&#xff0c;对file2无影响&#xff1b;修改file2&#xff0c;对file1无影响。删除file1&#xff0c;对file…...

界面组件Telerik ThemeBuilder R1 2023开创应用主题研发新方式!

Telerik DevCraft包含一个完整的产品栈来构建您下一个Web、移动和桌面应用程序。它使用HTML和每个.NET平台的UI库&#xff0c;加快开发速度。Telerik DevCraft提供最完整的工具箱&#xff0c;用于构建现代和面向未来的业务应用程序&#xff0c;目前提供UI for ASP.NET包含一个完…...

在FederatedScope 如何查看clientserver之间的传递的参数大小(通讯量)? 对源码的探索记录

在FederatedScope 如何查看client/server之间的传递的参数大小&#xff08;通讯量&#xff09;&#xff1f; 对源码的探索记录 背景需求 想给自己的论文补一个通讯开销对比实验&#xff1a;需要计算出client和server之间传递的信息(例如&#xff0c;模型权重、embedding)总共…...

2023爱分析 · 数据科学与机器学习平台厂商全景报告 | 爱分析报告

报告编委 黄勇 爱分析合伙人&首席分析师 孟晨静 爱分析分析师 目录 1. 研究范围定义 2. 厂商全景地图 3. 市场分析与厂商评估 4. 入选厂商列表 1. 研究范围定义 研究范围 经济新常态下&#xff0c;如何对海量数据进行分析挖掘以支撑敏捷决策、适应市场的快…...

20230215_数据库过程_高质量发展

高质量发展 —一、运营结果 SQL_STRING:‘delete shzc.np_rec_lnpdb a where exists (select * from tbcs.v_np_rec_lnpdbbcv t where a.telnumt.telnum and a.outcarriert.OUTCARRIER and a.incarriert.INCARRIER and a.owncarriert.OWNCARRIER and a.starttimet.STARTTIME …...

【百度 JavaScript API v3.0】LocalSearch 位置检索、Autocomplete 结果提示

地名检索移动到指定坐标 需求 在输入框中搜索&#xff0c;在下拉列表中浮动&#xff0c;右侧出现高亮的列表集。选中之后移动到指定坐标。 技术点 官网地址&#xff1a; JavaScript API - 快速入门 | 百度地图API SDK 开发文档&#xff1a;百度地图JSAPI 3.0类参考 实现 …...

运用Facebook投放,如何制定有效的竞价策略?

广告投放中&#xff0c;我们经常会遇到一个问题&#xff0c;就是不知道什么样的广告适合自己的业务。其实&#xff0c;最简单的方法就是根据我们业务本身进行定位并进行投放。当你了解了广告主所处行业及目标受众后&#xff0c;接下来会针对目标市场进行搜索和定位&#xff08;…...