1. 进程和线程
进程
是程序的一次执行过程,或是正在运行的一个程序;是动态的过程,有它自身的产生、存在和消亡的过程(生命周期)
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(是一个程序内部的一条执行路径)
若一个进程同一时间并行执行多个线程,就是支持多线程的;
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间,从同一堆中分配对象,可以访问相同的变量和对象。
2. 创建线程
方法4:使用线程池(经常创建销毁使用量特别大的资源,比如并发情况下的线程,对性能影响很大)
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完后放回线程池中,可以避免频繁创建销毁,实现重复利用;
好处:
提高响应速度:减少创建新线程的时间
降低资源消耗:重复利用线程池中线程,不需要每次创建
便于线程管理:corePoolSize(核心池的大小)、maximumPoolSize(最大线程数)、keepAliveTime(线程没有任务时最多保持多久会终止);
步骤:
a. 提供指定线程数量的线程池(可以在之后设置线程池属性)
b. 执行指定的线程操作,需要提供实现 Runnable 接口或 Callable 接口实现类的对象
c. 关闭连接池
方法1:
a. 创建一个继承于 Thread 类的子类
b. 重写 Thread 类的 run()方法
c. 创建 Thread 类的子类对象
d. 通过此对象调用 start()
① 启动当前线程
② 调用当前线程的 run()
方法2:
a. 创建一个实现了 Runnable 接口
b. 实现类去实现 Runnable 接口的类
c. 创建实现类的对象
d. 将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对象
e. 通过 Thread 类的对象调用 start()
方法1和2的对比:(优先选择 Runnable 接口,原因如下)
a. 实现的方式没有类的单继承性的局限性
b. 实现的方式更适合来处理多个线程有共享数据的情况
联系:两种都需要重写 run();将线程要执行的逻辑声明在 run()中;
新增方法3:使用 Callable 接口
a. 创建一个实现 Callable 类的实现类
b. 实现 call 方法,将此线程需要执行的操作声明在 call 中
c. 创建 Callable 接口实现类对象
d. 将此 Callable 接口实现类的对象作为传递到 FutureTask 构造器中,创建 FutureTask 的对象
e. 将 FutureTask 的对象作为参数传递到 Thread 类的构造器中,创建 Thread 对象,并调用 start();
f. FutureTask 对象的 get() 方法获取 Callable 中 call 方法的返回值;
较之Runnable接口优势在于:可以有返回值、方法可以抛出异常、支持泛型返回值、需要借助FutureTask类(比如获取返回结果);
Future 接口
a. 可以对具体 Runnable、Callable 任务的执行结果进行取消、查询是否完成、获取结果等
b. FutureTask 是 Future 接口的唯一实现类
c. FutureTask同时实现Runnable、Future接口,既可以作为Runnable被线程执行,又可以作为Future得到Callable返回值;
3. 常用的 Thread 方法
yield():释放当前cpu的执行权
join():在线程A中调用线程B的join(),此时线程A就进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞;
4. 线程的调度
同优先级线程组成先进先出队列,使用时间片策略(等时间轮流);
高优先级优先调度的抢占式策略(高优先级线程抢占CPU);
5. 线程的优先级
MAX_PRIORITY:10、MIN_PRIORITY:0、NORM_PRIORITY:5
高优先级优先调度,不意味着低优先级不能调度,只是被调度到的概率低;
6. 线程的生命周期:
Thread.State 类定义了线程的状态(NEW、RUNNABLE、BLOCKED、WAITING、TIMED_WAITING、TERMINATED)
重新分为5种:
新建:Thread类或其子类对象被声明并创建时;
就绪:新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时已具备运行条件只是没有分配到资源;
运行:就绪的线程被调度并获得CPU资源,进入运行状态,run()方法定义线程操作及功能;
阻塞:某种特殊情况下,线程被认为挂起或执行输入输出操作,让出CPU并临时终止自己执行,进入阻塞;
死亡:线程完成全部工作或被提前强制性终止或出现异常;
7. 在 Java 中通过同步机制来解决线程的安全问题:
方式一:同步代码块
synchronized(同步监视器) { //需要被同步的代码 }
说明:
a. 操作共享数据的代码,即为需要被同步的代码
b. 共享数据:多个线程共同操作的变量
c. 同步监视器:锁;任何一个类的对象都可以充当锁(要求多个线程必须要公用一把锁)
解决了线程的安全问题,但是操作同步代码时只有一个线程参与,其他线程等待,相当于单线程,效率低;
方式二:同步方法(如果操作共享数据的代码完整的声明在一个方法中,不妨声明此方法是同步的)
说明:
a. 同步方法仍然涉及到同步监视器,只是不需要我们显示的声明;
b. 非静态的同步方法,同步监视器是 this;
静态的同步方法,同步监视器是当前类本身;
方式三:Lock锁
Lock锁: ReentrantLock类(实现了类Lock,拥有与synchronized相同的并发性和内存语义,可以显式加锁、释放锁)
说明:
a. 实例化 ReentrantLock类
b. 给共享数据加锁:Lock()
c. 给共享数据解锁:unLock()
8. synchronized 与 Lock
异同(使用Lock锁,JVM将花费少量时间来调度线程,性能更好,具有更好的扩展性(提供更多子类))
同:都可以解决线程安全问题
异:synchronized 机制在执行完相应的同步代码以后自动释放同步监视器
Lock 需要手动的启动同步(Lock())结束也需要手动实现(unlock())
使用优先顺序:Lock -> 同步代码块(已经进入方法体,分配了相应资源)-> 同步方法(在方法体外)
9. 线程的通信
涉及到的三个方法:
wait():一旦执行,当前线程进入阻塞状态,并释放同步监视器;
notify():一旦执行,就会唤醒被wait的一个线程,如果有多个线程被wait,就唤醒优先级最高的那个
notifyAll():一旦执行,唤醒所有线程
说明:
a. wait()、notify()、notifyAll() 必须使用在同步方法或同步代码块中;
b. wait()、notify()、notifyAll() 方法的调用者必须是同步代码块或同步方法中的同步监视器;
c. wait()、notify()、notifyAll() 是定义在 Object 类中
10. wait 和 sleep 的异同
同:执行方法都可以是当前线程进入阻塞
异:1)两个方法声明的位置不同:Thread 类中声明 sleep(),Object 类中声明 wait();
2)调用的要求不同:sleep() 可以在任何需要的场景下调用,wait() 必须使用在同步代码块或同步方法中;
3)是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep()不会释放锁,wait()会;
标签:同步,Java,Thread,创建,线程,days02,多线程,方法,wait From: https://www.cnblogs.com/LinxhzZ/p/16746361.html