JAVA SE基础提要--15:线程与多线程(详细)

0x01.线程的概述

  • 并发:指两个或多个事件在同一个时间段内发生。
  • 并行:指两个或多个事件在同一时刻发生(同时发生)。
  • 进程:是指一个内存中运行的应用程序。
  • 线程:线程是进程中的一个执行单元,负责当前进程中程序的执行。
  • 一个程序运行后至少有一个进程,一个进程中可以包含多个线程 。

0x02.线程的调度

  • 分时调度: 所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。
  • 抢占式调度: 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个。
  • Java使用的线程调度为为抢占式调度。
  • CPU的一个核在任一时刻只能执行一个线程,CPU用抢占式调度模式在多个线程间进行着高速的切换,使得我们感觉像是在同时运行。
  • 线程程序并不能提高程序的运行速度,但能够提高程序运行效率。

0x03.多线程

  • 多线程指的是多个线程并发运行。
  • Java使用java.lang.Thread类代表线程
  • 多线程执行时,在栈内存中,每一个执行线程都有一片自己所属的栈内存空间。
  • 当执行线程的任务结束了,线程自动才在栈内存中释放。
  • 当所有的执行线程都结束了,进程才结束。
  • 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。

启动多线程的方法:

  1. 创建一个类并继承Thread类。
  2. 重写该类的run()方法,run()方法称为线程执行体。
  3. 创建线程对象。
  4. 调用start()方法来启动该线程。

0x04.Thread类

常用构造方法:

  • public Thread() :创建或分配一个新的线程对象。
  • public Thread(String name):分配线程对象的同时为线程取一个名字。
  • public Thread(Runnable target):分配一个带有指定目标新的线程对象。
  • public Thread(Runnable target,String name):分配一个带有指定目标新的线程对象并取一个名字。

常用方法:

  • public void start():线程开始执行。(JVM调用run()方法)
  • public void run():线程需要执行的代码块。
  • public static void sleep(long millis):暂停该线程指定的时间(毫秒)。
  • public static Thread currentThread():获取当前正在执行的线程对象的引用。
  • public String getName():获取线程名字。

0x05.Runnable接口

  • 采用 java.lang.Runnable 事常见的一种创建线程的方法。
  • 所有的多线程代码都是通过运行Threadstart()方法来运行的。
  • Thread线程负责执行其targetrun()方法。

使用方法:

  • 创建一个类并实现Runnable接口。
  • 重写run()方法。
  • 创建Runnable实现类的实例。
  • 以此实例作为Threadtarget来创建Thread对象。
  • 调用Thread对象的start()方法执行线程。

相比直接使用Thread的优点:

  • 适合多个相同的程序代码的线程去共享同一个资源。一个类继承Thread,则不适合资源共享。如果实现了Runable接口,则很容易的实现资源共享。
  • 避免单继承的局限性。
  • 增加程序的健壮性。
  • 可以放入线程池。

0x06.举例创建多线程

1.通过继承Thread类创建多线程:

创建类并继承Thread类:
public class Thread01 extends Thread {
    public Thread01(String name){
        super(name);
    }

    public void run(){
        for(int i=0;i<10000;i++){
            System.out.println("线程2正在执行");
        }
    }
}
创建多线程:
public class Main {
    public static void main(String[] args) {
        System.out.println("Main开始!!!");
        Thread01 t01 = new Thread01("第一个线程");
        t01.start();
        for (int i = 0; i < 10000; i++) {
            System.out.println("主线程正在执行");
        }
    }
}

2.通过实现Runnable接口:

创建类并实现Runnable接口:
public class Runnable01 implements Runnable {
    @Override
    public void run(){
        for(int i=0;i<10000;i++){
            System.out.println("线程2正在执行");
        }
    }
}
创建多线程:
public class Main {
    public static void main(String[] args) {
        System.out.println("Main开始!!!");
        Runnable01 ru = new Runnable01();
        Thread t01=new Thread(ru,"线程1");
        t01.start();
        for (int i = 0; i < 10000; i++) {
            System.out.println("主线程正在执行");
        }
    }
}

3.通过匿名内部类创建多线程:

public class Main {
    public static void main(String[] args) {
        Runnable r =new Runnable(){
            public void run(){
                for(int i=0;i<10000;i++){
                    System.out.println("线程2正在执行");
                }
            }
        };
        new Thread(r).start();
        for (int i = 0; i < 10000; i++) {
            System.out.println("主线程正在执行");
        }
    }
}

0x07.线程安全

  • 线程安全问题一般是由全局变量及静态变量引起的。
  • 若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的。
  • 若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

线程同步机制(synchronized)

同步机制的三种方式:
  • 同步代码块。
  • 同步方法。
  • 锁机制。
同步代码块:
  • synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。
  • 同步锁: 可以理解为在对象上标记的一把锁.
  • 锁的对象可以是任意类型。
  • 多个线程对象 要使用同一把锁。
  • 在任何时候,最多允许一个线程拥有同步锁,谁有锁就可以进入执行代码,其他的线程只能等待。
  • 对于非static方法,同步锁就是this
  • 对于static方法,使用当前方法所在类的字节码对象(类名.class)作为同步锁。

格式:

synchronized(同步锁){
    ...//代码块
}
同步方法:
  • 使用synchronized修饰的方法,叫做同步方法。
  • 保证任何时候只有一个进程可以进入该方法。

格式:

 public synchronized void method(){    
     ...//代码块
}
Lock锁机制:
  • java.util.concurrent.locks.Lock 机制提供的比synchronized代码块和synchronized方法更广泛的锁定操作。
  • 同步代码块/同步方法具有的功能Lock都有。
  • Lock锁也叫同步锁,加锁与释放锁的过程被方法化了。
  • public void lock() :加同步锁。
  • public void unlock():释放同步锁。

代码简要实现线程同步机制:

同步代码块:

public class Runnable01 implements Runnable {
    Object lock = new Object();
    int num;//假设是某个具有特殊意义的数字
    @Override
    public void run(){
        synchronized (lock) {
            num++;
        }
    }
}

同步方法:

public class Runnable01 implements Runnable {
    int num;//假设是某个具有特殊意义的数字
    @Override
    public void run(){
        addnum();
    }
    public synchronized void addnum(){
        num++;
    }
}

Lock锁机制:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Runnable01 implements Runnable {
    Lock lock = new ReentrantLock();
    int num;//假设是某个具有特殊意义的数字
    @Override
    public void run(){
        lock.lock();
        num++;
        lock.unlock();
    }
}

0x08.线程所处状态

  • java.lang.Thread.State 这个枚举中给出了线程所处的六种状态。
线程所处状态 状态说明
NEW(新建状态) 线程刚被创建,但是还没有启动,没有调用start方法。
Runnable(可运行状态) 表示线程可以在JVM中运行,可能正在运行自己代码,也可能没有。
Blocked(锁阻塞状态) 当一个线程试图获取一个对象锁,而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变成Runnable状态。
Waiting(无限等待状态) 一个线程在等待另一个线程执行一个唤醒时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。
TimedWaiting(计时等待状态) 同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleepObject.wait
Teminated(被终止状态) 可能因为run方法正常退出而死亡,也可能因为没有捕获的异常终止了run方法而死亡。

0x09.等待唤醒机制

  • 等待唤醒机制是多个线程间的一种协作机制。
  • 一个线程进行了规定操作后,就进入等待状态(wait())。
  • 待其他线程执行完他们的指定代码过后再将其唤醒(notify())。
  • otifyAll()用来唤醒所有的等待线程。
  • 一个进程如果能获取锁,线程就会从 WAITING 状态变成 RUNNABLE 状态;
  • 一个进程如果不能获取锁,从 wait set 出来,又进入 entry set,线程就从 WAITING 状态又变成 BLOCKED 状态。
  • wait方法与notify方法必须要由同一个锁对象调用。
  • wait方法与notify方法属于Object类的方法。
  • wait方法与notify方法必须要在同步代码块或者是同步方法中使用。

0x10.线程池

  • 线程池: 是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
  • 线程池的顶级接口是java.util.concurrent.Executor
  • Executor是一个执行线程的工具。
  • java.util.concurrent.ExecutorService是线程池接口。

使用线程池的优点:

  1. 提高响应速度。
  2. 降低资源消耗。
  3. 提高线程的可管理性。

创建并使用线程池:

  • 创建线程池对象。
  • 创建Runnable接口子类对象。
  • 提交Runnable接口子类对象。
  • 关闭线程池。
    常使用的创建方法:
  • public static ExecutorService newFixedThreadPool(int nThreads):返回线程池对象。(创建有界线程池,池中的线程个数可以指定最大数量)
  • public Future<?> submit(Runnable task):获取线程池中的某一个线程对象,并执行。Future接口用来记录线程任务执行完毕后产生的结果。

代码举例:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Runnable01 implements Runnable {
    Lock lock = new ReentrantLock();
    int num;//假设是某个具有特殊意义的数字
    @Override
    public void run(){
        lock.lock();
        num++;
        lock.unlock();
    }
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Main {
    public static void main(String[] args) {
        // 创建线程池对象,包含5个线程对象
        ExecutorService service = Executors.newFixedThreadPool(5);
        Runnable01 r = new Runnable01();  // 创建Runnable实例对象
        // 从线程池中获取线程对象,然后调用Runnable01中的run()
        service.submit(r);
        service.submit(r);//继续获取
        service.shutdown();//关闭线程池
    }
}
发布了62 篇原创文章 · 获赞 70 · 访问量 5482

猜你喜欢

转载自blog.csdn.net/ATFWUS/article/details/104532152