1、线程创建方式一:继承Thread类
1.1 步骤:
① 创建一个继承于Thread类的子类
② 重写Thread类的run() 方法 ----> 将此线程要执行的操作,声明在方法体中
③ 创建当前Thread的子类的对象
④ 通过对象调用start() : 1.启动线程 2.调用当前线程的run()
2、线程的创建方式二:实现Runnable接口
2.1步骤:
① 创建一个实现Runnable接口的类
② 实现接口中的run() ----> 将此线程要执行的操作,声明在此方法体中
③ 创建当前类的对象
④ 将此对象作为参数传递到Thread类的构造器中,创建Thread类的实例
⑤ Thread类的调用实例用start() : 1.启动线程 2.调用当前线程的run()
3、对比两种方式
共同点:① 启动线程,使用的都是Thread类中定义的start() ② 创建的线程对象,都是Thread类或其子类的实例
不同点:一个是类的继承,一个是接口的实现。
建议: 建议使用实现Runnable接口的方式。 Runnable方式的好处:① 实现的方式:避免了类的单继承的局限性 ②更适合处理有共享数据的问题 ③ 实现了代码和数据的分离
联系: public class Thread implements Runnable (代理模式)
4.线程的常用结构
4.1线程中的构造器
-public Thread() : 分配一个新的线程对象
-public Thread(String name) : 分配一个指定名字的线程对象
-public Thread(Runnable target):指定创建线程的目标对象,它实现了Runnable接口中的run方法
-public Thread(Runnable target, String name): 分配一个带有指定目标新的线程对象并指定名字
4.2 线程中的常用方法:
> start():① 启动线程 ② 调用线程的run()
>run() : 将线程中要执行的操作,声明在run()中。
>currentThread(): 获取当前执行代码对应的线程
>getName(): 获取线程名
>setName(): 设置线程名
> sleep(long millis): 静态方法,调用时,可以使得当前线程睡眠指定的毫秒数
>yield():静态方法,一旦执行此方法,就释放CPU的执行权
>join(): 在线程a中通过线程b调用join(),意味着线程a进入阻塞状态,直到线程b执行结束,线程a才结束阻塞状态,继续执行
>isAlive() :判断当前线程是否存活
4.3 线程的优先级
getPriority():获取线程的优先级
setPriority():设置线程的优先级。范围[1,10]
Thread类内部声明的三个常量:
- MAX_PRIORITY(10) :最高优先级
- MIN_PRIORITY(1): 最低优先级
- NORM_PRIORITY(5): 普通优先级,默认情况下main线程具有普通优先级
4.4 线程的生命周期
jdk1.5前:
新建 就绪 (堵塞) 运行 死亡
jdk1.5后:
新建 (准备和运行) (堵塞: 计时等待,锁堵塞 无限等待) 死亡
5、线程的安全问题
多个线程操作共享数据,就有可能出现安全问题。
必须保证一个线程在操作ticket时,其他线程必须等待,直到线程a操作ticket结束之后,其他线程才可以进来继续操作ticket。
5.1 java是如何解决线程安全问题的? 使用线程的同步机制
重点关注:共享数据及操作共享数据的代码;同步监视器(保证唯一性)
方式1:同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明:
- 需要被同步的代码,即为操作共享数据的代码
- 共享数据: 即多个线程都需要操作的数据。
- 需要被同步的代码:在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他进程必须等待。
- 同步监视器: 俗称锁。哪个线程获取了锁,哪个线程就能执行需要被同步的代码; 同步监视器可以使用任何一个类的对象充当。但是,多个线程必须共用同一个同步监视器。
注意:在实现Runnable接口的方式中,同步监视器可以考虑使用:this。
在继承Thread类的方式中,同步监视器要慎用this。
方式2:同步方法
说明:如果操作共享的代码完整的声明在了一个方法中,那么我们就可以将此方法声明为同步方法即可。
5.5 sychronized好处:解决了线程的安全问题
弊端:在操作共享数据时,多线程其实是穿行执行的,意味着性能低。
6.ReentrantLock的使用
1.创建Lock的实例,需要确保多个线程共用同一个Lock实例!需要考虑将此对象声明为static final
private static final RentrantLock lpck = new ReentranLock();
2.执行Lock方法,锁定对共享资源的调用
lock.lock();
3.unlock()的调用,释放对共享资源的锁定
lock.unlock();
面试题:
synchronized同步的方式与Lock的对比?
synchronized不管是同步代码块还是同步方法,都需要在结束一对{ } 之后,释放对同步监视器的调用
Lock是通过两个方法控制需要被同步的代码,更灵活一些。
Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高。
7、线程的通信
7.1线程通信的理解
当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调他们的工作,以此实现多线程共同操作一份数据。
7.2涉及到三个方法的使用
wait():线程一旦执行此方法,就进入等待状态。同时,会释放同步监视器的调用。
notify():一旦执行此方法,就会唤醒被wait()的线程中优先级最高的那一个线程。(如果wait()的多个线程的优先级相同,就随机唤醒一个)。被唤醒的线程从当初被wait的位置继续执行。
notifyyAll():一旦执行此方法,就会唤醒所有被wait的线程。
注意点:
- 此三个方法的使用,必须是在同步代码快或同步方法中。 Lock中用别的方法,这里暂不解释
- 此三个方法的调用者,必须是同步监视器。否则,会报IllegalMonitorStateException异常
- 此三个方法声明在object中
7.3.wait() 和sleep()的区别
相同点:
一旦执行,当前线程都会进入阻塞状态
不同点:
-声明的位置:wait():声明在Object类中
sleep():生命在Thread类中,静态的
- 使用的场景不同:wait():只能使用在同步代码块或同步方法中
sleep():可以在任何需要使用的场景
- 使用在同步代码块或方法中:wait():一旦执行,会释放同步监视器
sleep(): 一旦执行,不会释放同步监视器
- 结束阻塞的方式: wait():到达指定时间自动结束阻塞 或 通过notify唤醒,结束阻塞
sleep():到达指定时间自动结束阻塞
新增两种创建线程的方式
1.创建多线程的方式三: 实现Callable(jdk5.0新增的)
与之前的方式对比:与Runnable方式的对比的好处
- call():可以有返回值,更灵活
- call(): 可以使用throws的方式处理异常,更灵活
- Callable(): 使用了泛型参数,可以指明具体的call()的返回值类型,更灵活
缺点: 如果主线程 中需要获取分线程call()的返回值,则此时的主线程是阻塞状态的。
2.创建多线程的方式四:使用线程池
此方法的好处:
- 提高了程序执行的效率(因为线程已经提前创建好了)
- 提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务)
- 可以设置相关参数,对线程池中的线程的使用进行管理