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

java多线程开发

1.并发和并行

并发:同一时间段内多个任务同时进行。

并行:同一时间点多个任务同时进行。

2.进程线程

进程(Process):进程是程序的一次动态执行过程,它经历了从代码加载、执行、到执行完毕的一个完整过程;同时也是并发执行的程序在执行过程中分配和管理资源的基本单位,竞争计算机系统资源的基本单位。

线程(Thread):线程可以理解为进程中的执行的一段程序片段,是进程的一个执行单元,是进程内可调度实体,是比进程更小的独立运行的基本单位,线程也被称为轻量级进程。

3.实现多线程的几种方式

(1)继承thread类

示例:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/4 9:43*/class MyThread1 extends Thread {@Overridepublic void run() {//线程的执行体for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}public class Demo1 {public static void main(String[] args) {//程序的入口//step2:创建子类对象MyThread1 myThread = new MyThread1();//step3:调用start()方法//myThread.run();//这样写,是普通的方法的调用myThread.start();//相当于开启一个新的线程,这个新的线程跟main线程是平级的for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() +":" + i);}}
}

(2)实现runnable接口

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/5 8:42*/class myRunnable1 implements Runnable{@Overridepublic void run() {//线程的执行体for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}}
}public class Demo2 {public static void main(String[] args) {//创建实现类的对象myRunnable1 myRunnable1 = new myRunnable1();Thread thread = new Thread(myRunnable1);//启动一个线程thread.start();//main线程执行for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() +":" + i);}}
}

 继承Thread 和实现Runnable的区别
如果一个类继承Thread,则不适合资源共享,但是如果实现了Runable接口的话,则很容易的实现资源共享,总结:实现Runnable接口比继承Thread类所具有的优势

  • 适合多个相同的程序代码的线程去共享同一个资源
  • 可以避免java中的单继承的局限性。
  • 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。

(3)实现callable接口

 只有一个call方法,带有返回值。返回值是一个泛型格式的。

示例:

package com.example.demo.thread;import java.util.concurrent.Callable;/*** @author linaibo* @version 1.0* Create by 2023/2/3 16:17*/public class Mycallable implements Callable<Integer> {private String name ;public Mycallable(String name) {this.name = name;}@Overridepublic Integer call() throws Exception {Integer sum = 0;for (int i = 0; i < 20; i++) {sum += i;System.out.println(name + "开始执行" + i);}return sum;}
}

测试类

    @Testpublic void test() throws ExecutionException, InterruptedException {Mycallable name1 = new Mycallable("name1");Mycallable name2 = new Mycallable("name2");Mycallable name3 = new Mycallable("name3");FutureTask<Integer> task1 = new FutureTask<Integer>(name1);FutureTask<Integer> task2 = new FutureTask<Integer>(name2);FutureTask<Integer> task3 = new FutureTask<Integer>(name3);new Thread(task1).start();new Thread(task2).start();new Thread(task3).start();System.out.println(task1.get());System.out.println(task2.get());System.out.println(task3.get());}

结果:

name2开始执行0
name2开始执行1
name2开始执行2
name1开始执行0
name1开始执行1
name1开始执行2
name1开始执行3
name1开始执行4
name1开始执行5
name1开始执行6
name1开始执行7
name1开始执行8
name1开始执行9
name1开始执行10
name1开始执行11
name1开始执行12
name1开始执行13
name1开始执行14
name1开始执行15
name2开始执行3
name2开始执行4
name2开始执行5
name2开始执行6
name2开始执行7
name2开始执行8
name2开始执行9
name2开始执行10
name2开始执行11
name2开始执行12
name2开始执行13
name2开始执行14
name2开始执行15
name2开始执行16
name2开始执行17
name2开始执行18
name2开始执行19
name3开始执行0
name1开始执行16
name1开始执行17
name1开始执行18
name1开始执行19
name3开始执行1
name3开始执行2
name3开始执行3
190
190
name3开始执行4
name3开始执行5
name3开始执行6
name3开始执行7
name3开始执行8
name3开始执行9
name3开始执行10
name3开始执行11
name3开始执行12
name3开始执行13
name3开始执行14
name3开始执行15
name3开始执行16
name3开始执行17
name3开始执行18
name3开始执行19
190

3.线程的常用方法

(1).线程的优先级

我们可以通过传递参数给线程的 setPriority()来设置线程的优先级别
调整线程优先级: Java线程有优先级,优先级高的线程会获得较多的运行机会。优先级:只能反映 线程 的 中或者是 紧急程度,不能决定是否一定先执行
Java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:

示例:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/9 12:20*/class Thread100 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}class Thread101 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}class Thread102 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}public class Thread11 {public static void main(String[] args) {Thread100 t1 = new Thread100();Thread101 t2 = new Thread101();Thread102 t3 = new Thread102();Thread th1 = new Thread(t1, "线程1");Thread th2 = new Thread(t2, "线程2");Thread th3 = new Thread(t3, "线程3");th1.setPriority(Thread.MAX_PRIORITY);th2.setPriority(Thread.MIN_PRIORITY);th3.setPriority(Thread.NORM_PRIORITY);th1.start();th2.start();th3.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}}

 结果:

main开始执行0
线程1开始执行0
main开始执行1
线程1开始执行1
main开始执行2
线程1开始执行2
main开始执行3
线程1开始执行3
main开始执行4
线程1开始执行4
线程1开始执行5
线程1开始执行6
线程1开始执行7
线程1开始执行8
线程1开始执行9
线程3开始执行0
线程3开始执行1
线程3开始执行2
线程3开始执行3
线程3开始执行4
线程3开始执行5
线程3开始执行6
线程3开始执行7
线程3开始执行8
线程3开始执行9
线程2开始执行0
线程2开始执行1
线程2开始执行2
线程2开始执行3
线程2开始执行4
线程2开始执行5
线程2开始执行6
线程2开始执行7
线程2开始执行8
线程2开始执行9

(2).线程的休眠

使用线程的sleep()方法让线程休眠指定的毫秒数,在休眠结束的时候继续执行线程。

示例1:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/4 9:43*/class MyThread1 extends Thread {private static Integer target = 12;public MyThread1(String name) {super(name);}@Overridepublic void run() {//线程的执行体for (int i = 0; i < 4; i++) {
//            System.out.println(Thread.currentThread().getName() + ":" + i);System.out.println("卖了一张票,还剩下的票数:" + target--);}}
}public class Demo1 {public static void main(String[] args){//程序的入口//step2:创建子类对象MyThread1 myThread1 = new MyThread1("线程一");MyThread1 myThread2 = new MyThread1("线程二");MyThread1 myThread3 = new MyThread1("线程三");//step3:调用start()方法//myThread.run();//这样写,是普通的方法的调用myThread1.start();//相当于开启一个新的线程,这个新的线程跟main线程是平级的try {Thread.sleep(1000*2);} catch (InterruptedException e) {e.printStackTrace();}myThread2.start();myThread3.start();for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() +":" + i);}}
}

 myThread1线程执行完成之后,进行休眠两秒的操作,其他的线程myThread2和myThread3以及main线程等待两秒之后才会执行。

示例2:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/9 12:20*/class Thread100 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}class Thread101 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {if (i == 2){try {System.out.println(Thread.currentThread().getName() + "开始休眠" + "------------------");Thread.sleep(1000*5);} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}class Thread102 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}public class Thread11 {public static void main(String[] args) {Thread100 t1 = new Thread100();Thread101 t2 = new Thread101();Thread102 t3 = new Thread102();new Thread(t1, "线程1").start();new Thread(t2, "线程2").start();new Thread(t3, "线程3").start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}}

结果:

main开始执行0
main开始执行1
main开始执行2
main开始执行3
main开始执行4
线程3开始执行0
线程3开始执行1
线程3开始执行2
线程3开始执行3
线程3开始执行4
线程1开始执行0
线程1开始执行1
线程1开始执行2
线程1开始执行3
线程1开始执行4
线程2开始执行0
线程2开始执行1
线程2开始休眠------------------
线程2开始执行2
线程2开始执行3
线程2开始执行4

 代码中创建了3个线程,在加上主线程,线程2中执行休眠方法,让线程2休眠,其他线程继续执行。休眠时间结束后,线程2继续执行。

(3)线程的让步

Thread.yield()方法作用是: 暂停当前正在执行的线程对象(及放弃当前拥有的up资源),并执行其他线程
yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先的其他线程获得运行机会,因此,使用 yled() 的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证 yield() 达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。案例:创建两个线程A,B,分别各打印1000次从1开始每次增加1,其中B一个线程,每打印一次就yied一次,观察实验结果。

示例:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/9 12:20*/class Thread100 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);Thread.yield();}}
}class Thread101 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}class Thread102 implements Runnable {@Overridepublic void run() {for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}public class Thread11 {public static void main(String[] args) {Thread100 t1 = new Thread100();Thread101 t2 = new Thread101();Thread102 t3 = new Thread102();Thread th1 = new Thread(t1, "线程1");Thread th2 = new Thread(t2, "线程2");Thread th3 = new Thread(t3, "线程3");//设置线程的执行优先级th1.setPriority(Thread.MAX_PRIORITY);th2.setPriority(Thread.MAX_PRIORITY);th3.setPriority(Thread.NORM_PRIORITY);th1.start();th2.start();th3.start();for (int i = 0; i < 5; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}}

结果:

main开始执行0
main开始执行1
main开始执行2
main开始执行3
main开始执行4
线程1开始执行0
线程2开始执行0
线程2开始执行1
线程2开始执行2
线程2开始执行3
线程2开始执行4
线程2开始执行5
线程2开始执行6
线程2开始执行7
线程2开始执行8
线程2开始执行9
线程1开始执行1
线程1开始执行2
线程1开始执行3
线程3开始执行0
线程1开始执行4
线程3开始执行1
线程3开始执行2
线程3开始执行3
线程3开始执行4
线程3开始执行5
线程3开始执行6
线程3开始执行7
线程3开始执行8
线程3开始执行9
线程1开始执行5
线程1开始执行6
线程1开始执行7
线程1开始执行8
线程1开始执行9

sleep()和yield()的区别
sleep()使当前线程进入停滞状态,所以执行sleep()的线程在指定的时间内肯定不会被执行:yield()只是使当前线程重新回到可执行状态,所以执行yield()的线程有可能在进入到可执行状态后马上又被执行。

sleep 方法使当前运行中的线程睡眼一段时间,进入不可运行状态,这段时间的长短是由程序设定的,yield 方法使当前线程让出CPU 占有权,但让出的时间是不设定的。实际上,yield()方法对应了如下操作:先检测当前是否有相同优先级的线程处于同可运行状态,如有,则把PU 的占有权交给此线程,否则,继续运行原来的线程。所以yield()方法称为“退让”,它把运行机会让给了同等优先级的其他线程另外,sleep 方法允许较低优先级的线程获得运行机会,但 yield 方法执行时,当前线程仍处在可运行状态,所以,不可能让出较低优先级的线程些时获得CPU占有权。在一个运行系统中,如果较高优先级的线程没有调用 slee 方法,又没有受到IO 阻塞,那么,较低优先级线程只能等待所有较高优先级的线程运行结束,才有机会运行。

(4).线程的合并

Thread 中,join()方法的作用是调用线程等待该线程完成后,才能继续往下运行。join是Thread类的一个方法,启动线程后直接调用,join的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止,也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
为什么要用ioin()方法
在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join方法了。

示例:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/5 8:42*/class myRunnable2 implements Runnable {private Integer target = 4;@Overridepublic void run() {//线程的执行体for (int i = 0; i < 4; i++) {
//            System.out.println(Thread.currentThread().getName() + ":" + i);System.out.println("卖了一张票,还剩下的票数:" + --target);}}
}public class Demo3 {public static void main(String[] args) {System.out.println("主线程执行开始");//创建实现类的对象myRunnable2 myRunnable1 = new myRunnable2();Thread thread1 = new Thread(myRunnable1, "线程1");//启动一个线程thread1.start();try {thread1.join();} catch (InterruptedException e) {e.printStackTrace();}//main线程执行for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + ":" + i);}System.out.println("主线程执行结束");}
}

 结果:

主线程执行开始
卖了一张票,还剩下的票数:3
卖了一张票,还剩下的票数:2
卖了一张票,还剩下的票数:1
卖了一张票,还剩下的票数:0
main:0
main:1
main:2
main:3
main:4
main:5
main:6
main:7
main:8
main:9
主线程执行结束

(5)守护线程

守护线程setDaemon(true):设置守护线程
线程有两类: 用户线程(前台线程) 、守护线程 (后台线程)如果程序中所有前台线程都执行完毕了,后台线程会自动结束。结束垃圾回收器线程属于守护线程

示例1:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/11 15:09*/class Deamon1 implements Runnable{@Overridepublic void run() {for (int i = 0; i < 100; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}public class ThreadDeamon {public static void main(String[] args) {Deamon1 deamon1 = new Deamon1();Thread th1 = new Thread(deamon1, "线程1");th1.setDaemon(true);//设置为守护线程th1.start();for (int i = 0; i < 10; i++) {System.out.println(Thread.currentThread().getName() + "开始执行" + i);}}
}

结果:

main开始执行0
main开始执行1
main开始执行2
main开始执行3
main开始执行4
main开始执行5
main开始执行6
main开始执行7
main开始执行8
main开始执行9
线程1开始执行0
线程1开始执行1
线程1开始执行2
线程1开始执行3
线程1开始执行4
线程1开始执行5
线程1开始执行6
线程1开始执行7
线程1开始执行8
线程1开始执行9

主线程执行完成之后,即使线程1还没有执行完,也会结束执行。 

4.线程的生命周期

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。
(1)新建状态 (New)
当线程对象对创建后,即进入了新建状态,如: Thread t = new MyThread();
(2)就绪状态(Runnable)
当调用线程对象的start方法(t.start),线程进入就绪状态,处于就状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start(),此线程立即就会执行;
(3)运行状态(Running)

当CP开始调度处于就绪状态的线程时,此线程才得以真正执行,即进入到运行状态。就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中。

(4)阻塞状态

 处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态,根据阻塞产生的原因不同,阻塞状态又可以分为三种:
1.等待阻塞:运行状态中的线程执行wait0方法,使本线程进入到等待阻塞状态;
2.同步阻塞-- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
3其他阳塞 通过调用线程的sleep或join发出了I/O请求时,线程会进入到塞状态,当sleep状态超时、join等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
入就绪状态。

(5)死亡状态

线程执行完了或者因为异常退出了run()方法,该线程结束生命周期。

多线程状态之间的切换

就绪状态转换为运行状态:当此线程得到处理器资源。
运行状态转换为就绪状态:当此线程主动调用yield方法或在运行过程中失去处理器资源
运行状态转换为死亡状态:当此线程线程执行体执行完毕或发生了异常。
此处需要特别注意的暴:当调用线程的yielid方法时,线程从运行状态转换为就绪状态,但接下来CPU调度就绿状态中的个线程具有一定的随机性,因此,可能会出现A线程调用了yield方法后,接下来CPU仍然调度了A线程的情况。

5.线程安全问题

当多线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
临界资源:共享资源(同一对象),一次仅允许一个线程使用,才可保证其正确性原子操作: 不可分割的多步操作,被视作一个整体,其顺序和步骤不可打乱或缺省。
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

6.确保线程安全的方法

(1)同步代码块

对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

1.锁对象 可以是任意类型。
2.多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁 谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

不加锁的示例:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/7 11:51*/class TicketDemo implements Runnable{public int tikcet = 10;Object lock = new Object();@Overridepublic void run() {while (true) {
//            synchronized (lock) {if (tikcet > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + tikcet-- + "张票");}}
//        }}
}public class Ticket {public static void main(String[] args) {TicketDemo ticketDemo = new TicketDemo();new Thread(ticketDemo).start();new Thread(ticketDemo).start();new Thread(ticketDemo).start();}}

运行结果:

Thread-2卖了一张票,还剩下10张票
Thread-1卖了一张票,还剩下9张票
Thread-0卖了一张票,还剩下10张票
Thread-0卖了一张票,还剩下8张票
Thread-2卖了一张票,还剩下8张票
Thread-1卖了一张票,还剩下8张票
Thread-1卖了一张票,还剩下6张票
Thread-2卖了一张票,还剩下7张票
Thread-0卖了一张票,还剩下7张票
Thread-0卖了一张票,还剩下5张票
Thread-1卖了一张票,还剩下3张票
Thread-2卖了一张票,还剩下4张票
Thread-0卖了一张票,还剩下2张票
Thread-2卖了一张票,还剩下2张票
Thread-1卖了一张票,还剩下2张票
Thread-1卖了一张票,还剩下1张票
Thread-2卖了一张票,还剩下1张票
Thread-0卖了一张票,还剩下1张票

 会出现重复卖票的情况。

加锁的示例:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/7 11:51*/class TicketDemo implements Runnable {public int tikcet = 10;Object lock = new Object();@Overridepublic void run() {while (true) {synchronized (lock) {if (tikcet > 0) {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + tikcet-- + "张票");}}}}
}public class Ticket {public static void main(String[] args) {TicketDemo ticketDemo = new TicketDemo();new Thread(ticketDemo).start();new Thread(ticketDemo).start();new Thread(ticketDemo).start();}}

结果:

Thread-0卖了一张票,还剩下10张票
Thread-0卖了一张票,还剩下9张票
Thread-0卖了一张票,还剩下8张票
Thread-0卖了一张票,还剩下7张票
Thread-0卖了一张票,还剩下6张票
Thread-0卖了一张票,还剩下5张票
Thread-0卖了一张票,还剩下4张票
Thread-0卖了一张票,还剩下3张票
Thread-0卖了一张票,还剩下2张票
Thread-0卖了一张票,还剩下1张票

结果之后同步代码块,每次只能有一个线程操作此代码。保证了线程安全。 

(2)同步方法

使用synchronized修饰的方法,就叫做同步方法,保证A线程执行该方法的时候,其他线程只能在方法外等着。

语法:

只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。
线程退出同步方法时,会释放相应的互斥锁标记。
如果方式是静态,锁是类名.class。

示例:

锁相当于this。

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/7 12:11*/class Ticket2Demo implements Runnable {public Integer ticket = 10;Object lock = new Object();@Overridepublic void run() {maipiao();}public synchronized void maipiao() {while (true) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + ticket-- + "张票");}}}
}public class Ticket2 {public static void main(String[] args) {Ticket2Demo ticket2Demo = new Ticket2Demo();new Thread(ticket2Demo).start();new Thread(ticket2Demo).start();new Thread(ticket2Demo).start();}}

也可以使用静态方法,锁相当于当前类名。

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/7 12:11*/class Ticket2Demo implements Runnable {public static Integer ticket = 10;Object lock = new Object();@Overridepublic void run() {maipiao();}public synchronized static void maipiao() {while (true) {if (ticket > 0) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName() + "卖了一张票,还剩下" + ticket-- + "张票");}}}
}public class Ticket2 {public static void main(String[] args) {Ticket2Demo ticket2Demo = new Ticket2Demo();new Thread(ticket2Demo).start();new Thread(ticket2Demo).start();new Thread(ticket2Demo).start();}}

synchronized注意点
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法我们使用当前方法所在类的字节码对象(类名.class)。

(3)lock

当两个线程同时执行add方法,造成线程安全问题,可以通过创建一个lock的方式,在方法的开始加锁,方法执行完之后解锁。

package com.example.demo.thread;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;/*** @author linaibo* @version 1.0* Create by 2023/2/8 12:06*/public class MyThread2 implements Runnable{private int ticket = 10;private final Lock lock = new ReentrantLock();@Overridepublic void run() {while(this.sale()) {}}public boolean sale() {lock.lock();try{if(ticket == 0) {System.out.println(Thread.currentThread().getName() + "的票已经全部售完,此时的票数量为:"+ticket);return false;}Thread.sleep(200);System.out.println(Thread.currentThread().getName()  + " 正在售票,还剩余票数为:" + ticket--);}catch (Exception e) {e.printStackTrace();}finally {lock.unlock();}return true;}}
package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/8 12:08*/public class MyThread2T {public static void main(String[] args) {MyThread2 myThread2 = new MyThread2();
//        MyThread3 myThread2 = new MyThread3();new Thread(myThread2,"售票员1").start();new Thread(myThread2,"售票员2").start();new Thread(myThread2,"售票员3").start();}
}

 结果:

售票员1 正在售票,还剩余票数为:10
售票员1 正在售票,还剩余票数为:9
售票员1 正在售票,还剩余票数为:8
售票员2 正在售票,还剩余票数为:7
售票员2 正在售票,还剩余票数为:6
售票员2 正在售票,还剩余票数为:5
售票员2 正在售票,还剩余票数为:4
售票员2 正在售票,还剩余票数为:3
售票员3 正在售票,还剩余票数为:2
售票员1 正在售票,还剩余票数为:1
售票员1的票已经全部售完,此时的票数量为:0
售票员2的票已经全部售完,此时的票数量为:0
售票员3的票已经全部售完,此时的票数量为:0

7.线程通信

(1)概述

多个线程在处理同一个资源,但是处理的动作(线程的任务)却不相同。
为什么要处理线程间通信

多个线程并发执行时,在默认情况下CPU是随机切换线程的,当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些协调通信,以此来帮我们达到多线程共同操作一份数据。
如何保证线程间通信有效利用资源
多个线理在处理同一个资源,并且任务不同时,需要线程通信来帮助解决线程之间对同一个变量的使用或操作。就是多个线程在操作同一份数据时,避免对同一共享变量的争夺。也就是我们需要通过一定的手段使各个线程能有效的利用资源。而这种手段即---等待唤醒机制。

等待唤醒机制
这是多个线程间的一种协作机制,谈到线程我们经想到的是线程间的竞争,比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制,就好比在公司里你和你的同事们,你们可能存在在晋升时的竞争,但更多时候你们更多是一起合作以完成某些任务。就是在一个线程进行了规定操作后,就进入等待状态(wait),等待其他线程执行完他们的指定代码过后再将其唤醒(notify);在有多个线程进行等待时,如果需要,可以使用 notifyAl来唤醒所有的等待线程。
wait/notify 就是线程间的一种协作机制。

 示例:

package com.example.demo.thread;/*** @author linaibo* @version 1.0* Create by 2023/2/5 19:40*/public class WaitNotify {public static void main(String[] args) {Object lock = new Object();new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {System.out.println("线程1:顾客1告诉老板要早餐");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("顾客1开始吃早餐");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {System.out.println("线程2:顾客2告诉老板要早餐");try {lock.wait();} catch (InterruptedException e) {e.printStackTrace();}System.out.println("顾客2开始吃早餐");}}}).start();new Thread(new Runnable() {@Overridepublic void run() {try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock) {System.out.println("老板告诉顾客做好了");lock.notifyAll();}}}).start();new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock) {System.out.println("线程3:顾客3告诉老板要早餐");try {lock.wait();
//                        lock.wait(4000);会等待指定的时间,如果没有唤醒,就自己唤醒} catch (InterruptedException e) {e.printStackTrace();}System.out.println("顾客3开始吃早餐");}}}).start();}
}

运行结果:

线程1:顾客1告诉老板要早餐
线程2:顾客2告诉老板要早餐
线程3:顾客3告诉老板要早餐
老板告诉顾客做好了
顾客3开始吃早餐
顾客2开始吃早餐
顾客1开始吃早餐

注意: 所有的等待、通知方法必须在对加锁的同步代码块中。
等待唤醒机制就是用于解决线程间通信的问题的,使用到的3个方法的含义如下:
1.wait:线程不再活动,不再参与调度,进入 wait set(锁池)中,因此不会浪费 CPU资源,也不会去竞争锁了,这时的线程状态即是 WAITING,它还要等着别的线程执行一个特别的动作,也即是“通知(notify) 。使这个对象上等待的线程从wait set中释放出来,重新进入到调度队列(ready queue)中wait(long m):wait方法如果在指定的毫秒之后,还没有被notify唤醒,就会自动醒来。
sleep(long m):不会释放锁。

2. notify: 则选取所通知对象的 wait set 中的一个线程释放,例如,餐馆有空位置后,等候就餐最久的顾客最先入座。

3.notifyAll: 则释放所通知对象的 wait set 上的全部线程。

注意:
哪怕只通知了一个等待的线程,被通知线程也不能立即恢复执行,因为它当初中断的地方是在同步块内,而此刻它已经不持有锁,所以她需要再次尝试去获取锁很可能面临其它线程的竞争),成功后才能在当初调用 wait 方法之后的地方恢复执行。总结如下:
。如果能获取锁,线程就从 WAITING 状态变成 RUNNABLE 状态;
。否则,从 wait set 出来,又进入 entry set,线程就从 WAITING状态又变成 BLOCKED 状态调用wait和notify方法需要注意的细节
1.wait方法与notiy方法必须要由同一个锁对象调用。因为:对应的锁对象可以通过notiy唤醒使用同一个锁对象调用的wait方法后的线程

2. wait方法与notify方法是属于Object类的方法的,因为:锁对象可以是任意对象,而任意对象的所属类都是继承了Object类的。

3.wait方法与notify方法必须要在同步代码块或者是同步函数中使用。因为:必须要通过锁对象调用这2个方法。

8.线程死锁

线程死锁描述的是这样一种情况:多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻塞,因此程序不可能正常终止。

如下图所示,线程 A 持有资源 2,线程 B 持有资源 1,他们同时都想申请对方的资源,所以这两个线程就会互相等待而进入死锁状态。

示例:

public class DeadLockDemo {private static Object resource1 = new Object();//资源 1private static Object resource2 = new Object();//资源 2public static void main(String[] args) {new Thread(() -> {synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resource2");synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2");}}}, "线程 1").start();new Thread(() -> {synchronized (resource2) {System.out.println(Thread.currentThread() + "get resource2");try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread() + "waiting get resource1");synchronized (resource1) {System.out.println(Thread.currentThread() + "get resource1");}}}, "线程 2").start();}
}

结果:

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

线程 A 通过 synchronized (resource1) 获得 resource1 的监视器锁,然后通过Thread.sleep(1000);让线程 A 休眠 1s 为的是让线程 B 得到执行然后获取到 resource2 的监视器锁。线程 A 和线程 B 休眠结束了都开始企图请求获取对方的资源,然后这两个线程就会陷入互相等待的状态,这也就产生了死锁。

9.线程池

(1).概述

我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
那么有没有一种办法使得线程可以复用,就是执行完一个任务,并不被销毁,而是可以继续执行其他的任务?
在Java中可以通过线程池来达到这样的效果。
线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。合理利用线程池能够带来三个好处:
1.降低资源消耗。减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。
2.提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。

3.提高线程的可管理性,可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下。每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

(2).创建方式

Java里面线程池的顶级接口是java.util.concurrent.Executor,但是严格意义上讲 Executor 并不是一个线程池,而只是一个执行线程的工具,真正的线程池接口是java.util.concurrent.ExecutorService。
要配置一个线程池是比较复杂的,尤其是对于线程池的原理不是很清楚的情况下,很有可能配置的线程池不是较忧的,因此在java.util.concurent.Executors 线程工厂
类里面提供了一些静态工厂,生成一些常用的线程池。官方建议使用Executors工程类来创建线程池对象.
Java类库提供了许多静态方法来创建一个线程池.
Executors类中创建线程池的方法如下:
a、newFixedThreadPool 创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。

b、newCachedThreadPool 创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。

c、newSingleThreadPoolExecutor 创建一个单线程的Executor,确保任务串行执行。
d、newScheduledThreadPool 创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。

(3)示例1

package com.example.demo.thread;import com.example.demo.domain.BmsBillMemo;
import org.jetbrains.annotations.NotNull;import java.util.concurrent.*;/*** @author linaibo* @version 1.0* Create by 2023/2/8 11:54*/public class ThreadPool {public static void main(String[] args) {//创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
//        ExecutorService executorService = Executors.newFixedThreadPool(4);//创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。
//        ExecutorService executorService = Executors.newCachedThreadPool(Executors.defaultThreadFactory());//创建一个单线程的Executor,确保任务串行执行。ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());//创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。//ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);run1 r1 = new run1();run1 r2 = new run1();run1 r3 = new run1();//executorService.schedule(r1,3, TimeUnit.SECONDS);executorService.submit(r1);executorService.submit(r2);executorService.submit(r3);executorService.shutdown();}
}class run1 implements Runnable {Object lock = new Object();@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock) {for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName() + "    " + i);}}}
}

示例2

package com.example.demo.thread;import com.example.demo.domain.BmsBillMemo;
import org.jetbrains.annotations.NotNull;import java.util.concurrent.*;/*** @author linaibo* @version 1.0* Create by 2023/2/8 11:54*/public class ThreadPool {public static void main(String[] args) {//创建一个固定长度的线程池,当到达线程最大数量时,线程池的规模将不再变化。
//        ExecutorService executorService = Executors.newFixedThreadPool(4);//创建一个可缓存的线程池,如果当前线程池的规模超出了处理需求,将回收空的线程;当需求增加时,会增加线程数量;线程池规模无限制。
//        ExecutorService executorService = Executors.newCachedThreadPool(Executors.defaultThreadFactory());//创建一个单线程的Executor,确保任务串行执行。
//        ExecutorService executorService = Executors.newSingleThreadExecutor(Executors.defaultThreadFactory());//创建一个固定长度的线程池,而且以延迟或者定时的方式来执行,类似Timer。ScheduledExecutorService executorService = Executors.newScheduledThreadPool(2);run1 r1 = new run1();//run1 r2 = new run1();// run1 r3 = new run1();executorService.schedule(r1,3, TimeUnit.SECONDS);//  executorService.submit(r1);//  executorService.submit(r2);//  executorService.submit(r3);//  executorService.shutdown();}
}class run1 implements Runnable {Object lock = new Object();@Overridepublic void run() {try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}synchronized (lock) {for (int i = 0; i < 15; i++) {System.out.println(Thread.currentThread().getName() + "    " + i);}}}
}

相关文章:

java多线程开发

1.并发和并行 并发&#xff1a;同一时间段内多个任务同时进行。 并行&#xff1a;同一时间点多个任务同时进行。 2.进程线程 进程&#xff08;Process&#xff09;&#xff1a;进程是程序的一次动态执行过程&#xff0c;它经历了从代码加载、执行、到执行完毕的一个完整过程…...

杂记7--opencv的ar码模块学习

背景&#xff1a;项目需要用到marker知识&#xff0c;所以到官网上临时补一些知识。 概要&#xff1a;主要介绍marker一些接口的含义&#xff0c;纯属个人理解&#xff0c;有误则希望大佬不吝赐教 1、 涉及ar码操作学习&#xff0c;其头文件为&#xff1a; #include <op…...

[项目设计]高并发内存池

目录 1、项目介绍 2、高并发内存池整体框架设计 3、thread cache <1>thread cache 哈希桶对齐规则 <2>Thread Cache类设计 4、Central Cache <1>Central Cache类设计 5、page cache <1>Page Cache类设计 6、性能分析 <1>定长内存池实现…...

28岁才转行软件测试,目前32了,我的一些经历跟感受

我是92年的&#xff0c;算是最早的90后&#xff0c;现在跟你介绍的时候还恬不知耻的说我是90后&#xff0c;哈哈&#xff0c;计算机专业普通本科毕业。在一个二线城市&#xff0c;毕业后因为自身能力问题、认知水平问题&#xff0c;再加上运气不好&#xff0c;换过多份工作&…...

Python导入模块的3种方式

很多初学者经常遇到这样的问题&#xff0c;即自定义 Python 模板后&#xff0c;在其它文件中用 import&#xff08;或 from...import&#xff09; 语句引入该文件时&#xff0c;Python 解释器同时如下错误&#xff1a;ModuleNotFoundError: No module named 模块名意思是 Pytho…...

select 与 where、order by、limit 子句执行优先级比较

当 select 和 其他三种语句的一者或者多者同时出现时&#xff0c;他们之间是存在执行先后顺序的。 他们的优先级顺序是&#xff1a;where > select > order by > limit 目录 1、select 与 where 2、select 与 order by 3、order by 与 limit 4、优先级证明 1、s…...

Linux内核并发与竞争-原子操作

一.原子操作的概念首先看一下原子操作&#xff0c;原子操作就是指不能再进一步分割的操作&#xff0c;一般原子操作用于变量或者位操作。假如现在要对无符号整形变量 a 赋值&#xff0c;值为 3&#xff0c;对于 C 语言来讲很简单&#xff0c;直接就是&#xff1a; a3但是 C 语言…...

Java笔记-泛型的使用

参考&#xff1a; Java 泛型&#xff0c;你了解类型擦除吗&#xff1f; 泛型的使用 1、泛型的定义 可以广泛使用的类型&#xff0c;一种较为准确的说法就是为了参数化类型&#xff0c;或者说可以将类型当作参数传递给一个类或者是方法。 2、泛型的使用 2.1泛型类 public c…...

特斯拉无人驾驶解读

来源于Tesla AI Day Tesla无人驾驶算法的核心任务就是如何理解我们所看到的一切呢?也就是说,不使用高端的设备,比如激光雷达,仅仅使用摄像头就能够将任务做得很好。Tesla使用环绕型的8个摄像头获得输入。 第一步是特征提取模块Backbone,无论什么任务都离不开特征…...

生物素-琥珀酰亚胺酯Biotin-NHS;CAS号:35013-72-0;可对溶液中的抗体,蛋白质和任何其他含伯胺的大分子进行简单有效的生物素标记。

结构式&#xff1a; ​ 生物素-琥珀酰亚胺酯Biotin NHS CAS号&#xff1a;35013-72-0 英文名称&#xff1a;Biotin-NHS 中文名称&#xff1a;D-生物素 N-羟基琥珀酰亚胺酯&#xff1b;生物素&#xff0d;琥珀酰亚胺酯 CAS号&#xff1a;35013-72-0 密度&#xff1a;1.50.1 …...

Maven_第五章 核心概念

目录第五章 其他核心概念1、生命周期①作用②三个生命周期③特点2、插件和目标①插件②目标3、仓库第五章 其他核心概念 1、生命周期 ①作用 为了让构建过程自动化完成&#xff0c;Maven 设定了三个生命周期&#xff0c;生命周期中的每一个环节对应构建过程中的一个操作。 …...

【深度学习】人脸识别工程化落地

文章目录前言1、facenet2、使用2.1.其它blog2.2 实践总结前言 老早以前就希望能写一篇关于人脸识别的工程化落地的案例&#xff0c;一年前做疲劳驾驶时使用的dlib插件&#xff0c;它封装好了&#xff0c;人脸检测、对齐、相似度计算三个部分,就是插件比较难装,但同时也少了很多…...

AOP面向切面编程思想。

目录 一、AOP工作流程 1、基本概念 2、AOP工作流程 二、AOP核心配置 1、AOP切入点表达式 2、AOP通知类型 三、AOP通知获取数据 1、获取参数 2、获取返回值 3、获取异常 四、AOP事务管理 1、Spring事务简介 2、Spring事务角色 3、事务属性 一、AOP工作流程 1、…...

实验7-变治技术及动态规划初步

目录 1.统计个数 2.数塔dp -A 3.Horspool算法 4.计数排序 5.找零问题1-最少硬币 1.统计个数 【问题描述】有n个数、每个元素取值在1到9之间,试统计每个数的个数 【输入形式】第一行,n的值;第二行࿰...

JVM垃圾回收机制GC理解

目录JVM垃圾回收分代收集如何识别垃圾引用计数法可达性分析法引用关系四种类型&#xff1a; 强、软、弱、虚强引用软引用 SoftReference弱引用 WeakReferenceWeakHashMap软引用与虚引用的使用场景虚引用与引用队列引用队列虚引用 PhantomReference垃圾回收算法引用计数复制 Cop…...

C++中的容器

1.1 线性容器1&#xff09;std::array看到这个容器的时候肯定会出现这样的问题&#xff1a;为什么要引入 std::array 而不是直接使用 std::vector&#xff1f;已经有了传统数组&#xff0c;为什么要用 std::array?先回答第一个问题&#xff0c;与 std::vector 不同&#xff0c…...

2023备战金三银四,Python自动化软件测试面试宝典合集(五)

接上篇八、抓包与网络协议8.1 抓包工具怎么用 我原来的公司对于抓包这块&#xff0c;在 App 的测试用得比较多。我们会使用 fiddler 抓取数据检查结果&#xff0c;定位问题&#xff0c;测试安全&#xff0c;制造弱网环境;如&#xff1a;抓取数据通过查看请求数据&#xff0c;请…...

SpringDI自动装配BeanSpring注解配置和Java配置类

依赖注入 上篇博客已经提到了DI注入方式的构造器注入&#xff0c;下面采用set方式进行注入 基于set方法注入 public class User {private String name;private Address address;private String[] books;private List<String> hobbys;private Map<String,String>…...

2月面经:真可惜...拿了小米的offer,字节却惨挂在三面

我是2月份参加字节跳动和华为的面试的&#xff0c;虽然我只拿下了小米的offer&#xff0c;但是我自己也满足了&#xff0c;想把经验分享出来&#xff0c;进而帮助更多跟我一样想进大厂的同行朋友们&#xff0c;希望大家可以拿到理想offer。 自我介绍 我是16年从南京工业大学毕…...

磐云PY-B8 网页注入

文章目录1.使用渗透机场景windows7中火狐浏览器访问服务器场景中的get.php&#xff0c;根据页面回显获取Flag并提交&#xff1b;2.使用渗透机场景windows7中火狐浏览器访问服务器场景中的post.php&#xff0c;根据页面回显获取Flag并提交&#xff1b;3.使用渗透机场景windows7中…...

多传感器融合定位十-基于滤波的融合方法Ⅰ其二

多传感器融合定位十-基于滤波的融合方法Ⅰ其二3. 滤波器基本原理3.1 状态估计模型3.2 贝叶斯滤波3.3 卡尔曼滤波(KF)推导3.4 扩展卡尔曼滤波(EKF)推导3.5 迭代扩展卡尔曼滤波(IEKF)推导4. 基于滤波器的融合4.1 状态方程4.2 观测方程4.3 构建滤波器4.4 Kalman 滤波实际使用流程4…...

Java集合面试题:HashMap源码分析

文章目录一、HashMap源码二、HashMap数据结构模型图三、HashMap中如何确定元素位置四、关于equals与hashCode函数的重写五、阅读源码基本属性参考文章&#xff1a;史上最详细的 JDK 1.8 HashMap 源码解析参考文章&#xff1a;Hash详解参考文章&#xff1a;hashCode源码分析参考…...

华为OD机试 - 数组合并(Python),真题含思路

数组合并 题目 现在有多组整数数组, 需要将他们合并成一个新的数组。 合并规则, 从每个数组里按顺序取出固定长度的内容合并到新的数组中, 取完的内容会删除掉, 如果该行不足固定长度或者已经为空, 则直接取出剩余部分的内容放到新的数组中, 继续下一行。 如样例 1, 获得长度…...

Vue2创建移动端项目

一、Vscode Vscode 下载安装以及常用的插件 1、Vscode 下载 下载地址&#xff1a;Vscode 中文语言插件 搜索 chinese 主题 Atom 主题 文件图标主题 搜索 icon 源代码管理插件GitLens 搜索 GitLens Live Server _本地服务器 搜索 Live Server Prettier - Code formatt…...

PorterDuffXfermode与圆角图片

版权声明 本文原创作者&#xff1a;谷哥的小弟作者博客地址&#xff1a;http://blog.csdn.net/lfdfhl 圆角图片 在项目开发中&#xff0c;我们常用到这样的功能&#xff1a;显示圆角图片。 这个是咋做的呢&#xff1f;我们来瞅瞅其中一种实现方式 /*** param bitmap 原图* p…...

如何准备大学生电子设计竞赛

大学生电子设计竞赛难度中上&#xff0c;一般有好几个类型题目可以选择&#xff0c;参赛者可以根据自己团队的能力、优势去选择合适自己的题目&#xff0c;灵活自主空间较大。参赛的同学们可以在暑假好好学习相关内容&#xff0c;把往年的题目拿来练练手。这个比赛含金量还是有…...

【Java容器(jdk17)】ArrayList深入源码,就是这么简单

ArrayList深入源码一、ArrayList源码解析1. MIXIN 的混入2. 属性说明3. 构造方法4. 其他方法&#xff08;核心&#xff09;iterator 和 listIterator 方法add方法remove 方法sort方法其他二、ArrayList 为什么是线程不安全的&#xff1f;体现哪些方面呢&#xff1f;三、ArrayLi…...

【Java 面试合集】简述下Java的三个特性 以及项目中的应用

简述下Java的特征 以及项目中的应用 1. 概述 上述截图中就是Java的三大特性&#xff0c;以及特性的实现方案。接下来就每个点展开来说说 2. 封装 满足&#xff1a;隐藏实现细节&#xff0c;公开使用方法 的都可以理解为是封装 而实现封装的有利手段就是权限修饰符了。可以根据…...

git基本概念图示【学习】

基本概念工作区&#xff08;Working Directory&#xff09;就是你在电脑里能看到的目录&#xff0c;比如名字为 gafish.github.com 的文件夹就是一个工作区本地版本库&#xff08;Local Repository&#xff09;工作区有一个隐藏目录 .git&#xff0c;这个不算工作区&#xff0c…...

微前端qiankun架构 (基于vue2实现)使用教程

工具使用版本 node --> 16vue/cli --> 5 创建文件 创建文件夹qiankun-test。 使用vue脚手架创建主应用main和子应用dev 主应用 安装 qiankun: yarn add qiankun 或者 npm i qiankun -S 使用qiankun&#xff1a; 在 utils 内创建 微应用文件夹 microApp,在该文件夹…...

主题之家wordpress/网络销售哪个平台最好

前几天有朋友问到歌词滚动应该怎么做&#xff0c;针对歌词滚动这个功能做了一个简单的案例&#xff0c;仅供参考&#xff0c;大家如果有更好的做法记得call我们一下&#xff01;按照惯例&#xff0c;我们先看一下这个效果是怎样的点这里预览&#xff1a;https://o0piel.axshare…...

扫二维码直接进网站怎么做/有没有可以代理推广的平台

Android提供了调试工具被称为Dalvik Debug Monitor Server (DDMS)&#xff0c;工作原理如下&#xff1a; DDMS将搭建起IDE与测试终端(Emulator 或者connected device)的链接&#xff0c;它们应用各自独立的端口监听调试器的信息&#xff0c;DDMS可以实时监测到测试终端的连接情…...

建网站和建网页的区别/成人短期技能培训

RabbitMQ集群概述通过 Erlang 的分布式特性&#xff08;通过 magic cookie 认证节点&#xff09;进行 RabbitMQ 集群&#xff0c;各 RabbitMQ 服务为对等节点&#xff0c;即每个节点都提供服务给客户端连接&#xff0c;进行消息发送与接收。这些节点通过 RabbitMQ HA 队列&…...

凡科网怎么样可靠吗/sem 优化价格

整合ITIL&SOA&#xff0c;实践IT管理。Future S很有意思地将这两个看似不相干的概念放在一起。印象中收到邀请时对方只是提到SOA的概念。虽然现场的主题有些意外&#xff0c;但是也正好给了我一个了解二者关系的机会。 ITIL与SOA的关系是什么?是否存在相互依存的关系? -…...

工作简历模板免费使用/佛山seo技术

这是一个系列&#xff0c;我们将其命名为android最佳实践&#xff0c;如果你还没有看之前的文章&#xff1a; Android最佳实践&#xff08;一&#xff09; android最佳实践&#xff08;二&#xff09; android最佳实践&#xff08;三&#xff09; android最佳实践&#xff08;四…...

国内做外卖的网站有哪些/数据分析网官网

思路&#xff1a;要找一个最大距离最小的点&#xff0c;那么有2个方向1.这u点下面最远距离2.这个u点上面的最远距离&#xff0c;这里向上走有2种走法&#xff0c;一直是直接求u点的子节点没有访问过求向上和向下的最大值&#xff0c;访问过则求向上最大值和向下次大值的最大值&…...