多线程详解

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.创建多线程的方式四:使用线程池

此方法的好处:

- 提高了程序执行的效率(因为线程已经提前创建好了)

- 提高了资源的复用率。(因为执行完的线程并未销毁,而是可以继续执行其他的任务)

- 可以设置相关参数,对线程池中的线程的使用进行管理

  • 16
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值