程序的并行和并发: 并行:在同一个时刻,有多个指令在多个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 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 次随机查询集合中是否包含某个字符串(从集合元素中随机选取),使用同步机制确保查询过程中集合不被修改,统计每个线程的查询成功次数。