JUC并发编程2(高并发,AQS)

JUC

AQS核心

当有线程想获取锁时,其中一个线程使用CAS的将state变为1,将加锁线程设为自己。当其他线程来竞争锁时会,判断state是不是0,不是自己就把自己放入阻塞队列种(这个阻塞队列是用双向链表实现),当这个线程使用完,会把state变为0,该state使用volatile修饰。在AQS内部,每个Node节点都是等待锁的线程,队列中每个排队的个体就是一个Node节点,它的等待状态waitState成员变量,也是volatile修饰,Node节点里也记录该线程是否不再等待状态,还记录锁的模式独占锁还是共享锁。

三大核心:

  • state 状态,代表加锁状态,初始值是0 (state是被volatile修饰)
  • 获取到锁的线程
  • 还有一个阻塞队列(双向队列)

AQS锁有关:

  • ReentrantLock(可重入锁)
  • ReentrantReadWriteLock.ReadLock(可重入读写锁中的读锁)
  • ReentrantReadWriteLock.WriteLock(可重入读写锁中的写锁)
  • CountDownLatch
  • CyclickBarrier
  • Semaphore

Lock使用方式:

 Lock lock = new ReentrantLock();public  void doTicket(){ lock.lock();  //加锁try {System.out.println(Thread.currentThread().getName());} catch (Exception e) {e.printStackTrace();} finally {lock.unlock();  // 解锁}}

Synchronized 和 Lock区别

  • Synchronized 内置的Java关键字,Lock是一个Java类

  • Synchronized 无法判断获取锁的状态,Lock可以判断

  • Synchronized 会自动释放锁,lock必须要手动加锁和手动释放锁!可能会遇到死锁

  • Synchronized 是可重入锁,不可以中断的,非公平的;Lock,可重入的,可以判断锁,可以自己设置

读写锁

ReadWriteLock

  • 读写锁:更加细粒度的锁
  • 读-读:可以共存
  • 读-写:不能共存
  • 写-写:不能共存

它允许读读共存,读写和写写是互斥的,适合读多写少的场景,但会有写锁饥饿问题。
锁降级:写锁可以降级到读锁,但读锁不能升级到写锁;

public class ReadWriteLockDemo {public static void main(String[] args) {MyCache mycache = new MyCache();//开启5个线程 写入数据for (int i = 1; i <=5 ; i++) {int finalI = i;new Thread(()->{ mycache.put(String.valueOf(finalI),String.valueOf(finalI));}).start();}//开启10个线程去读取数据for (int i = 1; i <=10 ; i++) {int finalI = i;new Thread(()->{String o = mycache.get(String.valueOf(finalI));}).start();}}
}class MyCache{private volatile Map<String,String> map = new HashMap<>();//普通锁//private Lock lock = new ReentrantLock();//使用读写锁private ReadWriteLock lock = new ReentrantReadWriteLock();public void put(String key,String value){//写锁lock.writeLock().lock();try {//写入System.out.println(Thread.currentThread().getName()+" 线程 开始写入");map.put(key, value);System.out.println(Thread.currentThread().getName()+" 线程 写入完成");} finally {lock.writeLock().unlock();}}public String get(String key){//读锁lock.readLock().lock();String o;try {System.out.println(Thread.currentThread().getName()+" 线程 开始读取");o = map.get(key);System.out.println(Thread.currentThread().getName()+" 线程 读取完成");} finally {lock.readLock().unlock();}return o;}
}

对于读取,我们运行多个线程同时读取,也能在一定程度上提高效率。

StampedLock

JDK8新增的读写锁(邮戳锁),它采用乐观锁,其他线程尝试获取锁不会被阻塞,对读锁优化。获取锁的方法,返回一个邮戳Stamp,邮戳Stamp为0表示获取失败,其他是成功;释放锁的方法,需要一个邮戳Stamp与成功获取锁的邮戳Stamp一致,但它不支持可重入锁;

StampedLock stampedLock = new StampedLock();
// 乐观锁
public void tryOptimisticRead() {long stamp = stampedLock.tryOptimisticRead();  // 乐观读int result = number; // 获取一下最新的值//判断是否发生改变 stampedLock.validate()System.out.println("alidate方法值(true无修改,false有修改)" + "\t" + stampedLock.validate(stamp));if (!stampedLock.validate(stamp)) {System.out.println("有人修改过------有写操作");stamp = stampedLock.readLock();try {System.out.println("从乐观读 升级为 悲观读");result = number;System.out.println("重新悲观读后result:" + result);} finally {stampedLock.unlockRead(stamp);}}
}      

常用的辅助类

CountDownLatch

这个类使一个线程等待其他线程各自执行完毕后再执行。

主要方法:

  • countDown 减一操作;
  • await 等待计数器归零。
public static void main(String[] args) throws InterruptedException {//总数6个CountDownLatch countDownLatch = new CountDownLatch(6);for (int i = 1; i <= 6; i++) {new Thread(()->{System.out.println(Thread.currentThread().getName() +" 执行do");//每个线程都数量-1countDownLatch.countDown();},String.valueOf(i)).start();}//等待计数器归零countDownLatch.await();System.out.println("必须其他线程都执行完,在执行这里");//最后执行的...
}

CyclickBarrier

用于对多个线程任务进行同步执行。

主要方法:

  • await 在所有线程任务都到达之前,线程任务都是阻塞状态
public static void main(String[] args) {CyclicBarrier cyclicBarrier = new CyclicBarrier(7,()->{System.out.println("召唤神龙的线程~");});for (int i=1;i<=7;i++){int atI = i;new Thread(()->{try {System.out.println(Thread.currentThread().getName()+" 收集了第" + atI +"颗龙珠");cyclicBarrier.await(); //加法计数 等待} catch (InterruptedException e) {e.printStackTrace();} catch (BrokenBarrierException e) {e.printStackTrace();}},"线程"+i).start();}
}

应用:

  • CyclickBarrier可以根据基于子线程进行处理其他线程的结果,处理比较复杂的业务。并且可以通过reset方法重新执行方法。
  • CountDownLoatch则必须在主线程才能处理,一般用于任务执行初始化数据

Semaphore

信号量,在信号量定义两种操作:

  • acquire(获取)当一个线程调用acquire操作,它通过成功获取信号量(信号量-1),有阻塞,直到有线程释放信号量,或者超时。
  • release(释放)实际上将信号量的值+1,然后唤醒等待的线程。
public static void main(String[] args) {//停车位为3个Semaphore semaphore = new Semaphore(3);for (int i=1 ; i<=10; i++){int atI = i;new Thread(()->{try {semaphore.acquire(); //得到System.out.println(Thread.currentThread().getName() + "  抢到停车位" + atI);TimeUnit.SECONDS.sleep(2);System.out.println(Thread.currentThread().getName() + "  离开停车场");} catch (Exception e) {e.printStackTrace();} finally {semaphore.release();  //释放}},"线程"+i).start();}
}

作用: 多个共享资源互斥的使用! 并发限流,控制最大的线程数!

异步回调

CompletableFuture

就是另启一个线程来完成调用中的部分计算,使调用继续运行或返回,而不需要等待计算结果。一个completetableFuture就代表了一个任务。相当于前端的ajax异步请求。

没有返回值的异步回调:

@Test
void test1() throws ExecutionException, InterruptedException {System.out.println(System.currentTimeMillis());System.out.println("---------------------");CompletableFuture<Void> future = CompletableFuture.runAsync(()->{//发起一个异步任务try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+".....");});System.out.println(System.currentTimeMillis());System.out.println("------------------------------");//输出执行结果System.out.println(future.get());  //获取执行结果
}

有返回值的异步回调supplyAsync:

void test2() throws ExecutionException, InterruptedException {System.out.println(System.currentTimeMillis());System.out.println("---------------------");//有返回值的异步回调CompletableFuture<Integer> completableFuture=CompletableFuture.supplyAsync(()->{System.out.println(Thread.currentThread().getName());try {TimeUnit.SECONDS.sleep(2);//自定义手动的异常int i=1/0;} catch (InterruptedException e) {e.printStackTrace();}return 1024;});System.out.println(completableFuture.whenComplete((t, u) -> {//success 回调System.out.println("t=>" + t); //正常的返回结果System.out.println("u=>" + u); //抛出异常的 错误信息}).exceptionally((e) -> {//error回调  如果异常,返回404System.out.println(e.getMessage());return 404;}).get());
}

ThreadLocal

它是线程本地变量,解决多线程并发时访问共享变量的问题。它并不解决线程之间共享数据的问题,它适用于变量在线程之间隔离且在方法间共享的场景,每个线程持有一个只属于自己的专属的Map并维护ThreadLocal对象与具体实例的映射,该Map只有被持有它的线程访问,就没有线程安全问题以及锁的问题。

# 使用
static ThreadLocal<Object> threadLocal = new ThreadLocal<>();# 初始化
private static final ThreadLocal<Object> threadLocal = ThreadLocal.withInitial(Object::new);
private static final ThreadLocal<Object> threadLocal = ThreadLocal.withInitial(()->{初始化的值});# 设置线程本地变量的内容
threadLocal.set(user);# 获取线程本地变量的内容
threadLocal.get();# 移除线程本地变量
threadLocal.remove();

线程中断机制

一个线程中断,应该由线程自己停止,不能由其他线程进行停止。

# 中断此线程
interrupt()
# 判断当前线程是否被中断
isInterrupted()
# 案例
Thread t1 = new Thread(() -> {while (true) {// Thread.currentThread().isInterrupted() 判断线程是否被中断,true:中断状态if (Thread.currentThread().isInterrupted()) {System.out.println(Thread.currentThread().getName() + "\t 【实现方式3】,程序停止");break;}System.out.println("t1 -----正在运行!!!");
} }, "t1");
t1.start();
System.out.println("-----t1的默认中断标志位:" + t1.isInterrupted());

LockSupport

可以阻塞当前线程以及唤醒指定被阻塞的线程,有park()和unpark()进行通知与唤醒。

 Thread t1 = new Thread(() -> {TimeUnit.SECONDS.sleep(3);System.out.println(Thread.currentThread().getName() + "\t ----【t1】开始执行" + System.currentTimeMillis());LockSupport.park();System.out.println(Thread.currentThread().getName() + "\t ----【t1】 被唤醒" + System.currentTimeMillis());
}, "t1");
t1.start();new Thread(() -> {LockSupport.unpark(t1);System.out.println(Thread.currentThread().getName() + "\t 【t2】唤醒t1线程----发出通知");}, "t2").start();

volatile关键字

Volatile 是 Java 虚拟机提供 轻量级的同步机制。

volatile三大特性

  1. 保证可见性 (要么都完成,要么都不完成)
  2. 不保证原子性
  3. 禁止指令重排

JMM:Java内存模型,是java虚拟机规范中所定义的一种内存模型,Java内存模型是标准化的,屏蔽掉了底层不同计算机的区别(注意这个跟JVM完全不是一个东西,现在还有小伙伴搞错的)。

关于JMM的一些同步的约定

  1. 线程解锁前,必须把共享变量立刻刷回主存。
  2. 线程加锁前,必须读取主存中的 新值到工作内存中!
  3. 加锁和解锁是同一把锁

首先我们要了解JMM (Java内存模型),线程分为 工作内存主内存。 图中,有两个线程,每个线程都有属于自己的工作内存;我们首先在主存中有Flag变量,有一个线程A,从主存中读取值,把值加载工作内容中,这样工作内存中也有一个Flag变量。线程A修改值先修改工作内容,并把值再写入到主存中。这个流程叫内存交互。

内存交互有8种:

  • lock (锁定):作用于主内存的变量,把一个变量标识为线程独占状态
  • unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放,释放后的变量才可以被其他线程锁定
  • read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便 随后的load动作使用
  • load (载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
  • use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机 遇到一个需要使用到变量的值,就会使用到这个指令
  • assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中的值放入工作内存的变量副本中
  • store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中, 以便后续的write使用
  • write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中

问题: 程序不知道主内存的值已经被修改过了,但是volatile关键字,可以解决这个问题。

例子:private volatile boolean flag = false;

volatile 是保证原子性的,

原子性 : 不可分割

线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。

解决原子性问题,加锁/使用原子类

原子变量

原子变量:来源于 java.util.concurrent.atomic 类的小工具包,支持在单个变量上解除锁的线程安全编程,包下提供了常用的原子变量:
- AtomicBoolean 、AtomicInteger 、AtomicLong 、 AtomicReference
- AtomicIntegerArray 、AtomicLongArray
- AtomicMarkableReference
- AtomicReferenceArray

  • AtomicStampedReference

类中的变量都是volatile类型:保证内存可见性
使用CAS算法:保证数据的原子性

例子:

public class VDemo02 {// volatile 不保证原子性// private volatile static int num = 0;// 原子类的 Integerprivate volatile static AtomicInteger num = new AtomicInteger();public static void add(){// num++;   // 不是一个原子性操作// 原子类的 加减  // AtomicInteger + 1 方法, CASnum.getAndIncrement();}public static void main(String[] args) {//理论上num结果应该为 2 万for (int i = 1; i <= 20; i++) {new Thread(() -> {for (int j = 0; j < 1000; j++) { add(); }}).start();}while (Thread.activeCount() > 2) { Thread.yield();}System.out.println(Thread.currentThread().getName() + " " + num);}}

指令重排

你写的程序,计算机并不是按照你写的那样去执行的。

源代码–>编译器优化的重排–> 指令并行也可能会重排–> 内存系统也会重排—> 执行

Volatile 是可以保持 可见性。不能保证原子性,由于内存屏障(一种屏障指令,使用CPU对屏障指令前后发出内存操作,执行一个排序约束),可以保证避免指令重排的现象产生!

CAS算法

(Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。

CAS 比较并交换,当且仅当V==A时,B的值才更新给A,否则将不做任何操作。

public static void main(String[] args) {// 原子类AtomicInteger atomicInteger = new AtomicInteger(2020);// public final boolean compareAndSet(int expect, int update) ,参数1 期望的值,参数2,更新的值// 如果我期望的值达到了,那么就更新,否则,就不更新, CAS 是CPU的并发原语!// compareAndSet 也是CASatomicInteger.compareAndSet(2020, 2021);// 输出值System.out.println(atomicInteger.get());
}

Unsafe 类:

底层调用这个类,JAVA无法操作内存,C++可以操作内存,但是JAVA可以调用C++,Unsafe 类,就是java可以通过这个类操作内存。

CAS : 比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就 一直循环!

缺点:

  • 循环会耗时
  • 一次性只能保证一个共享变量的原子性
  • ABA问题

解决ABA 问题

比如一个线程1从内存位置V中取出A ,线程2也执行,将A–>B–>A,这时线程1进行CAS操作发现内存仍是A,然后线程1操作成功。

就是有一个线程速度快,把一个值改变,然后在改变原来的值,其他线程进行CSA,比较并交换,发现值一样,替换新值,并不知道他之前有人已经改过值了。

解决这问题:

  • 将类变成原子类
  • 操作过程添加版本号
// 原子引入
static AtomicStampedReference<Integer> atomic = new AtomicStampedReference<>(1,1);
public static void main3(String[] args) {new Thread(()->{// 获得版本号int stamp = atomic.getStamp();System.out.println("线程a1=>"+stamp);try {TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}// 比较并交换 ,增加一个参数,版本号 ,然后再修改版本号的值// 获取最新的版本号  atomic.getStamp()atomic.compareAndSet(1, 2, atomic.getStamp(), atomic.getStamp() + 1);System.out.println("线程a2=>"+ atomic.getStamp());System.out.println(atomic.compareAndSet(2, 1, atomic.getStamp(), atomic.getStamp() + 1));System.out.println("线程a3=>"+atomic.getStamp());},"线程A").start();// 乐观锁的原理相同!new Thread(()->{int stamp = atomic.getStamp(); // 获得版本号System.out.println("线程b1=>"+stamp);try {TimeUnit.SECONDS.sleep(2);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(atomic.compareAndSet(1, 6, stamp, stamp + 1));System.out.println("线程B2=>"+atomic.getStamp());},"线程B").start();}

原子类

基本类型类:AtomicBoolean、AtomicIntegern、AtomicLong
数组类型类:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
引用类型类:AtomicReference、AtomicReferenceFieldUpdater、AtomicMarkableReference、AtomicMarkableReference
LongAdder的效率比AtomicLong高(减少自旋次数)

AtomicLong

  • 原理:CAS + 自旋(它是多线程对单个热点值进行原子操作)
  • 缺点:当线程大量自旋会导致CPU生高,

LongAdder

  • 原理:CAS + Base + Cell数组(用空间换时间,采用分散热点数据)
  • 缺点:它的sum求和,对于最后结果不够准确

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/808413.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

探索ChatGPT-Plus:AI 助手全套开源解决方案

探索ChatGPT-Plus&#xff1a;AI 助手全套开源解决方案 ChatGPT-plus是一种新型的对话生成模型&#xff0c;它是在OpenAI的ChatGPT基础上进行了改进和优化的版本。ChatGPT-plus的出现引起了广泛关注&#xff0c;因为它在对话生成方面展现出了更加出色的表现和能力。在本文中&am…

蒙特卡洛方法【强化学习】

强化学习笔记 主要基于b站西湖大学赵世钰老师的【强化学习的数学原理】课程&#xff0c;个人觉得赵老师的课件深入浅出&#xff0c;很适合入门. 第一章 强化学习基本概念 第二章 贝尔曼方程 第三章 贝尔曼最优方程 第四章 值迭代和策略迭代 第五章 强化学习实践—GridWorld 第…

make/makefile学习

文章目录 1、makefile函数1.1、字符串替换函数&#xff1a;subst1.2、模式字符串替换函数&#xff1a;patsubst1.3、去空格函数&#xff1a;strip1.4、查找字符串函数&#xff1a;findstring 2、、:、&#xff1f;区别 1、makefile函数 1.1、字符串替换函数&#xff1a;subst …

《QT实用小工具·二十》存款/贷款计算器

1、概述 源码放在文章末尾 该项目实现了用于存款和贷款的计算器的功能&#xff0c;如下图所示&#xff1a; 项目部分代码如下&#xff1a; #ifndef WIDGET_H #define WIDGET_H#include <QWidget>namespace Ui { class Widget; }class Widget : public QWidget {Q_OBJ…

构造函数,原型对象,对象实例 以及原型链的关系

当我们使用构造函数new的方式创建实例对象&#xff0c;此时构造函数的.prototype属性就是实例对象的原型对象&#xff0c;实例对象可以通过.__proto__来访问到原型对象&#xff0c;同时实例对象会继承原型对象的属性方法。 构造函数和原型对象的关系其实是被包含的关系&#x…

【电子通识】普通电阻、敏感电阻、可调电阻的种类和特点

电阻的作用 在【分立元件】理解电阻 中我们知道电阻是在电路中对电流产生阻碍作用的元件。电阻是电子产品中最基本、最常用的电子元件之一。 有各产品的电路板中基本都有电阻器&#xff0c;通常起限流、滤波或分压等作用。实际上&#xff0c;电阻器的种类很多&#xff0c;根据其…

中服云数字孪生平台 3.0 版全新升级!不仅更好看,而且更好用!

近日&#xff0c;中服云数字孪生平台迎来版本升级。中服云数字孪生平台 3.0 版&#xff0c;以物联网平台 数据中台为基础&#xff0c;以 2D/3D 为展示形式&#xff0c;旨在打造一个从设备数据到孪生应用的一站式数智化平台。 中服云数字孪生平台架构 新版升级 功能急速迭代 …

4.Hexo 页面属性和模板设置

Frontmatter frontmatter基本上是可以定义的有关不同文件的信息&#xff0c;本质上是元数据 frontmatter是我们可以分配给每个内容页面的信息 在Hexo中创建文件时&#xff0c;Hexo主题可以使用该信息以不同的方式显示该内容 当在Hexo创建了一个文件&#xff0c;在source文件夹…

【JavaWeb】Tomcat服务器

目录 动态网站动态网站的特点 程序架构B/S与C/S的比较B/S技术的工作原理URL 什么是Web服务器 Web服务器、服务端、服务器的区别和联系什么是TomcatTomcat服务器的安装与配置解压缩版本Tomcat的配置添加系统变量&#xff0c;名称为CATALINA_HOME&#xff0c;值为Tomcat的安装目录…

Qt中播放GIF动画

在Qt应用程序中&#xff0c;如果你想在QLabel控件上播放GIF动画&#xff0c;可以使用QMovie类与QLabel配合来实现。以下是详细步骤和代码示例&#xff1a; 步骤1&#xff1a;引入必要的头文件 首先&#xff0c;在你的源代码文件中包含QMovie和QLabel相关的头文件&#xff1a;…

【微命令】git 如何修改某个分支的名字(git branch -m newbranch)

简要信息&#xff0c;快速记录 命令 # 切换到某个需要修改的分支 git checkout oldbranch# 修改分支名字 git branch -m newbranch假设作为git设计者&#xff0c;要用来修改branch的命令&#xff0c;那么就是 git branch作为前缀&#xff0c;然后进一步修改的命令是branch相关…

gradio简单搭建——关键词简单筛选【2024-4-11优化】

gradio简单搭建——关键词简单筛选[2024-4-11 优化] 新的思路&#xff1a;标签自动标注界面搭建优化数据处理与生成过程交互界面展示 新的思路&#xff1a;标签自动标注 针对通过关键词&#xff0c;在文本数据中体现出主体的工作类型这一任务&#xff0c;这里使用展示工具grad…

2012年认证杯SPSSPRO杯数学建模A题(第二阶段)蜘蛛网全过程文档及程序

2012年认证杯SPSSPRO杯数学建模 A题 蜘蛛网 原题再现&#xff1a; 第二阶段问题   现在我们假设一个具体的环境。假设有一个凸多边形的区域&#xff0c;蜘蛛准备在这个区域&#xff08;或其一部分&#xff09;上结一张网。   问题一&#xff1a; 在区域的边界上安置有若干…

HarmonyOS开发实例:【手势截屏】

介绍 本篇Codelab基于手势处理和截屏能力&#xff0c;介绍了手势截屏的实现过程。样例主要包括以下功能&#xff1a; 根据下滑手势调用全屏截图功能。全屏截图&#xff0c;同时右下角有弹窗提示截图成功。根据双击手势调用区域截图功能。区域截图&#xff0c;通过调整选择框大…

GeoServer:忘记密码重置

操作步骤 1. 找到data_dir/security/usergroup/default目录下的users.xml文件&#xff0c; 2.修改password为plain:geoserver&#xff0c; 这里无论原来的密码是什么&#xff0c;改为plain:geoserver之后&#xff0c;就可以通过admin&#xff1a;geoserver默认账户密码登录了。…

室内定位中文综述阅读

1 室内高精度定位技术总结与展望 [4]柳景斌,赵智博,胡宁松等.室内高精度定位技术总结与展望[J].武汉大学学报(信息科学 版),2022,47(07):997-1008.DOI:10.13203/j.whugis20220029. 1.1.1 WiFi‐RTT定位 2016 年 12 月&#xff0c;随着新版 IEEE802.11 标准的公布&#xff0c…

开一家抖音小店都需要准备什么?今天一篇文章带你成功开店!

大家好&#xff0c;我是电商小布。 想要来玩抖店&#xff0c;第一件事情当然就是来开一家属于自己的抖店。 那么大家开抖店需要准备什么内容呢&#xff1f;这个是很多新手小伙伴不了解的东西。 所以呢今天&#xff0c;小布就从所需的材料和资金方面&#xff0c;来带大家详细的…

直播带货行业将迎来大地震

关注卢松松&#xff0c;会经常给你分享一些我的经验和观点。 为什么这么多人喊着关闭直播带货?实体经济是到底因为什么萧条的?为什么大街上冷冷清清的?是房租、虚高的价格、还是直播带货引起的? 在4月9日的国务院政策吹风会上&#xff0c;市场监管明确指出&#xff1a; …

选择程序员是为什么?

本章节是关于为什么会选择一名程序员的经验分享 首先&#xff0c;我为什么会选择这个方向&#xff0c;可能是因为钱多&#xff0c;学东西不就是为了赚钱嘛&#xff1f;这是一点&#xff0c;不过最让我接收这个行业的是好奇世界的新大陆&#xff0c;可以简单的说就是&#xff0c…

ubuntu系统安装python虚拟环境

一、安装python&#xff1a; 步骤1&#xff1a;在Ubuntu系统中打开终端&#xff0c;你可以使用快捷键CtrlAltT来打开终端&#xff0c;或者在应用程序菜单中找到终端。 步骤2&#xff1a;更新软件包列表&#xff0c;在终端中输入以下命令&#xff0c;更新软件包列表&#xff1…