java基础学习--多线程、线程安全、线程进阶、线程池、生产者消费者模式

1 多线程

并行:指两个或多个事件在同一时刻发生(同时发生)。

并发:指两个或多个事件在同一个时间段内发生。

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

线程:进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。

进程与线程的区别

  • 进程有独立的内存空间,进程中的数据存放空间(堆空间和栈空间)是独立的,至少有一个线程。
  • 线程堆空间是共享的,栈空间是独立的,线程消耗的资源比进程小的多。

注意

  • 因为一个进程中的多个线程是并发运行的,那么从微观角度看也是有先后顺序的,哪个线程执行完全取决于 CPU 的调度,程序员是干涉不了的。而这也就造成的多线程的随机性。

  • Java 程序的进程里面至少包含两个线程,主进程也就是 main()方法线程,另外一个是垃圾回收机制线程。每当使用 java 命令执行一个类时,实际上都会启动一个 JVM,每一个 JVM 实际上就是在操作系统中启动了一个线程,java 本身具备了垃圾的收集机制,所以在 Java 运行时至少会启动两个线程。

  • 由于创建一个线程的开销比创建一个进程的开销小的多,那么我们在开发多任务运行的时候,通常考虑创建多线程,而不是创建多进程。

  • 计算机通常只有一个CPU时,在任意时刻只能执行一条计算机指令,每一个进程只有获得CPU的使用权才能执行指令。所谓多进程并发运行,从宏观上看,其实是各个进程轮流获得CPU的使用权,分别执行各自的任务。那么,在可运行池中,会有多个线程处于就绪状态等到CPU,JVM就负责了线程的调度。JVM采用的是抢占式调度,没有采用分时调度,因此可以能造成多线程执行结果的的随机性

多线程代码实现

方式一: 继承Thread类.

  • 定义一个类(MyThread), 继承Thread类.

    public class MyThread extends Thread

  • 重写Thread#run()方法.

  • 把要执行的代码放到run()方法中.

    @Override
    public void run() {

    ​ 要执行的代码}

  • 开启线程.

    MyThread mt = new MyThread();

    ​ mt.start();

注意

​ mt.run(); //这样写不会报错, 但是只是普通的方法调用而已, 并没有开启线程;

​ 对于同一个线程对象,不能调用两次start()方法。会报 IllegalThreadStateException(非法的线程状态异常)

匿名内部类写法

new Thread() {

​ @Override

​ public void run() {

​ 要是执行的代码 }

​ }.start();

方式二: 实现Runnable接口。

  • 定义一个类(MyRunnable), 实现Runnable接口

    public class MyRunnable implements Runnable

  • 重写Runnable#run()方法

  • 把要执行的代码放到run()方法中

    @Override

    ​ public void run() {

    ​ 要执行的代码 }

  • 创建Runnable接口的子类对象, 并将其作为参数传递给Thread类, 创建线程对象

    MyRunnable mr = new MyRunnable();

    Thread th = new Thread(mr);

  • 开启线程

    th.start();

匿名内部类写法

new Thread(new Runnable() {

​ @Override

​ public void run() {

​ 要执行的代码}

​ }).start();

lambda写法

new Thread(() -> {

​ 要执行的代码

​ }).start();

注意:因为Lambda表达式这种方式没有显示的重写run()方法, 所以导致通过start()方法开启线程的时候,找不到对

应的run()方法, 然后当做普通的方法调用了, 偶尔会看见抢资源的情况, 但是几率较小。

thread和runable的区别:如果要开辟多个线程时,需要new多个Thread的自定义子类对象。但runable实现,只需要new一次实现runable接口的子类。通过thread方法来创建多个对象。这就要求第一种方式里,定义的变量应该是静态(static)的,但方式二就不需要,并且因为只new了一次该类,所以synchronized的锁对象可以是this。

方式1开启多个线程:

MyThread mt1 = new MyThread(“窗口1”);

MyThread mt2 = new MyThread(“窗口2”);

方式2开启多个线程:

MyRunnable mr = new MyRunnable();

Thread t1 = new Thread(mr, “窗口1”);

Thread t2 = new Thread(mr, “窗口2”);

方式三: 实现Callable接口,需要结合线程池使用

Thread类的构造方法

  • public Thread():分配一个新的线程对象。
  • public Thread(String name):分配一个指定名字的新的线程对象。
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并指定名字。

Thread类的成员方法

  • public String getName():获取当前线程名称。
  • public void start():导致此线程开始执行;Java虚拟机调用此线程的run方法。
  • public void run():此线程要执行的任务在此处定义代码。
  • public static void sleep(long millis):使当前正在执行的线程以指定的毫秒数暂停(暂时停止执行)。
  • public static Thread currentThread():返回对当前正在执行的线程对象的引用。

Thread.sleep()使用会出现异常,因为父类run方法没有抛异常,因此只能使用try.catch方法来解决。

try {
Thread.sleep(50); //单位是毫秒, 该方法的特点是: 在哪里睡, 到点后就在哪里醒来.

​ } catch (InterruptedException e) {

​ e.printStackTrace(); }

2 线程安全:

概述:如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

原因:线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。Java中提供了同步机制(synchronized)来解决

同步代码块:synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

格式

synchronized(同步锁){

​ 需要同步操作的代码}

注意

  • 锁对象可以是任意类型,多个线程对象要使用同一把锁。

  • 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。

  • 线程同步(安全), 效率相对较低,线程不同步(不安全), 效率相对较高。

死锁:在实现多线程程序时, 不要出现同步代码块嵌套的行为, 否则可能会出现死锁的情况, 死锁指的是多个线程同时抢多把锁, 因为CPU执行线程的随机性, 从而导致线程"卡死"的情况. 即: 死锁至少需要两个线程, 两把锁, 一个线程先抢锁A, 后抢锁B, 而另一个线程先抢锁B, 后抢锁A,则它们就可能发生死锁现象。

线程的生命周期指的是某一个线程从开始创建直至销毁时, 所经历的全部阶段, 主要分为:新建、就绪、运行(运行的时候可能会发生阻塞)、死亡。

线程安全的类

  • StringBuffer

    线程安全,可变的字符序列

    从版本JDK 5开始,被StringBuilder 替代。 通常应该使用StringBuilder类,因为它支持所有相同的操作,但它更快,因为它不执行同步

  • Vector

    从Java 2平台v1.2开始,该类改进了List接口,使其成为Java Collections Framework的成员。 与新的集合实现不同,Vector被同步。 如果不需要线程安全的实现,建议使用ArrayList代替Vector

  • Hashtable

    该类实现了一个哈希表,它将键映射到值。 任何非null对象都可以用作键或者值

    从Java 2平台v1.2开始,该类进行了改进,实现了Map接口,使其成为Java Collections Framework的成员。 与新的集合实现不同,Hashtable被同步。 如果不需要线程安全的实现,建议使用HashMap代替Hashtable

3 线程进阶

线程调度方式

  • 分时调度模型:所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间片
  • 抢占式调度模型:优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些

Java使用的是抢占式调度模型,它具有随机性。假如计算机只有一个 CPU,那么 CPU 在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行是有随机性,因为谁抢到CPU的使用权是不一定的。

优先级相关方法

方法名 说明
final int getPriority() 返回此线程的优先级
final void setPriority(int newPriority) 更改此线程的优先级, 线程默认优先级是5;线程优先级的范围是:1-10

线程控制方法

方法名 说明
static void sleep(long millis) 使当前正在执行的线程停留(暂停执行)指定的毫秒数
void join() 表示加入线程,类似于现实生活中的插队, 当此线程执行完毕后, 其他线程才会接着运行
void setDaemon(boolean on) 将此线程标记为守护线程,当运行的线程都是守护线程时,

守护线程,会随着主线程的消失而消失,但由于线程抢占具有随机性和延迟性,所以当主线程消失时,守护线程不会立即消失。

设置主线程的方法:Thread.currentThread().setName("刘备 ");

Lock锁:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock。Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化

ReentrantLock构造方法

方法名 说明
ReentrantLock() 创建一个ReentrantLock的实例

加锁解锁方法

方法名 说明
void lock() 获得锁
void unlock() 释放锁

4 线程池

概述:就是一个池子(容器), 里边有一些线程对象, 用的时候从里边拿, 用完之后再放回去。

目的: 节约资源, 提高效率。

  • 线程池工厂类:Executors

    public static ExecutorService newFixedThreadPool(int nThreads) 线程池工厂类创建固定线程数的方法

    public static ExecutorService newCachedThreadPool(); 创建线程池, 可根据需求来创建新的线程对象, 针对于生命周期短的线程对象

  • 线程池接口:ExecutorService

    public Futuresubmit(Runnable task) 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第三种方式

    public void shutdown() 关闭线程池

实现多线程的方式三:Callable接口

好处

  • 线程执行后, 可以具有返回值.
  • 可以抛出异常

弊端:该方式只能结合线程池一起使用

接口

  • public Future submit(Callable task) 使用线程池对象方法submit提交线程执行任务,这是实现多线程的第三种方式

Callable和Runable的对比:

  • Callable是另外一种形式的线程执行目标类,相当于Runnable接口.
  • 其中的call方法相当于Runnable中的run方法。
  • 不一样的是,call方法具有返回值,run方法没有返回值。

两者的Future返回值对比:

  • 将方法返回值封装成了对象,结合线程提供了返回结果的更多信息和功能。
  • Future获取具体返回值的方法:Vget()
  • 比如run方法的返回值为void,获取到的值即为null。而返回值的类型是随着submit方法的调用而传入的。

5 生产者消费者模式

概述:所谓生产者消费者问题,实际上主要是包含了两类线程:一类是生产者线程用于生产数据,另一类是消费者线程用于消费数据。

  • 为了解耦生产者和消费者的关系,通常会采用共享的数据区域,就像是一个仓库
  • 生产者生产数据之后直接放置在共享数据区中,并不需要关心消费者的行为
  • 消费者只需要从共享数据区中去获取数据,并不需要关心生产者的行为

object类的等待和唤醒方法:

方法名 说明
void wait() 导致当前线程等待,直到另一个线程调用该对象的 notify()方法或 notifyAll()方法
void notify() 唤醒正在等待对象监视器的单个线程(随机唤醒)
void notifyAll() 唤醒正在等待对象监视器的所有线程

6 设计模式:

**概述:**实际开发中, 我们发现好多模块中的部分功能都是相似的, 每次写很麻烦, 于是我们可以把它们抽取出来, 定义成模型,这样按照模型做出来的东西就是符合某种规范的, 或者具有某些特性的, 这些模型就叫: 设计模式。 设计模式并不是一种语法, 而是前辈们总结的一系列的解决问题的思路和代码的解决方案, 设计模式一共分为 23 种.

分类:

  • 创建型: 5种, 主要是用来创建对象的.

​ 例如: 单例设计模式、工厂方法设计模型.

  • 结构型: 7种, 主要是用来描述类与类之间的关系

​ 例如:适配器设计模式、装饰设计模式

  • 行为型: 11种, 主要指的是事物能够做什么.

​ 例如:消费设计模式、 模板方法设计模式.

----------根据黑马程序员课程的学习总结

你可能感兴趣的:(java,java,学习,安全)