JAVA线程Java线程
定西补一下线程的知识.
创建和启动线程
概述
JVM允许程序运行多个线程,使用java.lang.Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例
Thread类的特性
- 每个线程都是通过某个特定Thread对象的run()方法完成操作的,因此把run()方法体称为
线程执行体 
- 通过该Thread对象的start()方法来启动这个线程,而非直接调用run()
 
- 要想实现多线程,必须在主线程中创建新的线程对象
 
继承Thread类
- 创建一个继承Thread类的子类
 
- 重写Thread类的run()方法,将此线程要执行的操作,声明在此方法体中
 
- 创建当前Thread的子类对象
 
- 通过对象调用start()方法
 
创建线程,打印100以内的偶数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
   | public class Test1 {     public static void main(String[] args) {         Thread1 t1 = new Thread1();         
 
 
 
          t1.start();     } }
  class Thread1 extends Thread {     @Override     public void run() {         for (int i = 0; i <= 100; i++) {             if (i % 2 == 0) {                 System.out.println(i);             }         }     } }
  | 
 
start()方法的作用
- 启动线程
 
- 调用当前线程的run()方法
 
问题1,如果不使用start而使用run,会发生什么?能实现分线程的创建和调用吗-不能
从运行结果可以看出,t1线程根本没有启动,而是去调用run()这个普通的方法,执行完之后,再回到主线程继续执行

问题2,如果创建两个分线程,启动两个分线程,该怎么操作?
调用两次start是不行的,会报错,所以,一个线程不能重复的start()

所以要想再创建一个分线程,可以再创建一个线程对象
1 2 3 4
   | Thread1 t1 = new Thread1(); Thread1 t2 = new Thread1(); t1.start(); t2.start();
   | 
 
还可以使用匿名内部类的形式创建线程,或者说,一般都是这么创建线程的
1 2 3 4 5 6 7 8 9 10 11
   |  new Thread() {     @Override     public void run() {         for (int i = 0; i <= 100; i++) {             if (i % 2 == 0) {                 System.out.println(i + Thread.currentThread().getName() + "*");             }         }     } }.start();
 
  | 
 
实现Runnable接口
- 创建一个实现Runnable接口的类
 
- 实现(重写)接口中的run方法
 
- 创建当前实现类的对象
 
- 将此对象作为参数传递到Thread构造器中,创建Thread类的实例
 
- 通过调用Thread实例的start()方法
 
实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
   | public class TestRunnable implements Runnable {
           @Override     public void run() {         for (int i = 0; i <= 100; i++) {             if (i % 2 != 0) {                 System.out.println(i + Thread.currentThread().getName() + "*");             }         }     } }
  class Test {     public static void main(String[] args) {         TestRunnable runnable = new TestRunnable();         Thread t1 = new Thread(runnable);         t1.start();     } }
  | 
 
也可以使用匿名内部类的形式创建
1 2 3 4 5 6 7 8 9 10
   | new Thread(new Runnable() {     @Override     public void run() {         for (int i = 0; i <= 100; i++) {             if (i % 2 != 0) {                 System.out.println(i + Thread.currentThread().getName() + "*");             }         }     } }).start();
  | 
 
这里就可以使用lambda来简化代码了
1 2 3 4 5 6 7 8
   |  new Thread(() -> {     for (int i = 0; i <= 100; i++) {         if (i % 2 != 0) {             System.out.println(i + Thread.currentThread().getName() + "*");         }     } });
 
  | 
 
想要使用这种lambda表达式,要满足以下条件:
- 目标接口是函数式接口:Lambda表达式通常用于创建实现函数式接口的对象。函数式接口是只包含一个抽象方法的接口。例如,Runnable接口就是一个函数式接口,因为它只有一个抽象方法run()。
 
- 目标接口是函数式接口:Lambda表达式通常用于创建实现函数式接口的对象。函数式接口是只包含一个抽象方法的接口。例如,Runnable接口就是一个函数式接口,因为它只有一个抽象方法run()。
 
- Lambda表达式的语法:Lambda表达式的语法通常如下:
 
两种方式的对比
- 共同点
- 启动线程都要用Thread类中的start()方法
 
- 创建的线程对象都是
new Thread或Thread子类对象 
 
- 不同点
- 一个是类的继承,一个是接口的实现
 
 
建议使用实现runnable接口的形式
实现Runnable接口的好处:
- 避免类的单继承的局限性
 
- 更适合处理有共享数据的问题
 
- 代码逻辑和数据分离
 
联系:public class Thread implements Runnable{}
查看源码,会发现Thread也是实现了Runnable接口
Thread类的常用结构
Thread的构造器
常用的构造器:
public Thread():分配一个新的线程对象 
public Thread(Runnable target):分配一个指定名字的新的线程对象 
public Thread(String name):指定创建线程的目标对象,它实现了Runnbale接口中的run()方法 
public Thread(Runnable target, String name):分配一个带有指定目标新的线程对象并指定名字 
对于public Thread(String name),可以给继承Thread类的子类增加一个有参的构造方法:XxxThread(String name),然后调用super(),即可调用到父类Thread的Thread(String name)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
   | public class Test1 {
      public static void main(String[] args) {         OddThread t1 = new OddThread("线程一");
          t1.start();
          for (int i = 0; i <= 100; i++) {             if (i % 2 == 0) {                 System.out.println(i + Thread.currentThread().getName());             }         }
      } }
  class OddThread extends Thread {
      
 
 
 
      public OddThread(String name) {         super(name);     }
      @Override     public void run() {         for (int i = 0; i <= 100; i++) {             if (i % 2 == 0) {                 System.out.println(i + Thread.currentThread().getName() + "*");             }         }     } }
  | 
 

对于public Thread(Runnable target, String name),就是在将接口实现类的对象放入Thread构造方法中,增加一个name
1 2 3 4 5 6 7 8 9 10 11 12 13 14
   | public class ThreadCon implements Runnable {     @Override     public void run() {         System.out.println("线程执行了: " + Thread.currentThread().getName());     } }
  class TesCon {     public static void main(String[] args) {         ThreadCon threadCon = new ThreadCon();         Thread t1 = new Thread(threadCon, "线程一");         t1.start();     } }
  | 
 
即可实现给线程命名:

常用方法
- start():
 
- run():
 
- currentThread()
 
- getName()
 
- setName()
 
- sleep(long mills)
 
- yield()
 
- join()
- 在a线程中通过b线程调用的
join方法,来使a线程进入阻塞状态,直到b线程执行结束 
- 下面这个代码就是,主线程中通过
t1.join();,来使主线程进入阻塞状态,直至t1执行完毕1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
   | public static void main(String[] args) {     OddThread t1 = new OddThread("线程一");     t1.start();     for (int i = 0; i <= 100; i++) {         if (i % 2 == 0) {             System.out.println(i + Thread.currentThread().getName());         }         if (i == 20) {             try {                 t1.join();             } catch (InterruptedException e) {                 e.printStackTrace();             }         }     } }
  | 
 
 
- isAlive()
 
- set/getPriority()
- 获取/设置优先级,范围
[1-10],越大优先级越高,优先级越高,被CPU执行的概率就越大 
 
生命周期
JDK1.5之前是这样的:
线程的状态转换

JDK1.5及之后,状态发生了变化

- 新建(NEW)
 
- 可运行(Runnable)
 
- 锁阻塞(BLOCKED)
 
- 计时等待(TIMED_WAITNG)
 
- 无限等待(WAITING)
 
- 死亡(TERMINATED)

 
大概就是阻塞变得复杂了
线程安全问题
当我们使用多个线程访问同一资源(可以是同一个变量,同一个文件,同一条记录等)的时候,若多个线程只有读操作,那么不会发生线程安全问题,但是如果多个线程中资源有读和写的操作,就容易出现线程安全问题
之前的项目里都几乎是单线程操作,因为测试也只有一个人进行测试
一个简单的案例,卖票
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
   | class SaleTicket implements Runnable {
      int ticket = 100;
      @Override     public void run() {         while (true) {             if (ticket > 0) {                 System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);                 ticket--;             } else {                 break;             }         }     } }
  public class WindowTest {
      public static void main(String[] args) {         SaleTicket saleTicket = new SaleTicket();         Thread t1 = new Thread(saleTicket, "窗口一");         Thread t2 = new Thread(saleTicket, "窗口二");         Thread t3 = new Thread(saleTicket, "窗口三");         t1.start();         t2.start();         t3.start();     }
  }
  | 
 
运行代码会发现,卖出的票很明显超过100张,且有可能卖到-1(加sleep),也就是重票或者错票
什么原因导致的?线程1操作ticket的过程中,尚未结束的情况下,其他线程也参与进来,对ticket进行操作
如何解决:必须保证一个线程a在操作ticket的过程中,其他线程必须等待,直到线程a操作ticket结束以后,其他线程才可以进行操作
Java是如何解决线程的安全问题的:使用线程的同步机制
同步代码块
- 介绍
synchronized(同步监视器){需要被同步的代码} 
- 需要被同步的代码,即为操作共享数据的代码
 
- 共享数据,即多个线程需要操作的数据,比如上面的ticket
 
- 需要被同步的代码,在被synchronized包裹以后,就使得一个线程在操作这些代码的过程中,其他线程必须等待
 
- 同步监视器,锁,哪个线程获取了锁,哪个线程就能执行需要被同步的代码
 
- 同步监视器,可以使用任何一个类的对象充当,但是多个线程必须公用同一个同步监视器,必须唯一
 

 
- 关于同步监视器对象的选用,当线程是基于实现的,可以用this,而是基于继承的,可以用Class对象
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87
   | class SaleTicket implements Runnable {
      int ticket = 100;
      Object obj = new Object();
      @Override     public void run() {         while (true) {
              try {                 Thread.sleep(5);             } catch (InterruptedException e) {                 e.printStackTrace();             }
                                        synchronized (obj) {                 if (ticket > 0) {
                      try {                         Thread.sleep(10);                     } catch (InterruptedException e) {                         e.printStackTrace();                     }
                      System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);                     ticket--;                 } else {                     break;                 }             }         }     } }
  public class WindowTest {
      public static void main(String[] args) {         SaleTicket saleTicket = new SaleTicket();         Thread t1 = new Thread(saleTicket, "窗口一");         Thread t2 = new Thread(saleTicket, "窗口二");         Thread t3 = new Thread(saleTicket, "窗口三");         t1.start();         t2.start();         t3.start();     }
  }
 
  class SaleTicket2 extends Thread {
      static int ticket = 100;
      static Integer t = ticket;
      @Override     public void run() {         while (true) {             synchronized (WindowTest2.class) {                 if (ticket > 0) {                     System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);                     ticket--;                 } else {                     break;                 }             }         }     } }
  public class WindowTest2 {     public static void main(String[] args) {         SaleTicket2 s1 = new SaleTicket2();         s1.setName("窗口一");         SaleTicket2 s2 = new SaleTicket2();         s2.setName("窗口二");         SaleTicket2 s3 = new SaleTicket2();         s3.setName("窗口三");
          s1.start();         s2.start();         s3.start();     } }
  | 
 
 
同步方法
如果操作共享数据的代码完整的声明在一个方法中,那么可以将此方法声明为同步方法即可
如下,即为一个同步方法,实现的效果也是一样的.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
   |  @Override public void run() {     while (isFlag) {         show();     } }
  public synchronized void show() {     if (ticket > 0) {
          try {             Thread.sleep(10);         } catch (InterruptedException e) {             e.printStackTrace();         }
          System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);         ticket--;     } else {         isFlag = false;     } }
 
  | 
 
但是这种写法,没有声明同步监视器,那现在的同步监视器是什么?
如果同步方法是非静态的,那么同步监视器默认就是this,且不能修改
上面是基于实现的线程,下面是基于继承的:
如果还这样写,会发现还是线程不安全的,因为这里的this并不唯一,下面会创建三个线程对象,所以this不唯一,也就不能作为同步监视器
1 2 3 4 5 6 7 8 9 10 11 12 13
   | public synchronized void show() {     if (ticket > 0) {         try {             Thread.sleep(10);         } catch (InterruptedException e) {             e.printStackTrace();         }         System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);         ticket--;     } else {         isFlag = false;     } }
  | 
 
所以要么将方法改成static,但是有些情况并不能改为static,所以当某些情况不满足同步方法时,不要强行使用,还是可以在方法中使用同步代码块.
总结
synchronized的好处:解决了线程安全问题
缺点:效率变低,在操作共享数据时,多线程其实是串行执行的,性能低
练习题,模拟用户存钱:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
   | package com.zzmr.threadsafe;
 
 
 
 
 
  class Account {     private double balance;
      public double getBalance() {         return balance;     }
      public void setBalance(double balance) {         this.balance = balance;     }
      public synchronized void deposit(double amt) {         if (amt > 0) {             balance += amt;         }
          try {             Thread.sleep(1000);         } catch (InterruptedException e) {             e.printStackTrace();         }
          System.out.println(Thread.currentThread().getName() + "存钱1000块,余额为:" + balance);     } }
  class Customer extends Thread {     Account account;
      public Customer(Account account) {         this.account = account;     }
      public Customer(Account account, String name) {         super(name);         this.account = account;     }
      @Override     public void run() {
          for (int i = 0; i < 3; i++) {             account.deposit(1000);         }
      } }
  public class Exer {
      public static void main(String[] args) {         Account account = new Account();         Customer c1 = new Customer(account, "客户一");         Customer c2 = new Customer(account, "客户二");         c1.start();         c2.start();     }
  }
   | 
 
线程安全的懒汉式
埋个坑:单例和多例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45
   | public class BankTest {
      static Bank b1 = null;     static Bank b2 = null;
      public static void main(String[] args) {         Thread t1 = new Thread(() -> b1 = Bank.getInstance());
          Thread t2 = new Thread(() -> b2 = Bank.getInstance());         t1.start();         t2.start();
          try {             t1.join();             t2.join();         } catch (InterruptedException e) {             e.printStackTrace();         }
          System.out.println(b1);         System.out.println(b2);         System.out.println(b1 == b2);     }
  }
  class Bank {     private Bank() {
      }
      private static Bank instance = null;
      public static synchronized Bank getInstance() {         if (instance == null) {             try {                 Thread.sleep(1000);             } catch (InterruptedException e) {                 e.printStackTrace();             }             instance = new Bank();         }         return instance;     } }
  | 
 
用同步方法来解决单例模式的线程安全问题
死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都等待对方放弃自己需要的同步资源,就形成了线程的死锁
一个简单的死锁例子
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49
   | public class DeadBlockTest {
      public static void main(String[] args) {         StringBuilder s1 = new StringBuilder();         StringBuilder s2 = new StringBuilder();
          new Thread(() -> {             synchronized (s1) {                 s1.append("a");                 s2.append("1");
                  try {                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }
                  synchronized (s2) {                     s1.append("b");                     s2.append("2");                     System.out.println(s1);                     System.out.println(s2);                 }
              }         }).start();
          new Thread(() -> {             synchronized (s2) {                 s1.append("c");                 s2.append("3");
                  try {                     Thread.sleep(100);                 } catch (InterruptedException e) {                     e.printStackTrace();                 }
                  synchronized (s1) {                     s1.append("d");                     s2.append("4");                     System.out.println(s1);                     System.out.println(s2);                 }
              }         }).start();     } }
  | 
 
第一个线程执行,锁住s1,当需要用到s2时,线程2又锁到了s2,需要s1,这就导致两个线程都不能执行下去
诱发死锁的原因
- 互斥条件
 
- 占用且等待
 
- 不可抢占
 
- 循环等待
 
以上4个条件,同时出现触发死锁
解决死锁
死锁一旦出现,基本很难人为干预,只能尽量规避,可以考虑打破上面的诱发条件
- 针对条件1,互斥条件基本上无法被破坏,因为线程需要通过互斥解决安全问题
 
- 针对条件2,可以考虑一次性申请所有所需的资源,这样就不存在等待的问题
 
- 针对条件3,占用部分资源的线程在进一步申请其他资源时,如果申请不到,就主动释放掉已经占用的资源
 
- 针对条件4,可以将资源改为线性顺序,申请资源时,先申请序号较小的,这样避免循环等待的问题
 
Lock的使用
除了使用synchronized同步机制处理线程安全问题之外,还可以使用JDK5.0提供的Lock锁的方式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
   | class SaleTicket2 extends Thread {
      static int ticket = 100;
      private static final ReentrantLock lock = new ReentrantLock();
      @Override     public void run() {         while (true) {
              try {                                  lock.lock();                 if (ticket > 0) {                     System.out.println(Thread.currentThread().getName() + "售票,票号为:" + ticket);                     ticket--;                 } else {                     break;                 }             } finally {                                  lock.unlock();             }         }     } }
  | 
 
lock和unlock中间的,其实就是之前放到同步代码块中的代码
需要确保ReentrantLock的对象唯一,公用,需要考虑将此对象声明为static final(如果是实现,可能就不需要了)
synchronized和lock对比
- synchronized不管是同步代码块还是同步方法,都需要在结束一对
{}之后,释放对同步监视器的调用,Lock是通过两个方法控制需要被同步的代码,更灵活一些 
- Lock作为接口,提供了多种实现类,适合更多更复杂的场景,效率更高
 
线程的通信
为什么要处理线程间通信
当我们需要多个线程来共同完成一件任务,并且我们希望他们有规律的执行,那么多线程之间需要一些通信机制,可以协调它们的工作,以此实现多线程共同操作一份数据
比如:线程A用来生产包子,线程B用来吃包子,包子可以理解为同一资源,线程A与线程B处理的动作,一个是生产者,一个是消费者,此时B线程必须等到A线程完成后才能执行,那么线程A与线程B之间就需要线程通信,即等待唤醒机制
等待唤醒机制
这是多个线程间的一种协作机制,谈到线程我们常常想到的是线程间的竞争(race),比如去争夺锁,但这并不是故事的全部,线程间也会有协作机制
在一个线程满足某个条件时,就进入等待状态wait()/wait(time),等待其他线程执行完他们的指定代码过后再将其唤醒notify()或者可以指定wait的时间,等时间到了自动唤醒,在有多个线程进行等待时,如果需要,可以使用notifyAll()来唤醒所有等待线程,wait/notify就是线程间的一种协作机制
案例1
使用两个线程打印1-100,线程1,线程2交替打印
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
   | class PrintNumber implements Runnable {
      private int number = 1;
      @Override     public void run() {         while (true) {             try {                 Thread.sleep(100);             } catch (InterruptedException e) {                 e.printStackTrace();             }             synchronized (this) {                 notify();                 if (number <= 100) {                     System.out.println(Thread.currentThread().getName() + ": " + number);                     number++;                     try {                                                  wait();                     } catch (InterruptedException e) {                         e.printStackTrace();                     }                 } else {                     break;                 }             }         }     } }
  public class PrintNumberTest {     public static void main(String[] args) {         PrintNumber printNumber = new PrintNumber();         Thread t1 = new Thread(printNumber, "线程1");         Thread t2 = new Thread(printNumber, "线程2");         t1.start();         t2.start();     } }
  | 
 
主要流程就是,当线程一执行到wait时,会进入等待状态,且释放掉同步监视器,而后线程2拿到同步监视器,先执行了notify,这个方法会唤醒线程一,而后打印,再执行wait,线程2等待,此时线程一拿到同步监视器,后从等待的地方继续开始执行,以此往复,即可实现交替打印1-100
下面是三个方法的介绍,三个方法必须使用在同步代码块或者同步方法中,不能使用在lock中,且此方法的调用者,必须是同步监视器
wait():线程一旦执行此方法,就进入等待状态,同时,会释放对同步监视器的调用 
notify()一旦执行,就会唤醒被wait()的线程中优先级最高的那个,如果多个线程的优先级相同,则随机唤醒一个,被唤醒的线程从wait的位置继续执行  
notifyAll(),一旦执行此方法,就会唤醒所有被wait的方法 
wait和sleep的区别
相同点:一旦执行,当前线程都会进入阻塞状态
不同点
- 声明的位置
- wait():声明在Object类中
 
- sleep():声明在Thread类中,静态的
 
 
- 使用的场景不同
- wait()只能使用在同步代码块/同步方法中
 
- sleep()使用在任何地方.
 
 
- 使用在同步代码块/同步方法中
- wait()会释放同步监视器
 
- sleep()不会释放同步监视器
 
 
- 结束阻塞的方法
- wait,到达指定时间自动结束阻塞,或通过notify唤醒
 
- sleep,到达指定时间自动结束阻塞
 
 
生产者消费者案例
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产,如果店员中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费着来取走产品
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109
   | package com.zzmr.communication;
 
 
 
 
 
  class Clerk {     private int productNumber = 0;
           public synchronized void addProduct() {         if (productNumber >= 20) {                          try {                 wait();             } catch (InterruptedException e) {                 e.printStackTrace();             }         } else {             productNumber++;                          notifyAll();             System.out.println(Thread.currentThread().getName() + "生产了第" + productNumber + "个产品");         }     }
           public synchronized void minusProduct() {         if (productNumber <= 0) {             try {                 wait();             } catch (InterruptedException e) {                 e.printStackTrace();             }         } else {             System.out.println(Thread.currentThread().getName() + "消费了第" + productNumber + "个产品");             productNumber--;                          notifyAll();         }     }
  }
  class Producer extends Thread {
      private Clerk clerk;
      public Producer(Clerk clerk) {         this.clerk = clerk;     }
      @Override     public void run() {         while (true) {             System.out.println("生产者开始生产产品");
              try {                 Thread.sleep(50);             } catch (InterruptedException e) {                 e.printStackTrace();             }
              clerk.addProduct();         }
      } }
  class Consumer extends Thread {
      private Clerk clerk;
      public Consumer(Clerk clerk) {         this.clerk = clerk;     }
      @Override     public void run() {         while (true) {
              try {                 Thread.sleep(100);             } catch (InterruptedException e) {                 e.printStackTrace();             }
              System.out.println("消费者开始消费产品");             clerk.minusProduct();         }     } }
  public class ProducerCustomer {
      public static void main(String[] args) {         Clerk clerk = new Clerk();         Producer producer = new Producer(clerk);         Consumer consumer = new Consumer(clerk);         Consumer consumer2 = new Consumer(clerk);         producer.setName("生产者1");         consumer.setName("消费者1");         consumer2.setName("消费者2");         producer.start();         consumer.start();         consumer2.start();     } }
   | 
 
补充
新增两种创建线程的方式
- 实现Callable(JDK5.0新增的)
- call()可以有返回值
 
- call()可以使用throws的方式处理异常
 
- 提供泛型,可以决定call()方法的返回类型
 
- 如果在主线程需要获取分线程call()的返回值,则此时的主线程是阻塞状态的.
 
 
- 使用线程池
 
现有问题,如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间
那么有没有一种办法使得线程可以复用,即执行完一个任务,并不被销毁,而是可以继续执行其他的任务
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完放回池中,可以避免频繁创建销毁,实现重复利用,类似生活中的公共交通工具

使用线程池的好处:
- 提高了程序执行的效率(因为线程已经提前创建好了)
 
- 提高资源的利用率
 
- 可以设置相关的参数,对线程池中的线程进行管理
 
示例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55
   | package com.zzmr.pool;
  import java.util.concurrent.Callable; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor;
 
 
 
 
 
  class NumberThread implements Runnable {
      @Override     public void run() {         for (int i = 0; i <= 100; i++) {             if (i % 2 == 0) {                 System.out.println(Thread.currentThread().getName() + ":" + i);             }         }     } }
  class NumberThread1 implements Runnable {
      @Override     public void run() {         for (int i = 0; i <= 100; i++) {             if (i % 2 != 0) {                 System.out.println(Thread.currentThread().getName() + ":" + i);             }         }     } }
  public class TestPool {     public static void main(String[] args) {                  ExecutorService service = Executors.newFixedThreadPool(10);         ThreadPoolExecutor service1 = (ThreadPoolExecutor) service;
                   service1.setMaximumPoolSize(50);
                   service.execute(new NumberThread());         service.execute(new NumberThread1());
          
                   service.shutdown();     } }
   |