多线程学习

这个只是简单的学了一下怎么用,几个小demo,没有深入的讲。

1. 线程简介

线程,进程,多线程

  • 并发:同时发生,在一个时间段内执行,不一定是同一时间点
  • 并行:同时执行,在一个时间点上有多个线程执行

关于run()和start()

  • 一个进程可以有多个线程

三个概念,程序、进程、线程:

  • 程序:静态代码
  • 进程:程序的一次执行过程
  • 线程:一个进程可以包含多个线程, 线程是CPU调度和执行的单位

很多多线程是模拟出来的,真正的多线程是指有多个CPU。模拟的线程只是一个CPU切换的很快产生了多线程的错觉。其实就是并发和并行的区别。

  • main()函数就是主线程;
  • 默认的话有主线程、GC线程等;
  • 线程因为调度,会代来开销
  • 资源抢夺问题,需要并发控制

2. 线程实现(重点)

  • 继承Thread类
  • 实现Runnable接口
  • 实现Callable接口

三种实现方式

java

public class TestThread1 extends Thread {

PS: 线程开启不一定立即执行,由CPU调度执行

java

public class TestThread2 extends Thread {    private String url;    private String name;​    public TestThread2(String url, String name) {        this.name = name;        this.url = url;    }​    public static void main(String[] args) {        TestThread2 testThread1 = new TestThread2("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "1.png");        TestThread2 testThread2 = new TestThread2("https://commons.apache.org/proper/commons-io/images/io-logo-white.png", "2.png");        TestThread2 testThread3 = new TestThread2("http://www.apache.org/events/current-event-125x125.png", "3.png");​        testThread1.start();        testThread2.start();        testThread3.start();    }​

需要Apache的一个包 commons-io

下载后Add to Library。

java

public class TestThread3 implements Runnable {    public static void main(String[] args) {
  • 继承 Thread 类:
  • 启动线程:子类对象.start()
  • 不推荐使用:避免单继承局限性
  • 实现 Runnable 接口:
  • 启动线程:new Thread(对象) + thread.start()
  • 推荐使用:没有单继承局限性,方便同一个对象被多个线程使用

Runnable可以被重复使用

Thread:基于继承;Runnable:基于组合;创建Thread比一个Runnable成本要昂贵一点。/

卖票问题

  • Thread.currentThread.getName():得到当前正在执行的线程方法
  • Thread.sleep(200):此线程暂停200ms

java

public class TestThread4 implements Runnable {    private int ticketNums = 10;​    public static void main(String[] args) {        TestThread4 t = new TestThread4();​        new Thread(t, "小明").start();        new Thread(t, "Bob").start();        new Thread(t, "牛牛").start();​    }​

这里还没讲怎么解决,看之后的。

龟兔赛跑问题

java

public class Race implements Runnable{​    private String winner;​

Callable其实相当于一个增强的Runnable,带有返回结果。

java

public class TestCallable implements Callable<Boolean> {
   private String url;
   private String name;

   public TestCallable(String url, String name) {
       this.name = name;
       this.url = url;
  }

   public static void main(String[] args) throws ExecutionException, InterruptedException {
       TestCallable tc1 = new TestCallable("https://commons.apache.org/proper/commons-io/images/commons-logo.png", "1.png");
       TestCallable tc2 = new TestCallable("https://commons.apache.org/proper/commons-io/images/io-logo-white.png", "2.png");
       TestCallable tc3 = new TestCallable("http://www.apache.org/events/current-event-125x125.png", "3.png");

继承Callable的时候会有个泛型,指定返回值的类型。

然后实现的是 call()方法,带有返回值。

Callable步骤

多线程的 Thread 和 Runnable 就是静态代理模式。

在代理模式(Proxy Pattern)中, 一个类代表另一个类的功能。这种类型的设计模式属于结构型模式。
主要解决:在直接访问对象时带来的问题
何时使用:想在访问一个类时做一些控制。
如何解决:增加中间层。
关键代码:实现与被代理类组合。

这里举个例子:

image-20210623152348219

java

3. 线程状态

线程的五大状态

Java中的线程状态变化

  • 不推荐使用JDK提供的stop()、destroy()等方法。【已废弃】
  • 推荐线程自己停止下来
  • 建议使用一个标志位进行终止变量当flag=false,则终止线程运行。

标志位的方式:

java

sleep()
  • sleep(1000),休眠1000毫秒,也就是1s
  • 会抛出 InterruptedException
  • 调用 sleep,线程进入阻塞状态;sleep 时间到达后,进入就绪状态
  • sleep 不会释放对象的锁,wait 会释放对象锁

java

public class TestSleep{    // 模拟倒计时    public void timeCount(int second) throws InterruptedException {        while (second > 0){            System.out.println(second--);            Thread.sleep(1000);        }    }    // 打印系统当前时间    public void timePrinter() throws InterruptedException {        SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");        while (true){            System.out.println(dateFormat.format(new Date(System.currentTimeMillis())));            Thread.sleep(1000);        }    }    public static void main(String[] args) throws InterruptedException {        TestSleep testSleep = new TestSleep();        testSleep.timeCount(10);        testSleep.timePrinter();    }}
  • 礼让线程,让当前正在执行的线程暂停,但不阻塞将线程
  • 从运行状态转为就绪状态
  • 让cpu重新调度,礼让不一定成功!看CPU心情

java

public class TestYield {    public static void main(String[] args) {        Runnable runnable = ()->{            System.out.println(Thread.currentThread().getName() + " 线程开始执行");            Thread.yield();            System.out.println(Thread.currentThread().getName() + " 线程停止");        };        new Thread(runnable,"线程A").start();        new Thread(runnable,"线程B").start();    }}

PS:我礼让就没成功过。。电脑的原因吗

  • join()合并线程,待此线程执行完之后,再执行其他线程,其他线程阻塞
  • 可以想象成插队

java

public class TestJoin implements Runnable {    @Override    public void run() {        for (int i = 0; i < 5000; i++) {            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("Join线程-------------" + i);        }    }    public static void main(String[] args) throws InterruptedException {        TestJoin testJoin = new TestJoin();        Thread thread = new Thread(testJoin);        thread.start();        for (int i = 0; i < 5000; i++) {            // 当主线程 200 的时候,让thread插队            if(i == 1000){                thread.join();            }            System.out.println("Main线程 + " + i);        }    }}

JDK1.8文档:
public static enum Thread.State extends Enum

  • NEW 尚未启动的线程处于此状态。
  • RUNNABLE 在Java虚拟机中 执行的线程处于此状态。
  • BLOCKED 被 阻塞等待监视器锁定的线程处于此状态。
  • WAITING 正在 等待另一个线程执行特定动作的线程处于此状态。
  • TIMED_WAITING 正在 等待另一个线程执行动作达到指定等待时间的线程处于此状态。
  • TERMINATED 已退出的线程处于此状态。 一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。

thread1.getState():得到 thread1 的状态,上面的那几个值

java

public class TestState {    public static void main(String[] args) throws InterruptedException {        Thread thread = new Thread(()->{            for (int i = 0; i < 5; i++) {                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    e.printStackTrace();                }                System.out.println("///////");            }        });        Thread.State state = thread.getState();        System.out.println("start之前: " + state);        thread.start();        state = thread.getState();        System.out.println("start之后:" + state);        while (state != Thread.State.TERMINATED){            // 这里一定要更新            state = thread.getState();            System.out.println(state);            Thread.yield();            Thread.sleep(1000);        }        state = thread.getState();        System.out.println("线程死了!--" + state);                // 线程是一次性用品,死亡后不能再次启动        thread.start();    }}
  • Java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度哪个线程来执行。
  • 线程的优先级用数字表示,范围从1~10. Thread.MIN_PRIORITY = 1; Thread.MAX_PRIORITY = 10; Thread.NORM_PRIORITY = 5;
  • 使用以下方式改变或获取优先级
  • getPriority():获取优先级
  • * setPriority(int xxx): 设置优先级

线程执不执行还是得看CPU,优先级高的不一定先执行。但是优先级高的权重大,更可能先执行。

  • 性能倒置:优先级低只是意味着获得调度的概率低,并不是优先级低就不会被调用了,这都是看CPU的调度

java

public class TestPriority {    public static void main(String[] args) {        System.out.println(Thread.currentThread().getName() + " --> " + Thread.currentThread().getPriority() );        MyPriority myPriority = new MyPriority();        Thread t1 = new Thread(myPriority,"t1");        Thread t2 = new Thread(myPriority,"t2");        Thread t3 = new Thread(myPriority,"t3");        Thread t4 = new Thread(myPriority,"t4");        Thread t5 = new Thread(myPriority,"t5");        t1.setPriority(1);        t2.setPriority(2);        t3.setPriority(3);        t4.setPriority(4);        t5.setPriority(Thread.MAX_PRIORITY);        t4.start();        t2.start();        t1.start();        t3.start();        t5.start();    }}class MyPriority implements Runnable{    @Override    public void run() {        System.out.println(Thread.currentThread().getName() + " --> " + Thread.currentThread().getPriority() );    }}
  • 线程分为用户线程和守护线程
  • 虚拟机必须确保用户线程执行完毕:如 main线程
  • 虚拟机不用等待守护线程执行完毕:如后台记录操作日志、监控内存、垃圾回收GC线程等..

thread1.setDaemon(true):将线程 thread1 设为守护线程

java

public class TestDaemon {    public static void main(String[] args) {        Bless bless = new Bless();        NormalT normalT = new NormalT();        Thread thread = new Thread(bless);        // 默认false表示为正常线程,true表示设为守护线程        thread.setDaemon(true);        Thread thread1 = new Thread(normalT);        thread1.start();        thread.start();        // 可以看到,主线程和Normal线程结束之后,程序结束。守护线程死循环也终止了    }}class Bless implements Runnable{    @Override    public void run() {        while (true){            System.out.println("God Bless You");        }    }}class NormalT implements Runnable{    @Override    public void run() {        for (int i = 0; i < 1000; i++) {            System.out.println("此线程存活");        }        System.out.println("此线程死亡");    }}

4. 线程同步(重点)

并发:同一个对象被多个线程 同时操作

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改这个对象。这时候我们就需要线程同步。

线程同步其实就是一种 等待机制,多个需要同时访问此对象的线程进入 这个对象的等待池形成队列,等待前面线程使用完毕,下一个线程再使用。

  • 队列 + 锁,解决线程同步的安全性。

synchronized,排他锁独占资源。

  • 使用锁可能会引起问题:
  • 一个线程持有锁会导致其他所有需要此锁的 线程挂起;
  • 在多线程竞争下,加锁﹐释放锁会导致比较多的上下文切换和调度延时,引起 性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致 优先级倒置﹐引起性能问题.

产生死锁的四个必要条件:1.互斥条件:一个资源每次只能被一个进程使用。2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。3.不剥夺条件:进程已获得的资源,在末使用完之前,不能强行剥夺。4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

上面列出了死锁的四个必要条件,我们只要想办法破其中的任意一个或多个条件就可以避免死锁发生

写个死锁案例:

java

public class DeadLock {    public static void main(String[] args) {        Integer i1 = 10;        Integer i2 = 20;        Thread thread = new Thread(new MyThread(i1, i2, 0), "线程1");        Thread thread2 = new Thread(new MyThread(i1, i2, 1), "线程2");        thread.start();        thread2.start();    }}class MyThread implements Runnable {    private Integer i1;    private Integer i2;    private Integer choice;    public MyThread(Integer i1, Integer i2, Integer choice) {        this.i1 = i1;        this.i2 = i2;        this.choice = choice;    }    @Override    public void run() {        // 用一个choice,分别先拿不同资源        if (choice == 0) {            // 先拿1,再拿2            synchronized (i1) {                System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1);                // 将两个这个拿到外层,可以解决死锁                synchronized (i2) {                    System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2);                }            }        } else {            // 先拿2,再拿1            synchronized (i2) {                System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2);                synchronized (i1) {                    System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1);                }            }        }    }}

将最后一段的代码改为:

java

@Overridepublic void run() {    // 用一个choice,分别先拿不同资源    if (choice == 0) {        // 先拿1,再拿2        synchronized (i1) {            System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1);        }        synchronized (i2) {            System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2);        }    } else {        // 先拿2,再拿1        synchronized (i2) {            System.out.println(Thread.currentThread().getName() + "得到了i2: " + i2);        }       synchronized (i1) {            System.out.println(Thread.currentThread().getName() + "得到了i1: " + i1);        }    }}

这样就能避免死锁了。为啥呢?我自己理解的,是锁升级到重量级锁了,阻塞了其中的一个线程。

JUC,就是 import java.util.concurrent

java

public class TestLock {    public static void main(String[] args) {        TestLock2 testLock2 = new TestLock2();        new Thread(testLock2,"小明").start();        new Thread(testLock2,"小二").start();        new Thread(testLock2,"小王").start();    }}class TestLock2 implements Runnable{    private final ReentrantLock reentrantLock = new ReentrantLock();    int tirckNums = 10;    @Override    public void run() {        while (true){            // 加锁            reentrantLock.lock();            try {                if (tirckNums > 0){                    try {                        Thread.sleep(500);                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                    System.out.println(Thread.currentThread().getName()+"-->"+tirckNums--);                }else {                    break;                }            }finally {                // 解锁                reentrantLock.unlock();            }        }    }}

手动锁,自己调用 lockunlock,锁代码块。

  • Lock是 显式锁(手动开启和关闭锁,别忘记关闭锁) ;synchronized是 隐式锁,出了作用域自动释放
  • Lock只有 代码块锁,synchronized有 代码块锁和方法锁
  • 使用Lock锁,JVM将花费较少的时间来调度线程, 性能更好。并且具有更好的扩展性(提供更多的子类)
  • 优先使用顺序:Lock >同步代码块(已经进入了方法体,分配了相应资源)>同步方法(在方法体之外)

5. 线程通信问题

可以观看:

生产者消费者问题

问题分析

Java方法

java

public class TestPC {
public static void main(String[] args) {
SynContainer synContainer = new SynContainer();

Producer producer = new Producer(synContainer);
Consumer consumer =new Consumer(synContainer);

producer.start();
consumer.start();
}
}

/**
* 生产者
**/
class Producer extends Thread {

SynContainer synContainer;

public Producer(SynContainer synContainer) {
this.synContainer = synContainer;
}

@Override
public void run() {
// 生产
for (int i = 0; i < 100; i++) {
synContainer.push(new Production(i));
System.out.println("生产者生产了--> "+i);
}
}
}

/**
* 消费者
**/
class Consumer extends Thread {
SynContainer synContainer;

public Consumer(SynContainer synContainer) {
this.synContainer = synContainer;
}
@Override
public void run() {
for (int i = 0; i < 100; i++) {
int id = synContainer.pop().id;
System.out.println("消费者消费了--> " + id);
}

}
}

/**
* 产品
**/
class Production {
// 产品编号
int id;

public Production(int id) {
this.id = id;
}
}

/**
* 缓冲区
**/
class SynContainer {
// 缓冲区容器,总容量
Production[] productions = new Production[10];
// 当前存在的容量
int count = 0;

//生产者放入产品
public synchronized void push(Production production) {
// 缓冲区已满
if (count == productions.length) {
// 缓冲区已满,生产者停止生产;
// 通知消费者,进行消费
try{
this.wait();
}catch (Exception e){
e.printStackTrace();
}
}
// 放入
productions[count] = production;
count++;
// 生产出来了,通知消费者,可以立即消费
this.notifyAll();
}

// 消费者取走产品
public synchronized Production pop() {
if (count == 0){
// 通知生产者生产
try{
this.wait();
}catch (InterruptedException e){
e.printStackTrace();
}
}

// 取出
count--;
Production production = productions[count];

// 取出之后,缓冲区有空格了
this.notifyAll();

return production;

}
}

Java 采用的是管程技术,synchronized 关键字及 wait()、notify()、notifyAll() 这三个方法都是管程的组成部分。而 管程和信号量是等价的,所谓等价指的是用管程能够实现信号量,也能用信号量实现管程。但是 管程利用OOP的封装特性解决了信号量在工程实践上的复杂性问题,因此java采用管理机制。
所谓 管程,指的是管理共享变量以及对其操作过程,让它们支持并发访问。翻译为 Java 领域的语言,就是管理类的成员变量和成员方法,让这个类是线程安全的。
有一点需要再次提醒,对于 MESA 管程来说,有一个编程范式,就是==需要在一个 while 循环里面调用 wait()==。 这个是 MESA 管程特有的

锁和 信号量(Semaphore)是实现多线程同步的两种常用的手段。

信号量需要初始化一个许可值,许可值可以大于0,也可以小于0,也可以等于0.

  • 如果大于0,表示,还有许可证可以发放,线程不会被阻塞;
  • 如果小于或者等于0,表示,没有许可证可以发放了,线程被阻塞住了。

它有两个常用的操作:

  • acquire()申请许可证,如果有,就可以获得,如果没有就等待了。相当于减法。
  • release()归还许可证,保证循环使用。相当于加法。

看一个例子,就会明白了,还是实现上次的那个生产者和消费者的例子。

java

package com.songx64.baselearn.threadlearn.kuangThread.gaoji;

import java.util.LinkedList;
import java.util.concurrent.Semaphore;

/**
* Created on 2021/6/26,上午 11:03
* 信号量机制 Semaphore
*
* @author SongX64
*/
public class TestPC2 {

public static void main(String[] args) {
SynContainer2 synContainer2 = new SynContainer2();
Thread p1 = new Producer2(synContainer2);
Thread c1 = new Consumer2(synContainer2);
Thread c2 = new Consumer2(synContainer2);

p1.start();
c1.start();
c2.start();
}
}


/**
* 产品
**/
class Production2 {
public int id;

public Production2(int id) {
this.id = id;
}
}

/**
* 生产者
**/
class Producer2 extends Thread {
SynContainer2 synContainer2;

public Producer2(SynContainer2 synContainer2) {
this.synContainer2 = synContainer2;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
synContainer2.push(new Production2(i));
System.out.println("生产了-->" + i);
}
}
}

class Consumer2 extends Thread {
SynContainer2 synContainer2;

public Consumer2(SynContainer2 synContainer2) {
this.synContainer2 = synContainer2;
}

@Override
public void run() {
for (int i = 0; i < 20; i++) {
int id = synContainer2.pop().id;
System.out.println(Thread.currentThread().getName()+"消费了 }
}
}

/**
* 缓冲区
**/
class SynContainer2 {
// 容器
LinkedList production2s = new LinkedList<>();
// 互斥信号量,保证安全性
Semaphore mutex = new Semaphore(1);
// 为满信号量,初始是最大容量
Semaphore isFull = new Semaphore(10);
// 为空信号量
Semaphore isEmpty = new Semaphore(0);


// 生产者生产,为满阻塞
public void push(Production2 production2) {
try {
//大于0,就放行
//acquire,就是减操作,如果 //release,就是加操作,如果 >0,就不会被阻塞
isFull.acquire();
// 添加操作是互斥的,因为占用了缓冲区
mutex.acquire();
int i = isFull.availablePermits();
production2s.add(production2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
// 释放互斥锁
mutex.release();
// 生产完成后,让空的增加,可以进行消费
isEmpty.release();
}
}

// 消费者消费,为空阻塞
public Production2 pop() {
Production2 temp = null;
// 为空减少一个信号量,如果是空的0再减就阻塞了;

try {
isEmpty.acquire();
mutex.acquire();
temp = production2s.removeLast();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mutex.release();
isFull.release();
}
return temp;
}
}

写的有点乱。。。但是知道Semaphore的用法就行了

6.高级主题

  • 背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对 性能影响很大。
  • 思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
  • 好处:
  • 提高响应速度(减少了创建新线程的时间)
  • 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
  • 便于线程管理(….)
    • corePoolSize:核心池的大小
    • maximumPoolSize:最大线程数
    • keepAliveTime:线程没有任务时最多保持多长时间后会终止

Java线程池类

简单使用:

java

public class TestPool {    public static void main(String[] args) {        // 1.1 手动创建线程池(建议),指定参数        // 参数:核心线程数,最大线程数,核心线程外的工作线程存活时间,时间单位,阻塞队列        ThreadPoolExecutor executorService = new ThreadPoolExecutor(                10,                20,                100,                TimeUnit.SECONDS,                new LinkedBlockingDeque<>());        // 1.2 自动创建线程池(不建议),10个核心线程/最大线程        //ExecutorService executorService = Executors.newFixedThreadPool(10);        executorService.execute(new MyThread());        executorService.execute(new MyThread());        executorService.execute(new MyThread());        executorService.execute(new MyThread());    }}class MyThread implements Runnable{    @Override    public void run() {        System.out.println(Thread.currentThread().getName());    }}

Original: https://www.cnblogs.com/wscsdn/p/15930994.html
Author: 扬帆起航$
Title: 多线程学习

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/580553/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 聊聊消息队列高性能的秘密——零拷贝技术

    前言 RocketMQ为什么这么快、Kafka为什么这么快?用了零拷贝技术?什么是零拷贝技术,它们二者的零拷贝技术有不同吗? 为什么需要零拷贝 在计算机产业中,I/O的速度相较CP…

    Java 2023年6月6日
    0100
  • 想入门数据结构,却总是拜倒在链表的石榴裙下?

    大家好,我是melo,一名大二上软件工程在读生,经历了一年的摸滚,现在已经在工作室里边准备开发后台项目啦不过这篇文章呢,还是想跟大家聊一聊数据结构与算法,学校也是大二上才开设了数据…

    Java 2023年6月5日
    087
  • session和cookie的区别

    一·概念理解 首先呢,要了解session和cookie的区别先要了解以下几个概念: 1、 无状态的HTTP协议: 协议,是指计算机通信网络中两台计算机之间进行通信所必须共同遵守的…

    Java 2023年6月14日
    062
  • nginx入门教程

    nginx入门教程 一.概述 什么是nginx? Nginx (engine x) 是一款轻量级的Web 服务器 、反向代理服务器及电子邮件(IMAP/POP3)代理服务器。 什么…

    Java 2023年5月30日
    084
  • 十、包机制与JavaDoc

    一、包机制 为了更好的组织类,Java提供了包机制,用于区别类名的命名空间。包语句的语句格式为: package pkg1[. pkg2[. pkg3…]]; 一般使用公司域名…

    Java 2023年6月5日
    072
  • IDEA Error:java: 无效的源发行版: 11错误

    IDEA Error:java: 无效的源发行版: 11错误 今天在网上下载了一个项目到本地运行报错 Error: Java : 无效的源发行版: 11 ,上网查了很多找到问题所在…

    Java 2023年6月5日
    096
  • java中的泛型(自定义泛型)

    引出问题 当我们在集合中加入数据的时候想要对数据进行约束。比如:就想传入自己创建的对象 遍历的时候,需要进行类型转换,如果结合中的数据量较大,对效率有影响 * java;gutte…

    Java 2023年6月6日
    072
  • rocketmq 搭建配置

    broker组1: NameServer地址 namesrvAddr=192.168.1.100: 9876;192.168.1.101: 9876 集群名称 brokerClus…

    Java 2023年5月30日
    059
  • java.lang.OutOfMemoryError: PermGen space及其解决方法

    今天换了个tomcat6.0.51的时候,两个项目同时debug启动,就报这个错误了:java.lang.OutOfMemoryError: PermGen space 网上找了下…

    Java 2023年5月29日
    060
  • 我的第一个博客

    404. 抱歉,您访问的资源不存在。 可能是网址有误,或者对应的内容被删除,或者处于私有状态。 代码改变世界,联系邮箱 contact@cnblogs.com 园子的商业化努力-困…

    Java 2023年6月9日
    067
  • WPF 静态资源(StaticResource)和动态资源(DynamicResource)

    简单的可以理解为,如果换皮肤而不重启程序,就需要用 DynamicResource <Window x:Class="WpfApp1.MainWindow&quot…

    Java 2023年6月14日
    052
  • Java抓取网页图片并下载到本地(HTTP)

    直接上代码: package com.clzhang.sample.net; import java.io.File; import java.io.FileOutputStrea…

    Java 2023年5月29日
    073
  • Spring PathMatchingResourcePatternResolver

    Spring PathMatchingResourcePatternResolver PathMatchingResourcePatternResolver是ResourcePat…

    Java 2023年6月7日
    079
  • 第一个微信小项目

    第一个好友分析: 我们需要用到wxpy这个库,这个库用到时会弹出一个二维码,这个二维码是通过扫码的方式登录微信,以获取信息 1 #导入模块 2 from wxpy import *…

    Java 2023年6月6日
    063
  • idea永久激活教程(新版)

    第一步 下载新版idea安装包 idea2022.x。下载方式(推荐):访问idea官网选择idea2022旗舰版本进行下载即可,不要选择community版本哦(communit…

    Java 2023年6月9日
    0114
  • SpringBoot进阶教程(七十)SkyWalking

    流行的APM(Application Performance Management工具有很多,比如Cat、Zipkin、Pinpoint、SkyWalking。优秀的监控工具还有很…

    Java 2023年6月8日
    066
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球