1、昨日复习

  1. 谈谈你对程序、进程、线程的理解

  2. 代码完成继承Thread的方式创建分线程,并遍历100以内的自然数

  3. 代码完成实现Runnable接口的方法创建分线程,并遍历100以内的自然数

  4. 对比两种创建方式

  5. 说说你对IDEA中Project和Module的理解
    workspace project
    project module
    package package
    QQ截图20220112130049.png

    2、线程的生命周期

    QQ截图20220112130421.png
    QQ截图20220112130923.png

    3、线程的同步

    问题的提出:
    多个线程执行的不确定性引起执行结果的不稳定
    多个线程对账本的共享,会造成操作的不完整性,会破坏数据。
    1、问题:买票过程中出现了,重票、错票———->出现了线程的安全问题。
    2、原因:当某个线程操作中,尚未完成时,其他线程参与进来,也操作车票。
    3、如何解决:当一个线程在操作票时,其他线程不能参与进来,直到线程a操作完,其他线程才可以开始操作票,即使线程a出现了阻塞,也不能改变。
    4、java中,通过同步机制,来解决线程的安全问题。


方式一:同步代码块
synchronized(同步监视器){
需要被同步的代码
}
说明:1、操作共享数据的代码,即为需要被同步的代码。—>不能包含代码多了,也不能少了。
2、共享数据:多个线程共同操作的变量。比如:票的数量。
3、同步监视器:锁。任何类的对象都可以充当锁。该对象不能是NULL值。
要求:多个线程必须共用同一把锁
补充:在实现Runnable接口创建多线程的方式中,可以考虑使用this充当同步监视器。
在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当同步监视器


方式二:同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的。
1、同步方法任然涉及到同步监视器,只是不需要我们显示的声明。
2、非静态的同步方法,同步监视器默认是:this
静态的同步方法,同步监视器默认是:当前类本身。


5、同步的方式,解决了线程的安全问题。——>好处。
操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程,效率低。——>局限性


1、死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
2、说明:
(1)出现死锁后,不会出现异常,不会出现报错,只是所有的线程都处于阻塞状态,无法继续
(2)我们使用同步时,要避免出现死锁。
解决方法 :
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步


方式三:Lock锁——-jdk5.0新增
QQ截图20220112211255.png
QQ截图20220112211402.png

  1. package com.atguigu.java3;
  2. /*
  3. 银行有一个账户。
  4. 有两个储户分别向同一个账户存3000元,每次存1000,存3次。每次存完打
  5. 印账户余额。
  6. */
  7. public class AccountTest {
  8. public static void main(String[] args) {
  9. Account acct = new Account(0);
  10. Customer c1 = new Customer(acct);
  11. Customer c2 = new Customer(acct);
  12. c1.setName("甲");
  13. c2.setName("乙");
  14. c1.start();
  15. c2.start();
  16. }
  17. }
  18. class Account {
  19. private double balance;
  20. public Account(double balance) {
  21. this.balance = balance;
  22. }
  23. public synchronized void depoist(double amt){
  24. if (amt>0){
  25. try {
  26. Thread.sleep(1000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. balance+=amt;
  31. System.out.println(Thread.currentThread().getName()+":存钱成功,余额为:"+this.balance);
  32. }
  33. }
  34. }
  35. class Customer extends Thread{
  36. private Account account;
  37. public Customer(Account account) {
  38. this.account = account;
  39. }
  40. @Override
  41. public void run() {
  42. for (int i = 0; i < 3; i++) {
  43. account.depoist(1000);
  44. }
  45. }
  46. }

4、线程的通信

QQ截图20220114141602.png
涉及到的三个方法:
wait():一旦执行该方法,当前线程就进入阻塞状态,并释放同步监视器。
notify():一旦执行该方法,就会唤醒wait的一个线程,如果有多个wait的线程,就唤醒优先级高的
notifyAll():一旦执行该方法,就会唤醒所有的被wait的线程。
说明:
1、三个方法必须使用在同步代码块或同步方法中。
2、这三个方法的调用者必须是同步代码块或同步方法中的同步监视器,否则会出异常。
3、这三个方法是定义在Object类中的


面试题:sleep()和wait()的异同
1、相同点:一旦执行方法,都可以使得当前线程进入阻塞状态
2、不同点:(1)两个方法声明的位置不同:Thread类中声明sleep(),Object()类中声明wait()
(2)调用的范围不同:sleep()可以在任何需要的场景下调用,wait()必须使用在同步代码块 或同步方法中。
(3)关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中,sleep不会释放 锁,wait会释放锁。

  1. package com.atguigu.java4;
  2. /*
  3. 生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处
  4. 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图
  5. 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通
  6. 知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如
  7. 果店中有产品了再通知消费者来取走产品。
  8. 分析:
  9. 1、是否是多线程?是
  10. 2、是否有共享数据?是
  11. 3、如何解决线程的安全?三种
  12. 4、是否涉及到线程的通信?是
  13. */
  14. public class ProductTest {
  15. public static void main(String[] args) {
  16. Clerk clerk= new Clerk();
  17. Productor p1 = new Productor(clerk);
  18. p1.setName("生产者1");
  19. Customer c1 = new Customer(clerk);
  20. c1.setName("消费者1");
  21. Customer c2 = new Customer(clerk);
  22. c2.setName("消费者2");
  23. p1.start();
  24. c1.start();
  25. c2.start();
  26. }
  27. }
  28. class Clerk {
  29. private int productCount=0;
  30. public synchronized void produceProduct() {//this
  31. if (productCount<20){
  32. productCount++;
  33. System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
  34. notify();
  35. }else {
  36. try {
  37. wait();
  38. } catch (InterruptedException e) {
  39. e.printStackTrace();
  40. }
  41. }
  42. }
  43. public synchronized void consumeProduct() {//this
  44. if (productCount>0){
  45. System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
  46. productCount--;
  47. notify();
  48. }else {
  49. try {
  50. wait();
  51. } catch (InterruptedException e) {
  52. e.printStackTrace();
  53. }
  54. }
  55. }
  56. }
  57. class Productor extends Thread {
  58. private Clerk clerk;
  59. public Productor(Clerk clerk) {
  60. this.clerk = clerk;
  61. }
  62. @Override
  63. public void run() {
  64. System.out.println(getName()+":开始生产产品。。。");
  65. while (true){
  66. try {
  67. sleep(10);
  68. } catch (InterruptedException e) {
  69. e.printStackTrace();
  70. }
  71. clerk.produceProduct();
  72. }
  73. }
  74. }
  75. class Customer extends Thread {
  76. private Clerk clerk;
  77. public Customer(Clerk clerk) {
  78. this.clerk = clerk;
  79. }
  80. @Override
  81. public void run() {
  82. System.out.println(getName()+":开始消费产品。。。");
  83. while (true){
  84. try {
  85. sleep(20);
  86. } catch (InterruptedException e) {
  87. e.printStackTrace();
  88. }
  89. clerk.consumeProduct();
  90. }
  91. }
  92. }

5、jdk新增线程创建方式

新增方式一:实现Callable接口
与使用Runnable相比, Callable功能更强大些
相比run()方法,可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果

Future接口
可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask是Futrue接口的唯一的实现类 。
FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值

1、创建一个实现Callable的实现类
2、实现call方法,将此线程需要执行的操作声明在call中
3、创建一个Callable接口实现类的对象
4、将此Callable接口实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask的对象
5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法。
6、获取Callable中call方法的返回值,调用FutureTask的对象.get();方法。

如何理解实现Callable接口的方法创建多线程比实现Runnable接口创建多线程方式强大?
1、call()可以有返回值
2、call()可以抛出异常,被外面的操作捕获,获取异常的信息
3、call()是支持泛型的


新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交通工具。
好处:
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
corePoolSize:核心池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后会终止
QQ截图20220114190014.png
1、提供指定线程数量的线程池。
2、执行指定的线程的操作,需要提供实现Runnable接口或Callable接口实现类的对象。
3、关闭线程池。