【Java篇】多线程 学习笔记

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6

一、基本概念程序、进程、线程

1. 程序、进程、线程

  • 程序(program) 是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码属于静态对象。
  • 进程(process) 是程序的一次执行过程或是正在运行的一个程序。是一个动态的过程有它自身的产生、存在和消亡的过程。——生命周期
  • 线程(thread)进程可进一步细化为线程是一个程序内部的一条执行路径。一个进程可以在同一时间并行执行多个线程。

2. 并行与并发

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

3. 使用多线程的优点

  1. 提高应用程序的响应。对图形化界面更有意义可增强用户体验。
  2. 提高计算机系统CPU的利用率。
  3. 改善程序结构。将既长又复杂的进程分为多个线程独立运行利于理解和
    修改。

4. 何时需要多线程

  1. 程序需要同时执行两个或多个任务。
  2. 程序需要实现一些需要等待的任务时如用户输入、文件读写操作、网络操作、搜索等。
  3. 需要一些后台运行的程序时。

 

二、线程的创建和使用

1. 线程的创建与使用

  • Java语言的JVM允许程序运行多个线程它通过 java.lang.Thread 类来体现。
  • Thread 类的特性:
    1. 每个线程都是通过某个特定 Thread 对象的 run() 方法来完成操作的经常把 run() 方法的主体称为 线程体
    2. 通过该 Thread 对象的 start() 方法来启动这个线程而非直接调用 run()

2. API中创建线程的两种方式

JDK1.5之前创建新执行线程有两种方法

  • 继承 Thread 类的方式
  • 实现 Runnable 接口的方式

方式一继承 Thread

  1. 定义子类继承 Thread 类;
  2. 子类中重写 Thread 类中的 run 方法;
  3. 创建 Thread 子类对象即创建了线程对象;
  4. 调用线程对象 start 方法启动线程调用 run 方法。

实现

// 1. 定义子类继承 Thread类
public class TestThread1 extends Thread{

    // 2.重写run() 方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程" + i);
        }
    }

    public static void main(String[] args) {

        // 3.创建一个线程对象
        TestThread1 testThread1 = new TestThread1();
        // 4.调用 start()开启线程
        testThread1.start();

        for (int i = 0; i < 100; i++) {
            System.out.println("主线程" + i);
        }
    }
}

 

注意点

  1. 如果自己手动调用 run() 方法那么就只是普通方法没有启动多线程模式。
  2. run() 方法由 JVM 调用什么时候调用执行的过程控制都由操作系统的 CPU 调度决定。
  3. 想要启动多线程必须调用 start 方法。
  4. 一个线程对象只能调用一次 start() 方法启动如果重复调用了则将抛出以上的异常“IllegalThreadStateException”。

方式二实现Runnable接口

  1. 定义子类实现 Runnable接口。
  2. 子类中重写 Thread 类中的 run 方法。
  3. 通过 Thread 类含参构造器创建线程对象。
  4. Runnable 接口的子类对象作为实际参数传递给Thread 类的构造器中。
  5. 调用 Thread 类的start方法开启线程调用Runnable子类接口的run方法。

实现

// 1. 定义子类实现 Runnable 接口
public class TestThread2 implements  Runnable{

    // 2. 重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 20; i++) {
            System.out.println("子线程" + i);
        }
    }

    public static void main(String[] args) {
        // 3. 创建线程对象
        TestThread2 testThread2 = new TestThread2();

        // 4. 将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中
        Thread thread  = new Thread(testThread2);

        // 5. 调用thread的start方法
        thread.start();

        for (int i = 0; i < 200; i++) {
            System.out.println("主线程" + i);
        }
    }
}

 

3. 继承方式和实现方式的联系与区别

public class Thread extends Object implements Runnable

区别

  1. 继承Thread 线程代码存放 Thread子类run方法中。
  2. 实现Runnable 线程代码存在接口的子类的run方法。

实现方式的优点

  1. 避免了单继承的局限性。
  2. 多个线程可以共享同一个接口实现类的对象非常适合多个相同线程来处理同一份资源。

4. Thread类的有关方法(1)

  1. void start(): 启动线程并执行对象的run()方法
  2. run(): 线程在被调度时执行的操作
  3. String getName(): 返回线程的名称
  4. void setName(String name): 设置该线程名称
  5. static Thread currentThread(): 返回当前线程。在Thread子类中就是this通常用于主线程和Runnable实现类

5. Thread类的有关方法(2)

  1. static void yield() 线程让步

    • 暂停当前正在执行的线程把执行机会让给优先级相同或更高的线程
    • 若队列中没有同优先级的线程忽略此方法
  2. join() 当某个程序执行流中调用其他线程的 join() 方法时调用线程将被阻塞直到 join() 方法加入的 join 线程执行完为止加入的是低优先级的线程也可以获得执行。

  3. static void sleep(long millis) (指定时间:毫秒)

    • 令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后
      重排队。
    • 抛出InterruptedException异常
  4. stop(): 强制线程生命期结束不推荐使用。

  5. **boolean isAlive()**返回 boolean判断线程是否还活着。

6. 线程的优先级

  1. 线程的优先等级

    • MAX_PRIORITY10
    • MIN _PRIORITY1
    • NORM_PRIORITY5
  2. 涉及的方法

    • getPriority() 返回线程优先值
    • setPriority(int newPriority) 改变线程的优先级
  3. 说明

    • 线程创建时继承父线程的优先级
    • 低优先级只是获得调度的概率低并非一定是在高优先级线程之后才被调用

7. 补充线程的分类

Java中的线程分为两类一种是守护线程一种是用户线程

  1. 它们在几乎每个方面都是相同的唯一的区别是判断JVM何时离开。
  2. 守护线程是用来服务用户线程的通过在 start()方法前调用thread.setDaemon(true)可以把一个用户线程变成一个守护线程。
  3. Java垃圾回收就是一个典型的守护线程。
  4. 若JVM中都是守护线程当前JVM将退出。

三、线程的生命周期

在这里插入图片描述

四、线程的同步

1. 线程同步

线程同步 即当有一个线程在对内存进行操作时其他线程都不可以对这个内存地址进行操作直到该线程完成操作 其他线程才能对该内存地址进行操作而其他线程又处于等待状态实现线程同步的方法有很多临界区对象就是其中一种。

线程不安全举例

public class TestThread3 implements Runnable{

    private int num = 10;

    @Override
    public void run() {
        while (true) {
            if (num <= 0) {
                break;
            }
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "-->拿到了第" + num-- + "票");
        }

    }

    public static void main(String[] args) {
        TestThread3 thread3 = new TestThread3();
        new Thread(thread3, "张三").start();
        new Thread(thread3, "李四").start();
        new Thread(thread3, "王五").start();
    }
}

出现问题的原因
当多条语句在操作同一个线程共享数据时一个线程对多条语句只执行了一部分还没有执行完另一个线程参与进来执行。导致共享数据的错误。

解决方法
对多条操作共享数据的语句只能让一个线程都执行完在执行过程中其他线程不可以参与执行。

 

2. Synchronized的使用方法

synchronized 关键字它包括两种方法

  1. synchronized 可以放在方法声明中表示整个方法为同步方法 :

public synchronized void show (String name) {
….
}

  1. 同步代码块

synchronized (Obj) {
} // obj称为同步监视器

  • Obj 可以是任何对象但是推荐使用共享资源作为同步监视器
  • 同步方法中无需指定同步监视器因为同步方法的同步监视器就是this就是这个对象本身或者是class

同步监视器的执行过程:

  1. 第一个线程访问锁定同步监视器执行其中代码。
  2. 第二个线程访问发现同步监视器被锁定无法访问。
  3. 第一个线程访问完毕解锁同步监视器。
  4. 第二个线程访问发现同步监视器没有锁然后锁定并访问。

3. 死锁

死锁 不同的线程分别占用对方需要的同步资源不放弃都在等待对方放弃自己需要的同步资源就形成了线程的死锁。
出现死锁后不会出现异常不会出现提示只是所有的线程都处于阻塞状态无法继续。

产生死锁的四个必要条件

  1. 互斥条件 一个资源每次只能被一个进程使用。
  2. 请求与保持条件 一个进程因请求资源而阻塞时对已获得的资源保持不放。
  3. 不剥夺条件 进程已获得的资源在未使用完之前不能强行剥夺。
  4. 循环等待条件 若干进程之间形成一种头尾相接的循环等待资源关系。

上述四个条件只要破坏其任意一个就可避免死锁的发生。

4. Lock(锁

  • 从JDK 5.0开始Java提供了更强大的线程同步机制——通过显式定义同步锁对象来实现同步。同步锁使用 Lock 对象充当。
  • Java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问每次只能有一个线程对Lock对象加锁线程开始访问共享资源之前应先获得Lock对象。
  • ReentrantLock 类实现了 Lock 它拥有与 synchronized 相同的并发性和内存语义在实现线程安全的控制中比较常用的是 ReentrantLock 可以显示加锁、释放锁。
class A{
    private final ReentrantLock lock = new ReenTrantLock();
    public void m(){
        lock.lock();
        try{
			//保证线程安全的代码;
        }
        finally{
            lock.unlock();
        } 
}
// 注意如果同步代码有异常要将unlock()写入finally语句块

5. synchronized 与 Lock 的对比

  1. Lock 是显式锁(手动开启和关闭锁别忘记关闭锁synchronized是隐式锁出了作用域自动释放。
  2. Lock只有代码块锁synchronized有代码块锁和方法锁
  3. 使用 Lock锁JVM将花费较少的时间来调度线程性能更好。并且具有更好的扩展性(提供更多的子类。
  4. 优先使用顺序 Lock -> 同步代码块(已经进入了方法体分配了相应资源 -> 同步方法
    (在方法体之外

五、JDK5.0新增线程创建方式

1. 新增方式一实现Callable接口

与使用Runnable相比 Callable功能更强大些:

  1. 相比run()方法可以有返回值
  2. 方法可以抛出异常
  3. 支持泛型的返回值
  4. 需要借助FutureTask类比如获取返回结果

2. 新增方式二使用线程池

使用线程池的优点

  1. 提高响应速度(减少了创建新线程的时间
  2. 降低资源消耗(重复利用线程池中线程不需要每次都创建
  3. 便于线程管理

 
 

创作不易如果有帮助到你请给文章点个赞和收藏让更多的人看到
关注博主不迷路内容持续更新中。

阿里云国内75折 回扣 微信号:monov8
阿里云国际,腾讯云国际,低至75折。AWS 93折 免费开户实名账号 代冲值 优惠多多 微信号:monov8 飞机:@monov6
标签: Java