多线程

  • 多线程的创建
  • Thread类的方法
  • 线程安全、线程同步
  • 线程通信、线程池
  • 定时器、线程状态..

Thread类

  • java是通过java.lang.Thread类来代表线程的
  • 按照面向对象的思想,Thread类应该提供了实现多线程的方式

创建线程

方式一

1.定义一个线程类:extends Thread

public class MyThread extends Thread{
//2.重写run方法
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.print(i);
}
}
} public clss ThreadDemo1{
main(){
//3.new 一个新线程
Tread t =new MyThread();
//4.调用start方法
t.start();
for(int i=0;i<5;i++){
System.out.print(i);
}
}
}

优缺点:

1.编码简单

2.线程类继承了Thread类不利于扩展

为什么用start()不用run()?

直接run()是普通方法执行,依然是一个主线程

不要把主线程的任务放到执行之前,还是单线程

方式二

  • 创建一个线程任务实现接口Runnable接口,重写run()方法

  • 创建线程类对象

  • 把对象任务交给Tread处理

    public MyThread implements Runnable{

    @Override

    public void run(){

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

    }

    }

    public class a{

    main(){

    //创建一个任务对象

    Runnable a = new MyThread();

    //任务对象交给Thread处理

    Thread t = new Thread(a);

    t.start();

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

    }

    }

Thread的构造器:

  • public Thread(String name) 可以为当线程指定名称
  • public Thread(Runnable target)分装Runnable对象为线程对象
  • public Thread(Runnable target,String name)

优缺点:

  • 实现接口,扩展性强
  • 多一层封装,如果线程有执行结果是不可以直接返回的 run()没有返回值

匿名内部类:

public class a{
main(){
//创建一个任务对象
Runnable a = new Runable(){
@Override
public void run(){
for(int i=0;i<5;i++){
System.out.print(i);
}
};
//任务对象交给Thread处理
Thread t = new Thread(a);
t.start();
for(int i=0;i<5;i++){
System.out.print(i);
}
}
}

方式三:JDK5.0 实现Callable接口和FutureTask

  • 得到任务对象

    • 实现Callable重写call(),
    • 用FutureTask把Callable对象分装成线程的任务对象
  • 把线程对象交给Thread

  • 调用Thread的strat()启动线程

  • 线程执行完毕后,通过FutureTask的get方法区获取任务执行的结果

    pubilc class A implements Callable{

    private int n;

    public A(n){

    this.n=n;

    }

    @Override

    public String Call() throw Exception{

    String sum=0;

    for(int i=0;i<n;i++){

    sum+=i;

    System.out.print(i);

    }

    return "结果是"+sum;

    }

    }

    public class B {

    main(){

    Callable c = new A(10);

    FutureTask f = new FutureTask<>(c);

    //FutureTask实现了Runable接口

    Thread t =new Thread(f);

    t.start();

    //没有结果会等待

    try{

    String s =f.get();

    }catch(Excetion e){

    e.printStackTrace();

    }

    }

    }

优缺点:

  • 实现接口扩展性强
  • 可以得到结果
  • 较为复杂

线程的常用方法

  • getName()获取线程名字

  • setName()修改线程名字

  • currentThread()获取线程的对象

    定义一个线程类:extends Thread

    public class MyThread extends Thread{

    //2.重写run方法

    @Override

    public void run(){

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

    }

    }

    public clss ThreadDemo1{

    main(){

    //3.new 一个新线程

    Tread t =new MyThread();

    //4.调用start方法

    t.setName("1号线程")

    t.start();

    Thread m =Thread,currentThread();

    for(int i=0;i<5;i++){

    System.out.print(i);

    }

      }

    }

主线程的名称就是main

线程的休眠方法:技术优化--百度网盘

  • public static void sleep(long time) 让当前的线程休眠,单位毫秒

    main(){

    for(int i =0;i<5;i++){

    if(i==3){

    //技术优化点

    Thread.sleep(3000);

    }

    }

    }

线程安全问题

  • 存在多线程共享

  • 同时访问共享资源

  • 存在修改共享资源

    public class ThreadDemo{

    main(){

    Account a =new Account ("郝泾钊",10000);

    //小明小红

    new DrawThread(acc,"小明").start();

    new DrawThread(acc,"小红").start();

    }

    }

    @Date

    @All..

    @Null..

    public class Account{

    private String card;

    private double money;

    public void drawMoney (double money){

    //判断是谁来取钱

    String name= Thread.currentThread().getName();

    //判断是否够钱

    if(this.money>=money){

    //这样更容易不安全

    System.out.print(name+"来取钱成功,出"+money);

    this.money-=moey;

    }else{

    //余额不足

    System.out.print(name+"来取钱,余额不足");

    }

    }

    }

    public class DrawThread extends Thread{

    private Account acc;

    public DrawThread(Account acc,String name){

    super(name);

    this.acc=acc;

    }

    @Override

    public void run(){

    acc.drawMoney(1000);

    }

    }

线程同步

  • 加锁

    @Date

    @All..

    @Null..

    public class Account{

    private String card;

    private double money;

    public void drawMoney (double money){

    //判断是谁来取钱

    String name= Thread.currentThread().getName();

    synchronized("heima"){

    //判断是否够钱

    if(this.money>=money){

    //这样更容易不安全

    System.out.print(name+"来取钱成功,出"+money);

    this.money-=moey;

    }else{

    //余额不足

    System.out.print(name+"来取钱,余额不足");

    }

    }

    }

    }

    synchronized(){}:

  • 锁用随机对象好不好? 不好 同步代码块

建议使用共享资源作为锁对象

@Date
@All..
@Null..
public class Account{
private String card;
private double money;
public void drawMoney (double money){
//判断是谁来取钱
String name= Thread.currentThread().getName();
synchronized(this){
//判断是否够钱
if(this.money>=money){
//这样更容易不安全
System.out.print(name+"来取钱成功,出"+money);
this.money-=moey;
}else{
//余额不足
System.out.print(name+"来取钱,余额不足");
}
}
}
}
  • 实例方法用this
  • 静态方法用类名.class

线程同步

方法上修饰synchronized

默认用this,但是代码要高度面向对象

@Date
@All..
@Null..
public class Account{
private String card;
private double money;
public synchronized void drawMoney (double money){
//判断是谁来取钱
String name= Thread.currentThread().getName();
//判断是否够钱
if(this.money>=money){
//这样更容易不安全
System.out.print(name+"来取钱成功,出"+money);
this.money-=moey;
}else{
//余额不足
System.out.print(name+"来取钱,余额不足");
}
}
}
  • 同步代码块好还是同步方法好?

    • 同步代码块好,性能好
  • 同步方法原理了?
    • 也是用synchronized修饰默认是this

Lock锁

JDK5 加入的,更丰富功能,Lock接口不能实例化

  • public ReentrantLock() 获得Lock的对象

方法:

  • void lock() 获得锁

  • void unlock() 释放锁

    @Date

    @All..

    @Null..

    public class Account{

    private String card;

    private double money;

    private final Lock lock =new ReentrantLock();

    public void drawMoney (double money){

    //判断是谁来取钱

    String name= Thread.currentThread().getName();

    //判断是否够钱

    lock.lock();

    if(this.money>=money){

    //这样更容易不安全

    System.out.print(name+"来取钱成功,出"+money);

    this.money-=moey;

    }else{

    //余额不足

    System.out.print(name+"来取钱,余额不足");

    }

    lock.unlock();

    }

    }

线程通信

  • 线程之间相互发送数据
  • 共享的数据的情况决定自己做什么

常见模型:

  • 生产者与消费者模型:
  • 生产者线程产生数据,唤醒消费者;然后等待自己;消费者消费完数据之后唤醒生产者,然后等待自己。

方法:

Object类中:

  • void wait() 让当前线程等待并释放占用锁,直到另一个线程调用notify或notifyAll方法
  • void notify() 唤醒末个等待的线程
  • void notifyAll()唤醒正在等待的所有线程

上述方法要用同步锁对象来调用

例子:

@Date
@All..
@Null..
public class Account{
private String card;
private double money;
public synchronized void drawMoney (double money){
//判断是谁来取钱
String name= Thread.currentThread().getName();
//判断是否够钱
if(this.money>=money){
//这样更容易不安全
System.out.print(name+"来取钱成功,出"+money);
this.money-=moey;
//没钱了,唤醒别的线程
this.notifyAll();
this.wait();
}else{
//当前等待,唤醒别的线程 先叫醒别人在打晕自己
this.notifyAll();
this.wait();
}
}
public synchronized void deposit(double money){
//判断是谁来存钱
String name= Thread.currentThread().getName();
//判断是否够钱
if(this.money==0){
//这样更容易不安全
System.out.print(name+"来存钱成功,存钱"+money);
this.money+=moey;
//有钱了,唤醒别的线程
this.notifyAll();
this.wait();
}else{
//有钱不存钱
this.notifyAll();
this.wait();
}
}
} main(){
Account acc =new Account("132",0);
//创建两个取钱线程
new DrawThread(acc,"小明");
new DrawThread(acc,"小红");
//创建两个存钱线程
new SaveThread(acc,"亲爹");
new SaveThread(acc,"亲爹");
new SaveThread(acc,"亲爹"); }

取钱:

public class DrawThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run(){
while(true){
acc.drawMoney(1000);
try{
Thread.sleep(3000);
}catch(Exception e){
e.printStackTrace();
}
}
}
}

存钱:

public class SaveThread extends Thread{
private Account acc;
public DrawThread(Account acc,String name){
super(name);
this.acc=acc;
}
@Override
public void run(){
acc.deposit(1000);
try{
Thread.sleep(3000);
}catch(Exception e){
e.printStackTrace();
}
}
}

线程池(重点)

  • 创建线程的开销很大,----线程池解决

线程池的接口:ExecutorService

得到线程池对象

1.使用ExecutorService的实现类ThreadPoolExecytor自创建一个线程

ExecutorService------->ThreadPoolExecytor

2.使用Executors(线程池的工具类)调用方法返回不同特点的线程池对象

public ThreadPoolExecutor(int corePoolSize,//核心线程数
int maximumPoolSize,//最大线程数
long keepAliveTime,//线程空闲时间
TimeUnit unit,//时间单位
BlockingQueue<Runnable> workQueue,//任务队列
ThreadFactory threadFactory,//线程工厂
RejectedExecutionHandler handler//拒绝策略)
{
...
}
  • corePoolSize 核心线程数,默认为1。

    • 设置规则:

      CPU密集型(CPU密集型也叫计算密集型,指的是运算较多,cpu占用高,读/写I/O(硬盘/内存)较少):corePoolSize = CPU核数 + 1

      IO密集型(与cpu密集型相反,系统运作,大部分的状况是CPU在等I/O (硬盘/内存) 的读/写操作,此时CPU Loading并不高。):corePoolSize = CPU核数 * 2
  • maximumPoolSize
    • 最大线程数,默认为Integer.MAX_VALUE 一般设置为和核心线程数一样
  • keepAliveTime
    • 线程空闲时间,默认为60s,一般设置为默认60s
  • unit
    • 时间单位,默认为秒
  • workQueue
    • 队列,当线程数目超过核心线程数时用于保存任务的队列。(BlockingQueue workQueue)此队列仅保存实现Runnable接口的任务。(因为线程池的底层BlockingQueue的泛型为Runnable)

      无界队列

      队列大小无限制,常用的为无界的LinkedBlockingQueue,使用该队列作为阻塞队列时要尤其当心,当任务耗时较长时可能会导致大量新任务在队列中堆积最终导致OOM。阅读代码发现,Executors.newFixedThreadPool 采用就是 LinkedBlockingQueue,而博主踩到的就是这个坑,当QPS很高,发送数据很大,大量的任务被添加到这个无界LinkedBlockingQueue 中,导致cpu和内存飙升服务器挂掉。

      当然这种队列,maximumPoolSize 的值也就无效了。当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。

      有界队列

      当使用有限的 maximumPoolSizes 时,有界队列有助于防止资源耗尽,但是可能较难调整和控制。常用的有两类,一类是遵循FIFO原则的队列如ArrayBlockingQueue,另一类是优先级队列如PriorityBlockingQueue。PriorityBlockingQueue中的优先级由任务的Comparator决定。

      使用有界队列时队列大小需和线程池大小互相配合,线程池较小有界队列较大时可减少内存消耗,降低cpu使用率和上下文切换,但是可能会限制系统吞吐量。

      同步移交队列

      如果不希望任务在队列中等待而是希望将任务直接移交给工作线程,可使用SynchronousQueue作为等待队列。SynchronousQueue不是一个真正的队列,而是一种线程之间移交的机制。要将一个元素放入SynchronousQueue中,必须有另一个线程正在等待接收这个元素。只有在使用无界线程池或者有饱和策略时才建议使用该队列
  • threadFactory
    • 线程工厂,用来创建线程。

      为了统一在创建线程时设置一些参数,如是否守护线程,线程一些特性等,如优先级。通过这个TreadFactory创建出来的线程能保证有相同的特性。

      它是一个接口类,而且方法只有一个,就是创建一个线程。

      如果没有另外说明,则在同一个ThreadGroup 中一律使用Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的NORM_PRIORITY 优先级和非守护进程状态。

      通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。

      如果从newThread 返回 null 时ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务
  • handler
    • 拒绝策略,默认是AbortPolicy,会抛出异常。

      当线程数已经达到maxPoolSize,且队列已满,会拒绝新任务。

      当线程池被调用shutdown()后,会等待线程池里的任务执行完毕再shutdown。如果在调用shutdown()和线程池真正shutdown之间提交任务,会拒绝新任务。

      AbortPolicy 丢弃任务,抛运行时异常。

      CallerRunsPolicy 由当前调用的任务线程执行任务。

      DiscardPolicy 忽视,什么都不会发生。

      DiscardOldestPolicy 从队列中踢出最先进入队列(最后一个执行)的任务。

核心线程--临时线程

临时线程什么时候创建?

新任务提交任务是发现核心任务都在忙时,任务队列也满了,并且可以创建临时线程,此时会创建临时线程

什么时候会开始拒绝任务?

核心线程和临时线程都在忙,任务队列也满了,新的任务会被拒绝。

线程池处理Runable、Callable任务

方法:

  • void execute(Runnable command) 执行任务、命令,没返回值--一般Runable任务
  • Future submit(Callable task)执行任务、命令,又返回值,--一般Callable任务
  • void shutdown()等任务执行完毕后关闭线程
  • List shutdownNow()立即关闭线程,停止执行的任务,并返回队列中未执行的任务

新任务的拒绝策略:

  • ThreadPoolExecutor.AborPolicy 丢弃任务并抛出RejectedExccutionException默认

  • ThreadPoolExecutor.DiscardPolicy丢弃任务,但是不抛出异常不推荐

  • ThreadPoolExecutor.DiscardOldestPolicy抛弃队列中等待最久的任务,然后把当前任务加入到队列中

  • ThreadPoolExecutor.CallerRunsPolicy由主线程负责调用任务的run()方法从绕过线程池执行

    public class TreadPoll{

    main(){

    //创建线程池对象

    ExcutorService pool =new ThreadPoolExcutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AborPolicy() )

    }

    //模拟线程处理

    Runnable target =new MyRunnable();

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    //任务队列

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    pool.execute(target);

    //创建临时线程

    pool.execute(target);

    pool.execute(target);

    //拒绝策略触发

    pool.execute(target);

    //关闭线程池(开发中一般不会使用)

    pool.shutdownNow();

    pool.shutdown();

    }

    public class MyRunnable implement Runnable{

    @Override

    public void run(){

    for(int i =0 ;i<5;i++){

    System.out.print(Thread.cuurentThread().getNmae()+"编写了hello world");

    }

    try{

    Thread.sleep(3000);

    }catch(Exception e){

    e.printStackTrace();

    }

    }

    }

处理Callable任务

public class TreadPoll{
main(){
//创建线程池对象
ExcutorService pool =new ThreadPoolExcutor(3,5,6,TimeUnit.SECONDS,new ArrayBlockingQueue<>(5),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AborPolicy() )
}
Future<String> f1=pool.submit(new MyCallable(100));
Future<String> f2=pool.submit(new MyCallable(100));
Future<String> f3=pool.submit(new MyCallable(100)); String rs = f1.get();
String rs2 =f2.get();
String rs3 = f3.get(); }

Executors工具类

  • public static ExecutorsService newCachedThreadPool() 线程池的数量随着任务的增加而增加,如果执行完毕空闲一段时间会被回收
  • public static ExecutorsService newFixedThreadPool(int nThreads)创建固定的线程池,如果某个线程因为执行异常而结束,那么线程池会补充一个新的线程替代它
  • public static ExecutorsService newSingleThreadExecutor()创建一个线程池对象,如果线程出现异常而结束,那么线程池会补充一个新的线程
  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

Executors底层也是实现 ThreadPoolExecutor实现的

main(){
ExecutorService pool=Executors.ExecutorsService newFixedThreadPool(3);
}

但是大型的并发项目会出现系统分险

  • 内存溢出,线程的内存溢出
  • 任务没有限制

定时器

方式一:Timer

方法二:newScheduledThreadPool

Timer

构造器:

  • public Timer() 创建定时器对象

方法:

  • public void schedule(TimerTask task,long delay, long period) 开启一个定时器执行task任务

问题:

  • Timer是单线程,存在延时与定时器的时间有出入的情况

  • 可能因为其中的某个任务的异常使Timer线程死掉,从而影响后续的任务执行

    main(){

    Timer timer =new Timer();

    timer.scedule(new TimerTask(){

    @Oberride

    public void run(){

    //业务

    System.out.print("定时器");

    }

    },3000,2000)

    }//3秒延时调用2秒周期调用

newScheduledThreadPool;

JDK1.5引入的并发包

Executors

  • public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)创建一个线程池,可以实现在给定的延迟后运行任务,或者定期执行任务

方法:

  • public ScheduledFuture<?> scheduleAtFixedRate(Runnable command,long initalDelay ,long period,TimeUnit unit) 周期调度方法

优点:

  • 基于线程池执行,某个任务情况不会影响其他的定时任务的执行

    main(){

    ScheduledExecutorService pool =Executor.newScheduledThreadPool(3);

    pool.scheduleAtFixedRate(new TimeTrask(){

    @Override

    public void run (){

    //业务

    }

    },0,2,TimeUnit.SECONDS);

    }//初始化延迟事件0秒 周期延迟2秒单位秒

并发和并行

多线程:并发和并行同时进行

生命周期

Thread类的枚举类的6中状态

  • new 新建状态
  • Runnable 可运行状态start()
    • Blocked锁阻塞状态
    • waiting无限等待状态wait()
    • Timed Waiting (计时等待)
      • sleep状态的线程好了不用强锁、、、我不要脸
      • wait状态的线程的时间到了,并得到锁,可以跑
      • wait状态时间没到,被唤醒,并得到锁可以跑
      • wait没有得到锁会进入锁阻塞
  • Teminated被终止状态

javaEE(多线程、线程通信、线程安全、线程池、线程池工具)的更多相关文章

  1. Java并发编程扩展(线程通信、线程池)

    之前我说过,实现多线程的方式有4种,但是之前的文章中,我只介绍了两种,那么下面这两种,可以了解了解,不懂没关系. 之前的文章-->Java并发编程之多线程 使用ExecutorService.C ...

  2. java多线程-线程通信

    线程通信的目标是使线程间能够互相发送信号.另一方面,线程通信使线程能够等待其他线程的信号. 通过共享对象通信 忙等待 wait(),notify()和 notifyAll() 丢失的信号 假唤醒 多线 ...

  3. 多线程一共就俩问题:1.线程安全(访问共享数据) 2.线程通信(wait(),notify())

    多线程一共就俩问题:1.线程安全(访问共享数据) 2.线程通信(wait(),notify()) 1.线程安全,无非就是加锁,访问共享资源时,synchronized 2.线程通信,就是控制各个线程之 ...

  4. Java多线程-同步:synchronized 和线程通信:生产者消费者模式

    大家伙周末愉快,小乐又来给大家献上技术大餐.上次是说到了Java多线程的创建和状态|乐字节,接下来,我们再来接着说Java多线程-同步:synchronized 和线程通信:生产者消费者模式. 一.同 ...

  5. JAVA基础知识之多线程——线程通信

    传统的线程通信 Object提供了三个方法wait(), notify(), notifyAll()在线程之间进行通信,以此来解决线程间执行顺序等问题. wait():释放当前线程的同步监视控制器,并 ...

  6. java多线程——线程通信

    一.线程通信目标 1.线程通信的目标是使线程间能够互相发送信号 2.线程通信使线程能够等待其他线程的信号 二.几种方式 1.通过共享对象 2.忙等待 线程 B 运行在一个循环里,以等待信号 (不释放c ...

  7. Java线程同步和线程通信

    一.线程同步 当多个线程访问同一个数据时,非常容易出现线程安全问题.这时候就需要用线程同步. 不可变类总是线程安全的,因为它的对象状态是不可改变的,但可变类对象需要额外的方法来保证线程安全. 1.同步 ...

  8. JavaSE——线程通信

    线程通信: 如果线程A和线程B持有同一个MyObject类的对象object,这两个线程会去调用不同的方法,但是它们是同步执行的,比如:线程B需要等待线程A执行完了methodA()方法之后,它才能执 ...

  9. Python网络编程(线程通信、GIL、服务器模型)

    什么是进程.进程的概念? 进程的概念主要有两点: 第一,进程是一个实体.每一个进程都有它自己的地址空间, 一般情况下,包括文本区域(text region).数据区域(data region)和堆栈( ...

  10. Java-多线程第三篇3种创建的线程方式、线程的生命周期、线程控制、线程同步、线程通信

    1.Java使用Thread类代表线程.     所有的线程对象必须是Thread类或其子类的实例. 当线程继承Thread类时,直接使用this即可获取当前线程,Thread对象的getName() ...

随机推荐

  1. 云原生之旅 - 13)基于 Github Action 的自动化流水线

    前言 GItHub Actions是一个持续集成和持续交付的平台,能够让你自动化你的编译.测试和部署流程.GitHub 提供 Linux.Windows 和 macOS 虚拟机来运行您的工作流程,或者 ...

  2. UBOOT编译--- include/config.h、 include/autoconf.mk、include/autoconf.mk.dep、u-boot.cfg(三)

    1. 前言 UBOOT版本:uboot2018.03,开发板myimx8mmek240. 2. 概述 本节主要接上一节解析 :include/config.h. include/autoconf.mk ...

  3. vue-element Form表单验证没错却一直提示错误

    在使用element-UI 的表单时,发生一个验证错误,已输入值但验证的时候却提示没有输入 修改前 <el-form-item>中的prop绑定的是cus_name,而item里面的控件绑 ...

  4. 关于CSDN微信登录接口的研究

    代码 import requests import re from threading import Thread import time import requests from io import ...

  5. 【消息队列面试】6-10:Rebalance机制、副本同步机制、架构设计、zk的作用、kafka的高性能

    六.简述kafka的Rebalance[偏向实战,有难度] 1.背景 kafka日志:在消息量大.高并发时,经常会出现rebalance中 rebalance会影响kafka性能,会阻塞partiti ...

  6. RabbitMQ、RocketMQ、Kafka延迟队列实现

    延迟队列在实际项目中有非常多的应用场景,最常见的比如订单未支付,超时取消订单,在创建订单的时候发送一条延迟消息,达到延迟时间之后消费者收到消息,如果订单没有支付的话,那么就取消订单. 那么,今天我们需 ...

  7. 读python代码-学到的python函数-2

    1.zip函数 zip() 函数用于将可迭代的对象作为参数,将对象中对应的元素打包成一个个元组,然后返回由这些元组组成的列表. 如果各个迭代器的元素个数不一致,则返回列表长度与最短的对象相同,利用 * ...

  8. MQ系列9:高可用架构分析

    MQ系列1:消息中间件执行原理 MQ系列2:消息中间件的技术选型 MQ系列3:RocketMQ 架构分析 MQ系列4:NameServer 原理解析 MQ系列5:RocketMQ消息的发送模式 MQ系 ...

  9. python基础练习题 经常更新

    小练习 1.打印出jason l1 = [11, 22, 'kevin', ['tony', 'jerry', [123, 456, 'jason']]] # print(l1[3][2][2]) l ...

  10. UnoCSS 简化 CSS 的书写,Nice!

    CSS 样式太多,重复写 在学习 UnoCSS 之前,我提出几个问题: 你是否有过写完了 HTML 之后,跳转到 style 写 CSS 这样来回跳转的痛苦? 你是否有过不知道如何给节点取类名的痛苦( ...