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