逸学java【初级菜鸟篇】11.多线程【多方位详解】

2023-12-02 10:44

本文主要是介绍逸学java【初级菜鸟篇】11.多线程【多方位详解】,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

hi,我是逸尘,一起学java吧


目标(任务驱动)

略述概述多线程一隅

线程

进程

在提到线程是什么之前我们还需要提到另一个名词 他就是进程

  • 进程一个内存中运行的应用程序,每个进程都有⼀个独立的内存空间,⼀个应用程序可以同时运行多个进程;
  • 进程也是程序的⼀次执⾏过程,是系统运行程序的基本单位;
  • 系统运行⼀个程序即是 ⼀个进程从创建、运行到消亡的过程。

我们可以打开我们的任务管理器

 我们通俗所说的一个正在运行的程序,其实它就可以说是一个进程。

线程

在上面学习了每个进程都有⼀个独立的内存空间,可以有同时运行很多线程的能力,线程就包含在进程里,那么线程就是一个进程的子概念。

  • 线程(thread)一个程序内部 一条执行路径
  • 是操作系统能够进行运算调度的最小单位。
  • 我们之前启动程序执行后,main方法的执行其实就是一条单独的执行路径。

进程和线程的关系 

多线程

多线程是指从软硬件上实现多条执行流程的技术(也可以单说是在程序里可以”同时“运行多个不同的任务的技术)这里的“同时”需要加上双引号。

简单的说,程序同时完成很多事情时,就是多线程。

我们为什么需要多线程

前面的内容中我们学习的所有代码都是顺序执行的(跳转的不算),一个任务完成之后才去执行下一个,但是说代码源于生活,我们在日常生活中,并不是这样呆板的活着的(也不可能),比如我们先呼吸一下,然后血液循环,然后说话,然后思考。我们一般是都是一并发生的,呼吸的同时可以血液循环可以说话,正如屏幕前看文章的你是可以呼吸的,是可以同时发生的,你可以一边呼吸一边思考我这句话是不是正确的。

在java中同样,需要这样的一并发生的设计处理,比如,一边实现服务的文上传一边下载,,需要协同处理,所以我们java中支持多线程,我们需要多线程。

ps:并不是所有的语言都支持多线程

并发

可以”同时“运行多个不同的任务(活动)这样的思想也被成为并发

多线程就一种并发编程的技术。

但是需要了解的是我们的并发并不是真正意义上的同时它是伪同时

ps:那是因为早期计算机的 CPU 都是单核的,一个 CPU 在同一时间只能执行一个进程/线程,当系统中有多个进程/线程等待执行时,CPU 只能执行完一个再执行下一个,为了解决所谓同时问题(并发),只能通过一种算法将 CPU 资源合理地分配给多个任务,轮询为系统的每个线程服务, 由于cpu切换的速度很快,给我们的感觉这些线程在同时执行,这就是真正的并发。

简单的说就是语言层面可以实现同时,但是我们早期电脑的单核cpu无法从技术满足。

并行

并行则是针对多核 CPU 提出的。和单核 CPU 不同,多核 CPU 真正实现了“同时执行多个任务。

真同时

我们现在的多线程是并发还是并行

因为多线程在单核下,多线程必定是并发的,不过现在的统一进程的多线程是可以运行在多核CPU下,所以可以是并行的。

并发和并行同时的,咱们就知道就可以了

实现线程

实现多线程的方法在我们的JAVA官方中指出的是两种

方法一 实现Runnable接口

Runnable接口是实现接口,可以继续继承类和实现接口,扩展性强这是这种方法的优点。

回顾接口内容:这是一个类实现接口,必须重写完全部接口的全部抽象方法,否则这个类需要定义成抽象类。

我们需要重写run()方法,而run()方法里面就是我们真正的任务功能内容。

实现过程 

  1.  我们需在类上要实现Runnable接口
  2. 需要我们重写run()方法
  3. 创建一个任务对象(其实就是实例化该类的对象)
  4. 把任务对象交给Thread的对象处理
  5. 调用start()启动线程,调用run()的内容(最终调用target.run())

实际上是创建一个任务对象,把任务线程对象处理。

package com.yd.thread;/*** 实现Runnable接口实现创建线程*///我们需在类上要实现Runnable接口
public class RunnableStyle implements Runnable{public static void main(String[] args) {//Runnable对象和Thread对象相关联//创建一个任务对象(其实就是实例化该类的对象)RunnableStyle runnableStyle = new RunnableStyle();//把任务对象交给Thread的对象处理Thread threadOne = new Thread(runnableStyle);//调用start()启动线程threadOne.start();}//需要我们重写run()方法@Overridepublic void run() {System.out.println("用Runnable接口来实现多线程");}
}

这样我们就实现了一个线程。

最终调用target.run()是什么意思

我们把Runnable任务对象交给Thread处理,我们来看一下Thread的run()内容,(我们可以ctrl+F12搜寻对应类的方法)

有值,运行

target是我们的本Runnable任务

 这样就是我们的Thread调用Runnable()重写的run方法,只是加上了一个判断后,运行了run()

简单的说我们这个run()还是是对Runnable()重写的run方法,而不是Thread的run()

方法二 继承Thread类

 我们查看源码可以看出来其实Thread类实质上是实现了Runnable的接口,

它run方法是对Runnable接口中的run()方法的具体实现(run()整个都被重写)。

当我们启动一个程序时,自动生成一个线程,这个线程就是我们主方法的运行路径,当我们自己实现线程时,我们程序员自己负责自己的线程(启动或者是什么的),我们的主方法线程的启动是java虚拟级负责的。 

比如我们下面的创建   那么我们的单线程程序就变成了主线程和我们创建的线程,也就是多线程了

实现过程

  1.  我们需在类上要继承Thread类
  2. 需要我们重写run()方法
  3. 创建该类对象
  4. 调用start()启动线程,调用run()的内容(run()重写)
package com.yd.thread;public class ThreadStyle extends Thread{public static void main(String[] args) {new ThreadStyle().start();}//需要我们重写run()方法@Overridepublic void run() {System.out.println("用继承Thread来实现多线程");}
}

run()重写是什么意思

以上面代码为例,这个run的内容将覆盖if判断的内容,而不是调用(重名run)

总结 

准确的讲,创建线程只有一种方式那就是构造Thread类,而实现线程的执行单元有两种方式
方法一:实现Runnable接口的run方法,并把Runnable实例传给Thread类
方法二:重写Thread的run方法(继承Thread类)

推荐使用方法一

我们提到当我们启动一个程序时,自动生成一个线程,这个线程就是我们主方法的运行路径,当我们自己实现线程时,我们程序员自己负责自己的线程(启动或者是什么的),我们的主方法线程的启动是java虚拟级负责的。 

Thread提供了很多与线程操作相关的方法

常用的就是获取当前线程对象currentthread()设置名称setname(),获取线程名称getname()

当然还有很多在实际开发中不常用的操作方法,但是也会在后面去一一讲解。

扩展 其他形式(万变不离其宗)

Lambda表达式的写法来表达线程实现【写法简单】

package com.yd.thread;public class One {public static void main(String[] args) {//简化写法new Thread(()->System.out.println(Thread.currentThread().getName())).start();//1.匿名内部类//        new Runnable() {//            @Override//            public void run() {//                System.out.println(Thread.currentThread().getName());//            }//        };//2.lambda写法  放入任务放到Thread类//        Runnable runnable = () -> System.out.println(Thread.currentThread().getName());//        new Thread(runnable);}
}

 ps:函数式接口,所以可以简化

用Callable接口,结合FutureTask类完成【可以返回结果】

我们的前面的实现是把实现Runnable接口的类的对象放入Thread中,运行,但是我们重写的run方法均不能直接返回结果。

这个时候我们可以用Callable结合FutureTask类来实现有返回值的方式放入Thread中,运行。

package com.yd.thread;import java.util.concurrent.Callable;public class Two implements Callable<String> {private int n;//构造函数的重载public Two(int n){this.n=n;}//重写的是call@Overridepublic String call() throws Exception {int sum=0;for (int i = 0; i <= n; i++) {sum+=i;}return "n"+"和是"+sum;}
}
package com.yd.thread;import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;public class TwoTest {public static void main(String[] args) {//创建一个Callable对象    /他不是线程任务,所以要再封装一下,但是需要它能返回的能力Callable<String> twoCallA = new Two(1);//交给FutureTask,封装Callable对象,变成未来任务对象  可以在线程执行完成后调用get方法,获取返回的结果               /创建一个Runnable对象任务FutureTask<String> stringFutureTaskA = new FutureTask<>(twoCallA);//交给Thread类Thread tA = new Thread(stringFutureTaskA);//启动线程tA.start();//同样再开一个线程Callable<String> twoCallB = new Two(100);FutureTask<String> stringFutureTaskB = new FutureTask<>(twoCallB);Thread tB = new Thread(stringFutureTaskB);tB.start();//获取线程执行完毕的结果try {String sA = stringFutureTaskA.get();System.out.println(sA);String sB = stringFutureTaskB.get();System.out.println(sB);}catch (Exception e){e.printStackTrace();}}
}

线程池的方式【是一种复用线程的技术】

不使用线程池,如果用户每发起一个请求,后台就创建一个新线程来处理,下次新任务来了又要创建新线程,而创建新线程开销是很大的,这样会严重影响系统的性能

使用线程池不用每次重新创建线程,保持重复利用

 

方法一

我们通常使用Executorservice的实现类ThreadpoolExecutor自创建一个线程池对象

ThreadPoolExcecutor构造器参数说明

参数一:指定线程池的线程数量(核心线程):corepoolsize          不能小于0
参数二:指定线程池可支持的最大线程数: maximumpoolsize     最大数量>核心线程数量
参数三:指定临时线程的最大存活时间: keepalivetime                不能小于0
参数四:指定存活时间的单位(秒,分,时,天): unit                  时间单位
参数五:指定任务队列:workqueue                                               不能为null
参数六:指定用哪个线程工厂创建线程:threadfactory                  不能为null
参数七:指定线程忙,任务满的时候,新任务来了怎么办: handler 不能为null

参数五一般我们选用的就是ArrayBlockingQueue和LinkedBlockingQueue,

队列是一种数据结构,我们会在下一个课程去学习,不过我们可以简单了解一下我们配置的通常几个

  1. ArrayBlockingQueue:一个由数组结构组成的有界阻塞队列(数组结构可配合指针实现一个环形队列)。
  2. LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列,在未指明容量时,容量默认为 Integer.MAX_VALUE。
  3. PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列,对元素没有要求,可以实现 Comparable 接口也可以提供 Comparator 来对队列中的元素进行比较。跟时间没有任何关系,仅仅是按照优先级取任务。
  4. DelayQueue:类似于PriorityBlockingQueue,是二叉堆实现的无界优先级阻塞队列。要求元素都实现 Delayed 接口,通过执行时延从队列中提取任务,时间没到任务取不出来。

简单的说参数五的作用也就是我们需要把任务怎么放进去,怎么排列。

参数六的我们默认写Executors.defaultThreadFactory()就可以了它的本质其实就是创建一个

Thread类对象,只不过多了一写处理

参数七的策略如下

​ 一般是用这三个

Abortpolicy  中止策略  默认

DiscardOldestPolicy 放弃最旧的策略

callerRunsPolicy调用主方运行策略

然后是Executorservice常用方法

 就是执行和关闭(线程池正常不会关闭)的方法

package com.yd.thread;import java.util.concurrent.*;public class ExecutorDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {//指定线程池的线程数量是3       相当于工作的员工是三个//指定线程池可支持的最大线程数7  相当于公司总共最多有七个人  临时工两个【等着】//指定临时线程的最大存活时间6    相当于临时工空闲多久被开除6加上后面的单位//指定任务队列                相当于吃饭的座位  任务相当于是菜//指定线程任务池的线程工厂      相当于HR 创建线程的方式//任务策略                   相当于忙不过来的解决办法,拒绝方法ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 7, 6, TimeUnit.MINUTES,new ArrayBlockingQueue<>(2), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());//现在是提交任务给线程池执行,我们之前是给给Tread类运行//执行Callable任务Future<String> submitA = pool.submit(new Two(1));Future<String> submitB = pool.submit(new Two(100));System.out.println(submitA.get());System.out.println(submitB.get());//执行Runnable任务pool.execute(new RunnableStyle());pool.execute(new RunnableStyle());pool.execute(new RunnableStyle());pool.execute(new RunnableStyle());pool.execute(new RunnableStyle());}}

我们的Runnable任务

package com.yd.thread;/*** 实现Runnable接口实现创建线程*///我们需在类上要实现Runnable接口
public class RunnableStyle implements Runnable{public static void main(String[] args) {//Runnable对象和Thread对象相关联//创建一个任务对象(其实就是实例化该类的对象)RunnableStyle runnableStyle = new RunnableStyle();//把任务对象交给Thread的对象处理Thread threadOne = new Thread(runnableStyle);//调用start()启动线程threadOne.start();}//需要我们重写run()方法@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"用继承Runnable来实现多线程");}
}

我们的callable任务

package com.yd.thread;import java.util.concurrent.Callable;public class Two implements Callable<String> {private int n;//构造函数的重载public Two(int n){this.n=n;}//重写的是call@Overridepublic String call() throws Exception {int sum=0;for (int i = 0; i <= n; i++) {sum+=i;}return Thread.currentThread().getName()+"  n"+"和是"+sum;}
}

可复用 

方法二 【存在风险,了解即可】

还有一种创建线程池的方案

使用Executors(线程池的工具类)调用已经搭配好的线程池对象

​ 

注意:Executors的底层其实也是基于线程池的实现类ThreadPoolExecutor创建线程池对象的,只是搭配好了。

但是在大型并发的使用可能会有风险,所以我们了解即可

定时器【周期调用的技术】   

定时器类似于我们生活中的“闹钟”,达到设定的时间后,就执行某个指定的代码。

但是我们需要注意的是它是保持大约的固定的时间间隔进行,但是一般来说没啥问题

引入了这个并发包单线程变为多线程  

  

     

    • task(command) 要安排的任务。

      delay - 任务执行前的延迟毫秒数。

      period - 连续任务执行之间的时间(以毫秒为单位)。

第一个参数:是一个TimerTask对象,TimerTask是一个继承了Runnable接口的类,我们只需要new一个TimerTask对象,重写里面的run方法即可。

第二个参数:表示指定的时间,默认单位是毫秒,即多长时间之后执行任务代码。

第三个参数:是连续任务执行之间的时间(以毫秒为单位)

最后一个参数 :是单位

 NANOSECONDS(纳秒)、MICROSECONDS(微秒)、MILISECONDS(毫秒)、SECONDS(秒)、MINUTE(分钟)、HOURS(小时)和DAYS(天)

  1. 创建Timer定时器,调用定时器的方法执行定时器任务
  2. 创建TimerTask定时器任务,可以通过匿名内部类的方式创建
package com.yd.thread;import java.util.Timer;
import java.util.TimerTask;public class TimerDemo {public static void main(String[] args) {// 创建定时器Timer timer = new Timer();// 创建定时器任务timer.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {System.out.println("1"+Thread.currentThread().getName());}}, 1, 1000);//任务2timer.scheduleAtFixedRate(new TimerTask() {@Overridepublic void run() {System.out.println("2"+Thread.currentThread().getName());}}, 1000, 200);}
}

操作线程的方法 

在操作线程之前我们要学习一下线程周期

线程周期

七种状态

接下来我们线程状态将结合着操作来谈

线程的加入

线程的加入就是:某个程序为多线程程序,假如存在线程A,现在需要插入B,并且要求B先执行完毕。

这就好比你现在看文章然后父母喊你吃饭一样,先得吃饭,然后才可以继续看,或者没有话费这样的更准确,先去充话费然后再打电话。

线程的加入可以用thread的join()方法来完成。

join()它的作用是将当前线程挂起,等待其他线程结束后再执行当前线程,

即当前线程等待另一个调用join()方法的线程执行结束后再往下执行。

通常在main主线程内,等待其它调用join()方法的线程执行结束再继续执行main主线程。

package com.yd.thread;public class Synchronized {public static void main(String[] args) throws InterruptedException {//实例化对象RunnableStyle one = new RunnableStyle();SynchronizedTest t = new SynchronizedTest();//把任务放到ThreadThread A = new Thread(t, "小尘");Thread a = new Thread(one);A.start();A.join(1000);a.start();
//        thread.join(1000);System.out.println(Thread.currentThread().getName()+"主线程的内容");}
}

​线程的睡眠

​线程的睡眠可以使用 sleep()方法,需要指定一个毫秒为单位,使线程在规定参数时间不能到就绪状态,同时醒来不能保证运行状态,但是能保证就绪状态,所以需要抛出异常。

​线程的中断

废除了stop()方法

我们会用提倡使用在run()中使用无限循环的形式,然后使用一个布尔类型标记控制循环的停止。

还有如果是使用sleep()或者wait()方法进入就绪状态,我们可以使用Thread的interrupt()方法来通知线程离开run()方法,同时抛出一个异常,可以在处理异常的时候完成业务中断,如终止while循环,或者是在while做一个isinterrupt判断。

package com.yd.thread;public class RunnableDemo implements Runnable{public static void main(String[] args) throws InterruptedException {RunnableDemo runnableDemo = new RunnableDemo();Thread thread = new Thread(runnableDemo);thread.start();Thread.sleep(2000);
//我们可以做一个对比,把下面注释去掉thread.interrupt();}//需要我们重写run()方法@Overridepublic void run() {int num=0;//Integer.MIN_VALUE为int的最大值,还没有被中断就可以运行while (!Thread.currentThread().isInterrupted()&&num<=Integer.MAX_VALUE/2){if (num%100==0){System.out.println(num+"是10000倍数");}num++;}System.out.println("运行结束");}}

​线程的优先级

线程可以划分优先级,优先级较高的线程得到的 CPU 资源较多,即 CPU 优先执行优先级较高的线程对象的任务,但是需要注意的是优先级小并不是得不到运行,只是概率小。

在 Java 中使用 setPriority 方法来设置优先级,同时把优先级划分成 1~10 这10个等级,

如果小于 1 或者大于 10,则 JDK 会抛出异常

// 线程最小的优先级等级
public final static int MIN_PRIORITY = 1;

// 线程默认的优先级等级
public final static int NORM_PRIORITY = 5;

// 线程最大的优先级等级
public final static int MAX_PRIORITY = 10;

​线程的礼让

线程礼让是指在某个特定的时间点,让线程暂停抢占CPU资源的行为,但是其只是一种暗示,不能保证,在我们线程中操作线程都不能一定保证操作。

线程的礼让使用yield方法来实现

package com.mie.yield;public class YieldThread1 extends Thread{@Overridepublic void run() {// TODO Auto-generated method stubfor (int i = 0; i < 10; i++) {if (i==5) {yield();//当i==5时线程礼让}System.out.println(Thread.currentThread().getName()+"----"+i);}}}
package com.mie.yield;public class YieldThread2 extends Thread{@Overridepublic void run() {// TODO Auto-generated method stubfor (int j = 0; j < 10; j++) {System.out.println(Thread.currentThread().getName()+"====="+j);}}
}
package com.mie.yield;public class Test {public static void main(String[] args) {YieldThread1 yieldThread1=new YieldThread1();YieldThread2 yieldThread2=new YieldThread2();yieldThread1.setName("线程1");yieldThread2.setName("线程2");yieldThread1.start();yieldThread2.start();}}

​线程的守护

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

  • 垃圾回收器线程就是一种守护线程

要将普通线程设置为守护线程,方法很简单,只需要调用 Thread.setDaemon() 方法即可。

public class MyThread extends Thread{private int i = 0;@Overridepublic void run() {try {while (true) {i++;System.out.println("i=" + (i));Thread.sleep(1000);}}catch (InterruptedException e){e.printStackTrace();}}
}
public class Run3 {public static void main(String[] args) {try {MyThread thread = new MyThread();thread.setDaemon(true); //该线程为守护线程thread.start();Thread.sleep(5000);System.out.println("主线程(用户线程)结束,thread线程(守护线程)也不再打印了!");}catch (InterruptedException e){e.printStackTrace();}}
}

线程的安全

多个线程同时操作同一个共享资源的时候可能会出现业务安全问题,称为线程安全问题。

最典型的案例就是账户取钱

小尘和小土是兄弟,他们有一个共同的账户,余额是2k,模拟2人同时去取钱2k。

如果说同时执行取2k,用代码实现发现钱都可以取到,平白无故多2k。

同样的案例还有很多比如,银行排号,火车站售票

为了解决这样奇葩的问题,我们得让多个线程实现先后依次访问共享资源,这样可以解决安全问题。

加锁

我们第一个想法就是,加锁,抢一个锁,谁先拿到谁先访问

这就好比上洗手间,一个人先到把门关上,出来再将门打开,下一个人才可以进入。

那么我们可以怎么操作呢

同步块

把访问共享资源的核心代码给上锁,以此保证线程安全

每次只允许一个线程加锁后进入,执行完毕后自动解锁,其他线程才可以进来执行。

ctrl+art+t 快捷键  

synchronized(Object){

}

同步机制使用了synchronized关键字,使用关键字代码块被称为同步块

我们将共享资源放在synchronized定义的区域,当其他线程获取这个锁的时候,就必须等待锁释放后才可以进入该区域。

Object是任意一个对象,每一个对象都存在一个标志位,并且有两个值,0和1。

如果是0代表同步块有线程运行,当期线程处于就绪状态,直到处于同步代码块内容执行完毕,值为1,当期线程才可以执行代码块内容。

我们模拟一个买橘子的场景,五个人买橘子每次只能买30个,一共老板只有100个。

package com.yd.thread;public class SynchronizedTest implements Runnable{int num=100;public void BuyOranges(int buyNum){String name = Thread.currentThread().getName();synchronized (this){if (this.num>buyNum){System.out.println(name+"买了"+buyNum+"橘子");//更新数量this.num-=buyNum;}else {System.out.println(name+"没有那么多橘子让你买");}}}@Overridepublic void run() {BuyOranges(30);}}
package com.yd.thread;public class Synchronized {public static void main(String[] args) {//实例化一个对象SynchronizedTest t = new SynchronizedTest();//把任务放到ThreadThread A = new Thread(t, "小尘");Thread B  = new Thread(t, "小土");Thread C  = new Thread(t, "小水");Thread E  = new Thread(t, "小火");Thread D  = new Thread(t, "小金");A.start();B.start();C.start();D.start();E.start();}
}

 

我们的锁对象不要影响其他线程执行

规范上:建议使用共享资源作为锁对象。
对于实例方法建议使用this作为锁对象。
对于静态方法建议使用字节码(类名.class)对象作为锁对象。

同步方法

把访问共享资源的核心方法给上锁,以此保证线程安全。

修饰符synchronized返回值类型方法名称(形参列表){
      操作共享资源的代码

}

原理:每次只能一个线程进入,执行完毕以后自动解锁,其他线程才可以进来执行。

同步方法其实底层也是有隐式锁对象的,只是锁的范围是整个方法代码

如果方法是实例方法:同步方法默认用this作为的锁对象,但是代码要高度面向对象。

如果方法是静态方法:同步方法默认用类名.class作为的锁对象。

lock锁

lock实现提供比使用synchronized方法和语句可以获得更广泛的锁定操作。

Lock并不是用来代替synchronized的而是当使用synchronized不满足情况或者不合适的时候来提供高级功能的

lock是接口,不能直接实例化,这里采用它的实现类Reentrantlock来构建lock锁对象

锁对象创建完以后,在方法的对应的位置添加。

灵活性地提高带来了额外的责任。 缺少块结构锁定需要手动地去释放锁。 在大多数情况下,应使用以下惯用法:

Lock lock = new ReentrantLock();
lock.lock();
try{
  
   }finally {
   lock.unlock();
}

package com.yd.thread;import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;public class SynchronizedTest implements Runnable{int num=100;Lock lock = new ReentrantLock();public  void  BuyOranges(int buyNum) {try {String name = Thread.currentThread().getName();lock.lock();if (this.num > buyNum) {System.out.println(name + "买了" + buyNum + "橘子");//更新数量this.num -= buyNum;} else {System.out.println(name + "没有那么多橘子让你买");}}  finally {lock.unlock();}}@Overridepublic void run() {BuyOranges(30);}}

当然我们线程的内容(涉及到并发,安全内容)需要结合我们后面高级内容具体的来谈,在这里只是基础的把知识点内容复述一下。

这篇关于逸学java【初级菜鸟篇】11.多线程【多方位详解】的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/445025

相关文章

JVM 的类初始化机制

前言 当你在 Java 程序中new对象时,有没有考虑过 JVM 是如何把静态的字节码(byte code)转化为运行时对象的呢,这个问题看似简单,但清楚的同学相信也不会太多,这篇文章首先介绍 JVM 类初始化的机制,然后给出几个易出错的实例来分析,帮助大家更好理解这个知识点。 JVM 将字节码转化为运行时对象分为三个阶段,分别是:loading 、Linking、initialization

Spring Security 基于表达式的权限控制

前言 spring security 3.0已经可以使用spring el表达式来控制授权,允许在表达式中使用复杂的布尔逻辑来控制访问的权限。 常见的表达式 Spring Security可用表达式对象的基类是SecurityExpressionRoot。 表达式描述hasRole([role])用户拥有制定的角色时返回true (Spring security默认会带有ROLE_前缀),去

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

Spring Security--Architecture Overview

1 核心组件 这一节主要介绍一些在Spring Security中常见且核心的Java类,它们之间的依赖,构建起了整个框架。想要理解整个架构,最起码得对这些类眼熟。 1.1 SecurityContextHolder SecurityContextHolder用于存储安全上下文(security context)的信息。当前操作的用户是谁,该用户是否已经被认证,他拥有哪些角色权限…这些都被保

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Spring Security 从入门到进阶系列教程

Spring Security 入门系列 《保护 Web 应用的安全》 《Spring-Security-入门(一):登录与退出》 《Spring-Security-入门(二):基于数据库验证》 《Spring-Security-入门(三):密码加密》 《Spring-Security-入门(四):自定义-Filter》 《Spring-Security-入门(五):在 Sprin

Java架构师知识体认识

源码分析 常用设计模式 Proxy代理模式Factory工厂模式Singleton单例模式Delegate委派模式Strategy策略模式Prototype原型模式Template模板模式 Spring5 beans 接口实例化代理Bean操作 Context Ioc容器设计原理及高级特性Aop设计原理Factorybean与Beanfactory Transaction 声明式事物

Java进阶13讲__第12讲_1/2

多线程、线程池 1.  线程概念 1.1  什么是线程 1.2  线程的好处 2.   创建线程的三种方式 注意事项 2.1  继承Thread类 2.1.1 认识  2.1.2  编码实现  package cn.hdc.oop10.Thread;import org.slf4j.Logger;import org.slf4j.LoggerFactory

OpenHarmony鸿蒙开发( Beta5.0)无感配网详解

1、简介 无感配网是指在设备联网过程中无需输入热点相关账号信息,即可快速实现设备配网,是一种兼顾高效性、可靠性和安全性的配网方式。 2、配网原理 2.1 通信原理 手机和智能设备之间的信息传递,利用特有的NAN协议实现。利用手机和智能设备之间的WiFi 感知订阅、发布能力,实现了数字管家应用和设备之间的发现。在完成设备间的认证和响应后,即可发送相关配网数据。同时还支持与常规Sof

JAVA智听未来一站式有声阅读平台听书系统小程序源码

智听未来,一站式有声阅读平台听书系统 🌟&nbsp;开篇:遇见未来,从“智听”开始 在这个快节奏的时代,你是否渴望在忙碌的间隙,找到一片属于自己的宁静角落?是否梦想着能随时随地,沉浸在知识的海洋,或是故事的奇幻世界里?今天,就让我带你一起探索“智听未来”——这一站式有声阅读平台听书系统,它正悄悄改变着我们的阅读方式,让未来触手可及! 📚&nbsp;第一站:海量资源,应有尽有 走进“智听