java核心day04

多线程

程序(**program**):为了完成特定任务、用某种语言编写的一组指令集合。指一段静态的代码,静态队形

进程 (process):程序的一次执行过程,或是正在运行的一个程序。 是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期。

线程 (thread):进程可进一步细化为线程,是一个程序内部的一条执 行路径。 ·

  • 并行:多个 CPU 同时执行多个任务。比如:多个人同时做不同的事。

  • 并发:一个 CPU(采用时间片)同时执行多个任务。比如:秒杀、多 个人做同一件事

优点

  • 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。

  • 提高计算机系统 CPU 的利用率。

    • 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利 于理解和修改

Thread 类的特性:

每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的, 经常把 run() 方法的主体称为线程体。 通过该 Thread 对象的 start() 方法来启动这个线程,而非直接调用 run()

常用方法:

  • Thread():创建新的 Thread 对象。

  • Thread(String threadname):创建线程并指定线程实例名。 Thread(Runnabletarget):指定创建线程的目标对象,它实现了 Runnable 接口中的 run 方法。

  • Thread(Runnable target, String name):创建新的 Thread 对象。


线程创建的两种方式

继承 Thread 类的方式。

实现 Runnable 接口的方式。

  • 继承Thread类的方式

    • . 创建一个继承于 Thread 类的子类;

    • 重写 Thread 类的 run() 方法 ---> 将此线程的方法声明在 run() 中;

    • 创建 Thread 类的子对象;

    • 通过此对象调用 start()。

 // 例子 : 遍历 100 以内的所有的偶数
 // 1. 创建一个继承于 Thread 类的子类
         class MyThread extends Thread{
 // 2. 重写 Thread 类的 run()
         @Override
         public void run() {
         for(int i = 1;i < 100;i++){
         if(i % 2 == 0){
         System.out.println(i);
          }
          }
         }
         }
 public class ThreadTest {
 public static void main(String[] args) {
 ​
 ​
 // 3. 创建 Thread 类的子对象
 ​
 MyThread t1 = new MyThread();
 ​
 // 4. 通过此对象调用 start():
 // ①启动当前线程 ②调用当前线程的 run()
 t1.start();
 ​
 // 如下操作仍在 main 线程中执行的
 for(int i = 1;i < 100;i++){
 if(i % 2 == 0){
 System.out.println(i + "***main()***");
  }
  }
 ​

测试 Thread 类有关方法:

  • start():启动当前线程,执行当前线程的 run()。

  • run():通常需要重写 Thread 类中的此方法,将创建的线程要执行的 操作声明在此方法中。

  • currentThread():静态方法,返回当前代码执行的线程。

  • getName():获取当前线程的名字。

  • setName():设置当前线程的名字。

  • yield():释放当前 CPU 的执行权。

  • join():在线程 a 中调用线程 b 的 join(), 此时线程 a 就进入阻塞状态, 直到线程 b 完全执行完以后,线程 a 才结束阻塞状态。

  • stop():已过时。当执行此方法时,强制结束当前线程。

  • sleep(long millitime):让当前线程“睡眠”指定时间的 millitime 毫秒。 在指定的 millitime 毫秒时间内,当前线程是阻塞状态的。

  • isAlive():返回 boolean,判断线程是否还活着

线程的调度

  • 线程的优先级 调度策略: 时间片。

  • 抢占式:高优先级的线程抢占 CPU。

Java 的调度方法: 同优先级线程组成先进先出队列(先到先服务),使用时间片策略; 对高优先级,使用优先调度的抢占式策略

线程的优先级

线程的优先级等级: - MAX_PRIORITY:10

  • MIN _PRIORITY:1 - NORM_PRIORITY:5 --->

  • 默认优先级 涉及的方法: - getPriority() :返回线程优先值。

  • setPriority(intnewPriority) :改变线程的优先级。

    说明 : 高优先级的线程要抢占低优先级线程 cpu 的执行权。但是只是从 概率上讲,高优先级的线程高概率的情况下被执行。并不意味着只有当高优 先级的线程执行完以后,低优先级的线程才会被执行

实现 Runnable 接口

  • 创建多线程的方式二:实现 Runnable 接口 1. 创建一个实现了 Runnable 接口的类

  • .实现类去实现 Runnable 中的抽象方法 :run();

  • 创建实现类的对象;

  • 将此对象作为参数传递到 Thread 类的构造器中,创建 Thread 类的对 象;

  • 通过 Thread 类的对象调用 start()

继承方式和实现方式的联系

比较创建线程的两种方式。 开发中:优先选择:实现 Runnable 接口的方式。

原因:

  • 实现的方式没有类的单继承性的局限性。

  • 实现的方式更适合来处理多个线程有共享数据的情况。 联系:public class Thread implements Runnable。

  • 相同点:两种方式都需要重写 run(), 将线程要执行的逻辑声明在 run() 中。

线程的生命周期

新建---就绪---运行---阻塞---死亡

阻塞:没有抢夺到cup资源,或者被人零时挂起,终止自己的操作

线程状态导致状态发生条件
NEW(新建)线程刚被创建,但是并未启动。还没调用start方法。
Runnable(可运行)线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。
Blocked(锁阻塞)当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待)一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
Timed Waiting(计时等待)同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。
Teminated(被终止)因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。

线程同步

解决多个线程执行的不确定性引起执行结果的不稳定。 多个线程对账本的共享,会造成操作的不完整性,会 破坏数据

同步的方式

  • 使用synchronized修饰同步监视器

    • 操作共享数据的代码,即为需要同步的代码---》不能包含代码多了,也不能少

    • 共享数据:多个线程共同操作的变量

    • 同步监视器:俗称:锁。任意一个类的对象都可以来充当锁

    • 多个线程必须要共用一把锁

 synchronized( 同步监视器 ){
 // 需要被同步的代码
 }

  • 声明在一个方法上(同步方法)

    • 好处:解决了线程的安全问题

    • 操作代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低---局限

 synchronized (dog)

补充:

实现Runnable接口创建多线程的方式中,我们可以使用this充当同步监视器

 class Windows1 implements Runnable {
 private int ticket = 100;
 // Object obj = new Object();
 // Dog dog = new Dog();
 282 283
 @Override
 public void run() {
 while (true) {
 synchronized (this) {
 // 此时的 this: 唯一的 windows1 的对象 // 方式二 :synchronized (dog) {
 if (ticket > 0) {
 try {
 Thread.sleep(100);
 } catch (InterruptedException e) {
 e.printStackTrace();
  }
 System.out.println(Thread.currentThread().
 getName() + ": 卖票,票号为 : " + ticket);
 ticket--;
 } else {
 break;
  }
  }
  }
 }
 }
 public class WindowsTest1 {
 public static void main(String[] args) {
 Windows1 w = new Windows1();
 Thread t1 = new Thread(w);
 Thread t2 = new Thread(w);
 Thread t3 = new Thread(w);
 t1.setName(" 窗口 1");
 t2.setName(" 窗口 2");
 t3.setName(" 窗口 3");
 t1.start();
 t2.start();
 t3.start();
 }
 }
 class Dog {
 }

同步代码块处理继承 Thread 类 的线程安全问题

 class Windows extends Thread {
 private static int ticket = 100;
 private static Object obj = new Object();
 @Override
 public void run() {
 while (true) {
  // 正确的
 // synchronized (obj) {
 synchronized (Windows.class) { 
  // Class clazz = Windows.class
  // 错误的,因为此时 this 表示的是 t1,t2,t3 三个对象
 // synchronized (this) {
 if (ticket > 0) {
 try {
 Thread.sleep(100);
 } catch (InterruptedException e) {
 e.printStackTrace();
  }
 System.out.println(getName() + ": 卖 票, 票
 号为 : " + ticket);
 ticket--;
 } else {
 break;
  }
  }
  }
 }
 }
 同步代码块处理继承 Thread 类
 的线程安全问题
 使用同步代码块解决继承 Thread 类的方式的线程安全问题。
 例子:创建三个 c 窗口卖票,总票数为 100 张。
 284 285
 public class WindowsTest2 {
 public static void main(String[] args) {
 Windows t1 = new Windows();
 Windows t2 = new Windows();
 Windows t3 = new Windows();
 t1.setName(" 窗口 1");
 t2.setName(" 窗口 2");
 t3.setName(" 窗口 3");
 t1.start();
 t2.start();
 t3.start();
 }
 ​

同步方法处理实现Runnable的线程安全问题

  • 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。

  • 非静态的同步方法,同步监视器是:this; 静态的同步方法,同步监视器是:当前类本身。

 class Windows3 implements Runnable {
 private int ticket = 100;
 @Override
 public void run() {
 while (true) {
 show();
  
 }
 }
 public synchronized void show() { // 同步监视器:this
 // synchronized (this){
 if (ticket > 0) {
 try {
 Thread.sleep(100);
 } catch (InterruptedException e) {
 e.printStackTrace();
  
 }
 System.out.println(Thread.currentThread().getName() 
 + ": 卖票,票号为 : " + ticket);
 ticket--;
  
 }
 //
 }}
 }
 public class WindowsTest3 {
 public static void main(String[] args) {
 Windows3 w3 = new Windows3();
 同步方法处理实现 Runnable
  的线程安全问题
 使用同步方法解决实现 Runnable 接口的线程安全问题
 关于同步方法的总结:
 1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明。
 2. 非静态的同步方法,同步监视器是:this;
  静态的同步方法,同步监视器是:当前类本身。
 286 287
 Thread t1 = new Thread(w3);
 Thread t2 = new Thread(w3);
 Thread t3 = new Thread(w3);
 t1.setName(" 窗口 1");
 t2.setName(" 窗口 2");
 t3.setName(" 窗口 3");
 t1.start();
 t2.start();
 t3.start();
 }
 ​

线程安全的单例模式之懒汉式

使用同步机制将单例模式中的懒汉式改写为线程安全的。

 public class BankTest {
 }
 class Bank {
 private Bank() {
 }
 private static Bank instance = null;
 public static Bank getInstance() {
 // 方式一:效率稍差
 // 快捷键 :Alt+Shift+Z
 // synchronized (Bank.class) {
 // if(instance == null){
 // instance = new Bank();
 / }
 // return instance;
 // }
 // 方式二:效率较高
 if (instance == null) {
 synchronized (Bank.class) {
 if (instance == null) {
 instance = new Bank();
  }
  }
  }
 return instance;
 }
 }

死锁问题

死锁的理解:

不同的线程分别占用对方需要同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了死锁

说明:

死锁形成后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,我们使用同步时,要避免出现死锁

 public class ThreadTest {
 public static void main(String[] args) {
 StringBuffer s1 = new StringBuffer();
 StringBuffer s2 = new StringBuffer();
 new Thread() {
 @Override
 public void run() {
 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(new Runnable() {
 @Override
 public void run() {
 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();
 }
 ​

Lock 锁方式解决线程安全问题

java.util.concurrent.locks.Lock 接口是控制多个线程对 共享资源进行访问的工具。锁提供了对共享资源的独占 访问,每次只能有一个线程对 Lock 对象加锁,线程开始 访问共享资源之前应先获得 Lock 对象。

ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和内存语义,在实现线程安全的控制中, 比较常用的是 ReentrantLock,可以显式加锁、释放锁

synchronized 与 Lock 的异同?

  • 相同:二者都可以解决线程安全问题;

  • 不同:synchronized 机制在执行完相应的同步代码以后,自动的 释放同步监视器。 Lock 需要手动的启动同步(lock()),同时结束同步也需要手动 的实现(unlock())。 2. 优先使用顺序: Lock ·同步代码块(已经进入了方法体,分配了相应资源) ·同步方法(在方法体之外)

线程的通信

涉及到的三个方法:

  • wait():一旦执行此方法,当前线程就进入阻塞状态, 并释放同步监视器。

  • notify():一旦执行此方法,就会唤醒被 wait 的一个 线程。如果有多个线程被 wait,就唤醒优先级高的那个。

  • notifyAll():一旦执行此方法,就会唤醒所有被 wait 的线程。

说明:

wait(),notify(),notifyAll() 三个方法必须使用在同 步代码块或同步方法中。

wait(),notify(),notifyAll() 三个方法的调用者必须 是同步代码块或同步方法中的同步监视器。否则,会出 现 IllegalMonitorStateException 异常。

wait(),notify(),notifyAll() 三个方法是定义在 java. lang.Object 类中。

sleep() 和 wait() 的异同

相同点:

  • 一旦执行方法,都可以使得当前的线程进入 阻塞状态。

不同点:

  • 两个方法声明的位置不同: Thread 类中声明 sleep() Object 类中声明 wait()

  • 调用的要求不同: sleep() 可以在任何需要的场景下调用。 wait() 必须使用在同步代码块或同步方法中。

  • 关于是否释放同步监视器:如果两个方法都使用在 同步代码块或同步方法中,sleep() 不会释放锁,wait() 会 释放锁

创建多线程的方式三:实现 Callable 接口

如何理解实现 Callable 接口的方式创建多线程比实现 Runnable 接口创建多线程方式强大?

  • call() 可以有返回值的。

  • call() 可以抛出异常,被外面的操作捕获,获取异 常的信息。

  • Callable 是支持泛型的。

  • . 需要借助 FutureTask 类,比如获取返回结果

Future 接口

  • 可以对具体 Runnable、Callable 任务的执行结果进行 取消、查询是否完成、获取结果等。

    • FutrueTask 是 Futrue 接口的唯一的实现类。

  • FutureTask 同时实现了 Runnable, Future 接口。它既 可以作为 Runnable 被线程执行,又可以作为 Future 得到 Callable 的返回值

 //1. 创建一个实现 Callable 的实现类
 class NumThread implements Callable {
 // 2. 实现 call 方法,将此线程需要执行的操作声明在 call() 中
 @Override
 public Object call() throws Exception {
 int sum = 0;
 for (int i = 1; i <= 100; i++) {
 if (i % 2 == 0) {
 System.out.println(i);
 sum += i;
  }
  }
 return sum;
 }
 }
 public class ThreadNew {
 public static void main(String[] args) {
 // 3. 创建 Callable 接口实现类的对象
 NumThread numThread = new NumThread();
  // 4. 将此 Callable 接口实现类的对象作为传递到 FutureTask 构
 造器中,创建 FutureTask 的对象
 FutureTask futureTask = new FutureTask(numThread);
  // 5. 将 FutureTask 的对象作为参数传递到 Thread 类的构造器
 中,创建 Thread 对象,并调用 start()
 new Thread(futureTask).start();
 try {
  // 6. 获取 Callable 中 call 方法的返回值
 // get() 返回值即为 FutureTask 构造器参数 Callable 实现
 类重写的 call() 的返回值。
 Object sum = futureTask.get();
 System.out.println(" 总和为 :" + sum);
 } catch (InterruptedException e) {
 e.printStackTrace();
 } catch (ExecutionException e) {
 e.printStackTrace();
  }
 }
 }
 ​

创建多线程的方式四:使用线程池

经常创建和销毁、使用量特别大的资源,比如并发情 况下的线程,对性能影响很大。

思路: 提前创建好多个线程,放入线程池中,使用时直接获 取,使用完放回池中。可以避免频繁创建销毁、实现重 复利用。类似生活中的公共交通工具

好处:

提高响应速度(减少了创建新线程的时间) 降低资源消耗(重复利用线程池中线程,不需要每次 都创建) 便于线程管理 corePoolSize:核心池的大小 maximumPoolSize:最大线程数 keepAliveTime:线程没有任务时最多保持多长时 间后会终止

 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 ThreadPool {
 public static void main(String[] args) {
 // 1. 提供指定线程数量的线程池
 ExecutorService service = Executors.
 newFixedThreadPool(10);
 ThreadPoolExecutor service1 = (ThreadPoolExecutor) 
 service;
 // 设置线程池的属性
 // System.out.println(service.getClass());
 // service1.setCorePoolSize(15);
 // service1.setKeepAliveTime();
  // 2. 执行指定的线程的操作。需要提供实现 Runnable 接口或
 Callable 接口实现类的对象
 service.execute(new NumberThread()); 
 // 适合适用于 Runable
 service.execute(new NumberThread1()); 
 // 适合适用于 Runable
 // service.submit(Callable callable); // 适合适用于 Callable
 // 3. 关闭连接池
 service.shutdown();
 }
 }
 ​

线程池相关 API JDK 5.0 起提供了线程池相关 API:ExecutorService 和 Executors。 ExecutorService:真正的线程池接口。

常见子类 ThreadPoolExecutor。

  • ·void execute(Runnable command) :执行任务 / 命令,没有返回值, 一般用来执行 Runnable。

  • ·Future submit(Callable task):执行任务,有返回值,一般又来执行 Callable。 ·void shutdown():关闭连接池。

  • Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线 程池。

  • ·Executors.newCachedThreadPool():创建一个可根据需要创建新线程 的线程池。 ·

  • Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程 池。

  • ·Executors.newSingleThreadExecutor():创建一个只有一个线程的线程 池。

  • ·Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在 给定延迟后运行命令或者定期地执行

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值