第一讲 多线程概述
1、 定义
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。在程序运行时,会被分配一个内存空间,进程就用于标识这个空间,封装单元,线程才是线程中真正执行的哦部分。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。
一个进程中至少有一个线程。
例子:java JVM 启动时会有一个进程java.exe。该进程中至少一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程就称为主线程。
说明:jvm启动时不止一个线程,还有一个负责垃圾回收机制的线程。
2、 多线程
多线程可以使得多个代码“同步”执行。在单核的前提下,多个线程都在获取cpu的执行权,cpu执行到谁,谁就运行。需要明确的是,在某一个时刻,只能有一个线程在运行。
第二讲 创建线程-继承Tread类
1、 创建线程第一种方式:继承Tread类
步骤:
1)定义类继承Tread类;
2)复写Tread类中的run方法;
3)调用线程的start()方法,该方法有两个作用:启动线程、调用run方法;
2、 run和start方法特性
Thread类用于描述线程,该类就定义了一个功能,用于存放线程要运行的代码,该存储功能就是run方法。主线程把运行的代码存储在main方法中。
复写run方法的目的:将自定义的代码存储在run方法中,让线程运行。
3、 练习:创建两个线程,和主线程交替运行
l 定义线程类继承Tread类
l 创建两个线程;
l 启动两个线程;
代码实现如下:
4、 线程运行状态
线程在运行过程中有如下5中状态:创建、运行、冻结、临时状态(阻塞)、消亡。
这几种状态之间的关系如下:
5、 线程在虚拟机中的名称
获取线程名称:
获取线程名称格式:线程对象.getName();
线程都有自己默认的名称:Thread-编号,该编号从0开始。
设置线程名称:
setName()或构造函数设置线程名称。
Thread类的构造函数和为线程设置名称,因此,可以在定义线程类时,调用Thread类的构造函数来设置线程的名称。
Thread类中的方法currentThread()可以获取到当前运行的线程对象。
获取和设置线程名称的意义:当线程多的时候,可以对线程进行管理。
第三讲 创建线程-实现Runnable接口
1、 多个窗口同时卖票例子
创建多个线程,同时执行卖票功能。
利用继承Thread类方法创建线程,实现多个窗口同时卖票,但票的数量必须定义为静态的,否则会出现多卖的情况。然后我们在写代码时,较少定义静态变量,因为静态变量生存周期较长浪费资源。
因此,就引入了第二种创建线程的方式:实现Runnable接口
2、 创建线程的第二种方式——实现Runnable接口
步骤:
1) 定义类实现Runnable接口;
2) 覆盖Runnable接口中的run方法;
目的:将线程要运行的代码存放在该run方法中。
3) 通过Thread类建立线程对象;
4) 将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数;
要将Runnable接口的子类对象传递给Thread类的构造函数。
因为:自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去指定指定对象的run方法。即,明确该run方法所属的对象。
5) 调用Thread类的start方法,并开启线程,并调用Runnable接口子类的run方法;
代码实现如下:
实现方式与继承方式的对比的好处:
l 实现方式可以避免java单继承的局限性,在定义线程时,建议使用实现方式;
l 资源被独立了出来,解决了多窗口卖票的问题;——这个卖票过程只在内存中创建了一个tick,多个卖票线程只能对这个票资源进行操作。
第四讲 多线程安全问题
1、 多线程安全问题概述
问题出现的原因:当多条语句在操作同一个线程共享数据时,一个线程对多条语句执行了一部分,还没有执行完,就被另一个线程参与进来执行,导致共享数据的错误。
解决办法:对多条操作共享数据的语句,只能让一个线程执行完了,才能让其他线程参与进来。
Java中的解决办法:同步代码块
Synchronized(对象)
{
需要被同步的代码
}
说明:对象如同锁,持有锁的线程可以在同步中执行,没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
2、 同步使用
同步使用的前提:必须要有两个或两个以上的线程;必须是多个线程使用同一个锁。
同步的好处:解决了多线程的安全问题;
同步的弊端:多个线程需要判断锁,较为耗费资源;
3、 同步的表现形式
同步代码块
代码示例:
同步函数
4、 同步中的锁
1) 同步代码块的锁可以 是任意的对象;
2) 同步代函数的锁是this(函数所在类的实例对象);
3) 同步静态函数的锁是该类对应的字节码文件对象,类名.class;
第五讲 同步在单例设计模式中的应用
1、 概述
单例设计模式有两种体现方式:饿汉式和懒汉式。
因为懒汉式是函数被调用时才会创建对象,而创建对象的语句不止一条,所以在多线程时容易出现安全隐患。
2、 解决懒汉式安全问题方法
将获取对象的函数定义为同步的,如下:
但每次调用该函数时都需要判断锁,影响运行效率,可以通过双重判断形式来稍微提高运行效率,如下。这样,可以减少锁的次数,但代码增多了。
3、 死锁
同步中嵌套同步,而且同步的锁不一样。而且不同线程都在等待对方的锁。
知识点总结
1、 创建线程有两种方式:继承Thread类;实现Runnable接口
实现Runnable接口可以解决多结成问题,还能将资源独立出来,保证资源的唯一性。
2、 多线程的出现使得很多程序可以“同时”运行,但可能会出现共享数据错误,造成多线程安全问题,此时,可以通过同步机制来进行控制,同步包括:同步代码块、同步函数。
3、 使用同步代码块时,需要明确如下问题:
1) 明确哪些代码是多线程运行代码(run方法中的代码);
2) 明确共享数据;
3) 明确多线程运行代码中哪些是操作共享数据的;
4、 懒汉式同步问题
懒汉式的实例是延迟加载的,而且存在多条操作语句,在多线程情况下可能会出现安全问题。此时可以用同步函数或同步代码块来控制,但每次获取实例时都需要判断锁影响效率,我们可以通过双重判断的方式来减少判断锁的次数,从而稍微的提高了运行效率。但实际应用过程中,建议使用饿汉式。