|
|
@@ -0,0 +1,197 @@
|
|
|
+程序的并行和并发:
|
|
|
+并行:在同一个时刻,有多个指令在多个CPU(运算器+控制器--计算机核心)上同时执行。
|
|
|
+并发:在同一个时刻,多个指令在单个CPU上交替执行。
|
|
|
+
|
|
|
+程序的进程和线程:
|
|
|
+进程:就是正在运行的程序。
|
|
|
+一个程序一旦运行起来,就会通过输入流进入到内存运行起来。变成了进程。
|
|
|
+独立性:进程是一个能够独立运行的基本单位,同时也是系统分配资源和调度的独立单位。
|
|
|
+动态性:进程的本质是程序的一次执行过程,进程是动态产生,动态消亡。
|
|
|
+并发性:任何的进程都可以和其他进程一起并发执行。
|
|
|
+
|
|
|
+线程:
|
|
|
+ 是进程中的单个顺序控制了,是一条执行路径。
|
|
|
+ 单线程:是一个进程中如果只有一条执行路径,就称为是单线程程序。
|
|
|
+ 多线程:一个进程中如果有多条执行路径,就称为多线程程序。
|
|
|
+
|
|
|
+Java中实现线程的方式:
|
|
|
+方式一:继承Thread类
|
|
|
+
|
|
|
+方法:
|
|
|
+ void run() 当线程开启后,该方法会被调用执行。
|
|
|
+ 是从Thread类继承过来的,目的是让子类重写。
|
|
|
+ void start() 让此线程开始执行,Java虚拟机会自动调用run()方法。
|
|
|
+实现步骤:
|
|
|
+ 1、定义一个类继承Thread类,
|
|
|
+ 2、在子类中重写Thread类中的run()方法
|
|
|
+ 3、创建子类的对象
|
|
|
+ 4、调用start()方法
|
|
|
+
|
|
|
+方式二:实现Runnable接口
|
|
|
+实现步骤:
|
|
|
+ 1、定义一个类实现Runnable接口
|
|
|
+ 2、在实现类中重写run方法
|
|
|
+ 3、创建Thread类的对象,将实现类对象作为构造方法的参数传入
|
|
|
+ 4、调用Thread类的start方法
|
|
|
+
|
|
|
+方式三:实现Callable接口-创建带有返回值的线程
|
|
|
+
|
|
|
+方法:
|
|
|
+ V call() 计算结果,如果无法计算结果则抛出一个异常。
|
|
|
+ FutureTask(Callable<V> callable)
|
|
|
+ 创建一个FutureTask,一旦运行,就会执行传入的参数Callable对象的call方法。
|
|
|
+ V get() 如果又必要,则等待计算完成,然后获取结果。
|
|
|
+
|
|
|
+实现步骤:
|
|
|
+ 1、定义一个类实现Callable接口,并且可以指定泛型
|
|
|
+ 2、在实现类中重写call()方法
|
|
|
+ 3、创建实现类的对象
|
|
|
+ 4、创建FutureTask对象,把Callable的实现类的对象作为FutureTask类的构造方法的参数
|
|
|
+ 5、创建Thread类的对象,把FutureTask对象作为Thread类的构造方法的参数
|
|
|
+ 6、调用Thread对象的start方法,启动线程
|
|
|
+ 7、再调用Future接口的get方法(实际上就是FutureTask对象的get方法),获取线程结束后的计算结果。
|
|
|
+
|
|
|
+线程的方法:
|
|
|
+ void setName(String name) 可以将该线程的名称改为name参数
|
|
|
+ String getName() 获取线程名称
|
|
|
+ Thread currentThread() 获取当前正在执行的线程对象的引用。
|
|
|
+
|
|
|
+ 线程休眠:
|
|
|
+ static void sleep(long ms) 让当前的线程停留(暂停执行)指定的毫秒数。
|
|
|
+
|
|
|
+
|
|
|
+线程的生命周期:
|
|
|
+ 什么是生命周期? 从生到死的过程。 怎么来滴?怎么没滴?
|
|
|
+
|
|
|
+线程优先级:
|
|
|
+ 线程有两种调度方式:
|
|
|
+ 分时调度模型:所有的线程轮流使用CPU的使用权,平均分配每个线程占用的时间片。
|
|
|
+ 抢占式调度模型:
|
|
|
+ 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么就会随机选择一个,
|
|
|
+ 并且优先级比较高的线程获取的时间片也相对多一些。
|
|
|
+ps: java使用的就是抢占式调度模型。
|
|
|
+随机性问题:假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一个指令,线程只有得到了时间片才能得到使用权,
|
|
|
+才可以执行指令。所以多线程的执行是随机性,也就是谁能抢到执行权是不一定的。
|
|
|
+
|
|
|
+final int getPriority() 返回线程的优先级
|
|
|
+final void setPriority(int newPriority) 修改当前线程的优先级,线程的默认优先级是5,优先级的范围:1~10;
|
|
|
+MAX_PRIORITY 10 最高优先级
|
|
|
+MIN_PRIORITY 1 最低优先级
|
|
|
+
|
|
|
+
|
|
|
+守护线程
|
|
|
+ 守护线程随着其他非守护线程的结束而结束。
|
|
|
+ 设置守护线程的方法:void setDaemon(boolean on)
|
|
|
+
|
|
|
+卖票问题:
|
|
|
+- 案例需求
|
|
|
+某电影院目前正在上映国产大片《南京照相馆》,共有100张票,而它有3个窗口卖票,请设计一个程序模拟该电影院卖票
|
|
|
+
|
|
|
+- 实现步骤
|
|
|
+ - 定义一个类SellTicket实现Runnable接口,里面定义一个成员变量:private int tickets = 100;
|
|
|
+ - 在SellTicket类中重写run()方法实现卖票,代码步骤如下
|
|
|
+ - 判断票数大于0,就卖票,并告知是哪个窗口卖的,卖1张票前,sleep(1),模拟出票过程。
|
|
|
+ - 卖了票之后,总票数要减1
|
|
|
+ - 票卖没了,线程停止
|
|
|
+ - 定义一个测试类SellTicketDemo,里面有main方法,代码步骤如下
|
|
|
+ - 创建SellTicket类的对象
|
|
|
+ - 创建三个Thread类的对象,把SellTicket对象作为构造方法的参数,并给出对应的窗口名称
|
|
|
+ - 启动线程
|
|
|
+
|
|
|
+卖票出现了问题:
|
|
|
+ 1、相同的票出现了多次
|
|
|
+ 2、出现了负数票
|
|
|
+原因:为什么出现上面的问题?
|
|
|
+ 线程执行的随机性导致,在卖票的过程中丢失了CPU的执行权,导致的问题。
|
|
|
+
|
|
|
+那么就需要解决多个线程的同步问题,保证线程的数据一致性,线程安全。
|
|
|
+这种问题称为线程的安全问题:
|
|
|
+ 1、有多个线程
|
|
|
+ 2、线程之间的数据是共享的。
|
|
|
+ 3、有多个线程操作共享数据。
|
|
|
+
|
|
|
+实现解决安全问题的方式:
|
|
|
+ 1、把多条语句操作共享数据的代码锁起来。让任何时刻都只有一个线程能执行。
|
|
|
+ 2、Java提供了同步代码块的方式解决问题
|
|
|
+
|
|
|
+解决线程安全的方式:
|
|
|
+ 1、同步代码块 synchronized代码块
|
|
|
+ 2、同步方法 synchronized修饰的方法
|
|
|
+ 3、可重入锁(Lock) ReentrantLock锁
|
|
|
+
|
|
|
+1、同步代码块:
|
|
|
+ synchronized(任意对象){
|
|
|
+ 多条语句操作共享数据的代码
|
|
|
+ }
|
|
|
+ synchronized(任意对象)就相当于给代码加锁了,任意对象就可以看成是一个所。
|
|
|
+
|
|
|
+ 好处:解决多线程数据安全的问题。
|
|
|
+ 弊端:当线程很多的守护,因为每个线程都要判断同步锁,比较耗费资源,会降低程序的执行效率。
|
|
|
+
|
|
|
+2、同步方法:
|
|
|
+ 就是把synchronized关键字加到方法上
|
|
|
+ 格式:
|
|
|
+ 访问权限修饰符 synchronized 返回值类型 方法名(参数){
|
|
|
+ 方法体
|
|
|
+ }
|
|
|
+ 同步方法里的锁对象是当前的对象 this
|
|
|
+ 静态同步方法格式:
|
|
|
+ 访问权限修饰符 static synchronized 返回值类型 方法名(参数){
|
|
|
+ 方法体
|
|
|
+ }
|
|
|
+ 静态同步方法锁对象是这个类 类名.class的对象
|
|
|
+
|
|
|
+同步监视器:
|
|
|
+俗称就是锁,代码块中任何一个类的对象都可以当成锁,但是所有的线程必须要共用同一把锁,共用同一个对象
|
|
|
+如果是同步方法中,这个锁就是this,也就是方法所属对象,静态同步方法的锁是类本身。
|
|
|
+如果是使用同步代码块,恰好使用继承Thread的方式创建线程,那么一定一定不能使用this作为锁。
|
|
|
+
|
|
|
+3、ReentrantLock锁(可重入锁)
|
|
|
+ Lock类本身是一个接口,他有一个实现类ReentrantLock,我们可以创建实现类对象
|
|
|
+ 包含两个常用的方法:
|
|
|
+ 获得锁 void lock()
|
|
|
+ 释放锁 void unlock()
|
|
|
+
|
|
|
+lock锁和synchronized同步代码块的相同点和不同点
|
|
|
+ 1、Lock锁是一种显式锁,需要我们手动的开启和关闭,synchronized是隐式锁,出了作用域之后会自动释放。
|
|
|
+ 2、Lock锁只有代码块锁,synchronized包含代码块锁和方法锁
|
|
|
+ 3、使用Lock锁,Java虚拟机JVM可以花费更少的时间来调度线程,性能更好,并且具有更高的扩展性。
|
|
|
+
|
|
|
+死锁:
|
|
|
+ 线程的死锁是由于两个或多个线程互相持有对方需要的资源,导致这些线程都处在等待状态,不能继续执行。
|
|
|
+产生死锁的原因:
|
|
|
+ 1、资源有限
|
|
|
+ 2、同步嵌套
|
|
|
+需要做的是避免死锁。
|
|
|
+出现死锁后并不会出现异常,也不会出现提示,只是所有的线程都在正常运行,只是都没在工作,而是都在被阻塞而已。
|
|
|
+所以使用线程同步的时候,一定要避免死锁。
|
|
|
+解决方案:
|
|
|
+ 1、设计专门的算法,原则。
|
|
|
+ 2、尽量减少同步资源的定义。
|
|
|
+ 3、尽量避免嵌套同步。
|
|
|
+
|
|
|
+
|
|
|
+List集合的实现类
|
|
|
+ ArrayList LinkedList
|
|
|
+Set集合的实现类
|
|
|
+ HashSet TreeSet
|
|
|
+Map集合的实现类
|
|
|
+ HashMap TreeMap
|
|
|
+以上6个集合实现类都不是线程同步的,都是线程不安全的。
|
|
|
+怎么样让以上的集合变为线程安全的?
|
|
|
+1、使用synchronized进行二次封装
|
|
|
+2、使用可重入锁。
|
|
|
+3、使用Collections工具类,该类里有把线程不安全的集合封装成线程安全的集合的方法。
|
|
|
+
|
|
|
+
|
|
|
+题目 1:多线程操作 HashSet 的线程安全问题
|
|
|
+需求:
|
|
|
+创建 3 个线程,同时向一个 HashSet 中添加 1-100 的整数(每个线程负责不同区间,如线程 1 加 1-33,线程 2 加 34-66,线程 3 加 67-100),观察最终集合大小是否为 100(可能出现重复添加),然后使用 Collections.synchronizedSet () 包装 HashSet,确保线程安全并验证结果。
|
|
|
+
|
|
|
+题目 2:多线程向 TreeSet 添加元素并排序
|
|
|
+需求:
|
|
|
+创建 2 个线程,线程 1 向 TreeSet 中添加 10 个随机偶数,线程 2 添加 10 个随机奇数(范围 1-100),所有线程执行完毕后,遍历 TreeSet 输出元素,验证是否自动按自然顺序排序且无重复。
|
|
|
+
|
|
|
+题目 3:多线程并发查询 HashSet
|
|
|
+需求:
|
|
|
+创建一个包含 50 个字符串的 HashSet,启动 4 个线程,每个线程循环 100 次随机查询集合中是否包含某个字符串(从集合元素中随机选取),使用同步机制确保查询过程中集合不被修改,统计每个线程的查询成功次数。
|