Java 中的多线程

CPU 通常被称为计算机的大脑,负责执行程序指令。它执行指令指定的基本算术、逻辑、控制和输入/输出操作。

核心是 CPU 内的独立处理单元。现代 CPU 可以拥有多个核心,从而可以同时执行多项任务。

四核处理器有四个核心,可同时执行四项任务。例如,一个核心可处理网络浏览器,另一个核心可处理音乐播放器,另一个核心可处理下载管理器,还有一个核心可处理后台系统更新。

程序

程序是用编程语言编写的一组指令,用于告诉计算机如何执行特定任务

Microsoft Word 是一个允许用户创建和编辑文档的程序。

过程

进程是正在执行的程序的一个实例。当程序运行时,操作系统会创建一个进程来管理其执行。

当我们打开Microsoft Word时,它就变成操作系统中的一个进程。

线

线程是进程内最小的执行单位。一个进程可以有多个线程,这些线程共享相同的资源,但可以独立运行。

像 Google Chrome 这样的网络浏览器可能会对不同的标签使用多个线程,每个标签都作为单独的线程运行。

多任务

多任务处理允许操作系统同时运行多个进程。在单核 CPU 上,这是通过分时、在任务之间快速切换来实现的。在多核 CPU 上,真正的并行执行是将任务分布在各个核心上。操作系统调度程序会平衡负载,确保高效且响应迅速的系统性能。

例如:我们一边听音乐一边浏览互联网并下载文件。

多任务处理利用了 CPU 及其核心的功能。当操作系统执行多任务处理时,它可以将不同的任务分配给不同的核心。这比将所有任务分配给单个核心更有效率。

多线程

多线程是指在单个进程内同时执行多个线程的能力。

网络浏览器可以使用多线程,通过单独的线程来呈现页面、运行 JavaScript 和管理用户输入。这使浏览器响应更快、更高效。

多线程通过将单个任务分解为更小的子任务或线程来提高多任务处理的效率。这些线程可以同时处理,从而更好地利用 CPU 的功能。

在单核系统中:

线程和进程均由操作系统调度程序通过时间分片和上下文切换进行管理,以产生同时执行的假象。

在多核系统中:

线程和进程都可以在不同的核心上真正并行运行,操作系统调度程序在核心之间分配任务以优化性能。

时间分片

  • 定义:时间分片将 CPU 时间划分为称为时间片或量子的小间隔。
  • 功能:操作系统调度程序将这些时间片分配给不同的进程和线程,确保每个进程和线程都获得公平的 CPU 时间份额。
  • 目的:防止任何单个进程或线程独占CPU,提高响应能力并实现并发执行。

上下文切换

  • 定义:上下文切换是保存当前正在运行的进程或线程的状态并加载下一个要执行的进程或线程的状态的过程。
  • 功能:当一个进程或线程的时间片到期时,OS调度程序会执行上下文切换,将CPU的焦点转移给另一个进程或线程。
  • 目的:这允许多个进程和线程共享 CPU,使其看起来像在单核 CPU 上同时执行,或者提高多核 CPU 上的并行性。

多任务可以通过多线程实现,其中每个任务被分成并发管理的线程。

虽然多任务通常指运行多个应用程序,但多线程更细粒度,处理同一应用程序或进程内的多个线程。

Java 中的多线程

Java 对多线程提供了强大的支持,允许开发人员创建可以同时执行多项任务的应用程序,从而提高性能和响应能力。

在 Java 中,多线程是指两个或多个线程同时执行,以最大程度地提高 CPU 的利用率。Java 的多线程功能是 java.lang 包的一部分,因此可以轻松实现并发执行。

在单核环境中,Java 的多线程由 JVM 和 OS 管理,它们在线程之间切换以产生并发的感觉。

线程共享单核,并使用时间分片来管理线程执行。

在多核环境中,Java 的多线程可以充分利用可用的核心。

JVM 可以将线程分布在多个核心上,从而允许真正的并行执行线程。

线程是一种轻量级进程,是处理的最小单位。Java 通过其 java.lang.Thread 类和 java.lang.Runnable 接口支持多线程。

当 Java 程序启动时,会立即有一个线程开始运行,这个线程称为主线程,负责执行程序的 main 方法。

public class Test {
    public static void main(String[] args) {
        System.out.println("Hello world !");
    }
}

要在 Java 中创建新线程,您可以扩展 Thread 类或实现 Runnable 接口。

方法一:扩展Thread类

  1. 创建了一个扩展Thread的新类World。
  2. 重写 run 方法来定义构成新线程的代码。
  3. 调用start方法来启动新线程。
public class Test {
    public static void main(String[] args) {
        World world = new World();
        world.start();
        for (; ; ) {
            System.out.println("Hello");
        }
    }
}
public class World extends Thread {
    @Override
    public void run() {
        for (; ; ) {
            System.out.println("World");
        }
    }
}

方法2:实现Runnable接口

  1. 创建了一个实现 Runnable 的新类 World。
  2. 重写 run 方法来定义构成新线程的代码。
  3. 通过传递 World 实例来创建 Thread 对象。
  4. 在 Thread 对象上调用 start 方法来启动新线程。
public class Test {
    public static void main(String[] args) {
        World world = new World();
        Thread thread = new Thread(world);
        thread.start();
        for (; ; ) {
            System.out.println("Hello");
        }
    }
}
public class World implements Runnable {
    @Override
    public void run() {
        for (; ; ) {
            System.out.println("World");
        }
    }
}

线程生命周期

Java 中线程的生命周期由几个状态组成,线程在执行过程中可以经历这些状态。

  • 新建:线程被创建但还未启动时处于此状态。
  • 可运行:调用 start 方法后,线程变为可运行状态。它已准备好运行并等待 CPU 时间。
  • 运行:线程正在执行时,处于此状态。
  • 阻塞/等待:当线程等待资源或另一个线程执行操作时,它处于此状态。
  • 终止:当线程执行完毕后,它就处于此状态。
public  class  MyThread  extends  Thread { 
    @Override 
    public  void  run () { 
        System.out.println( "RUNNING" ); // 正在运行
        try { 
            Thread.sleep( 2000 ); 
        } catch (InterruptedException e) { 
            System.out.println(e); 
        } 
    } 

    public  static  void  main (String[] args)  throws InterruptedException { 
        MyThread  t1  =  new  MyThread (); 
        System.out.println(t1.getState()); // 新建
        t1.start(); 
        System.out.println(t1.getState()); // 可运行
        Thread.sleep( 100 ); 
        System.out.println(t1.getState()); // TIMED_WAITING
         t1.join(); 
        System.out.println(t1.getState()); // 终止
    } 
}

可运行模式与线程

当您想将任务与线程分离时,请使用 Runnable,以便允许类在需要时扩展另一个类。如果您需要覆盖 Thread 方法,或者任务本身需要直接控制线程本身,请扩展 Thread,但这会限制继承。

线程方法

  1. start( ):开始执行线程。Java 虚拟机 (JVM) 调用run()该线程的方法。
  2. run( ):线程的入口点。启动线程时,run()将调用该方法。如果线程是使用实现的类创建的Runnable,则该run()方法将执行run()Runnable对象的方法。
  3. sleep(long millis):使当前执行的线程休眠(暂时停止执行)指定的毫秒数。
  4. 加入( ): 等待该线程终止当一个线程调用join()另一个线程的方法时,会暂停当前线程的执行,直到被加入的线程执行完毕。
  5. setPriority(int newPriority):改变线程的优先级。优先级是Thread.MIN_PRIORITY(1) 到Thread.MAX_PRIORITY(10) 之间的值。
public class MyThread extends Thread {
    public MyThread(String name) {
        super(name);
    }

    @Override
    public void run() {
        System.out.println("Thread is Running...");
        for (int i = 1; i <= 5; i++) {
            for (int j = 0; j < 5; j++) {
                System.out.println(Thread.currentThread().getName() + " - Priority: " + Thread.currentThread().getPriority() + " - count: " + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();

                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {

        MyThread l = new MyThread("Low Priority Thread");
        MyThread m = new MyThread("Medium Priority Thread");
        MyThread n = new MyThread("High Priority Thread");
        l.setPriority(Thread.MIN_PRIORITY);
        m.setPriority(Thread.NORM_PRIORITY);
        n.setPriority(Thread.MAX_PRIORITY);
        l.start();
        m.start();
        n.start();

    }
}

6.interrupt():中断线程。如果线程在调用wait()sleep()或时被阻塞join(),则会抛出InterruptedException

7.yield (): Thread.yield()是一种静态方法,它建议当前线程暂时暂停执行,以允许其他具有相同或更高优先级的线程执行。需要注意的是,这yield()只是对线程调度程序的一个提示,实际行为可能因 JVM 和操作系统而异。

public class MyThread extends Thread {
    @Override
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName() + " is running...");
            Thread.yield();
        }
    }

    public static void main(String[] args) {
        MyThread t1 = new MyThread(); 
        MyThread t2 = new MyThread();
        t1.start();
        t2.start();
    }
}

8. Thread.setDaemon(boolean):将线程标记为守护线程或用户线程。当 JVM 退出时,所有守护线程都将终止。 

public  class  MyThread  extends  Thread { 
    @Override 
    public  void  run () { 
        while ( true ) { 
            System.out.println( "Hello world! " ); 
        } 
    } 

    public  static  void  main (String[] args) { 
        MyThread  myThread  =  new  MyThread (); 
        myThread.setDaemon( true ); // myThread 现在是守护线程(如垃圾收集器)
        MyThread  t1  =  new  MyThread (); 
        t1.start(); // t1 是用户线程
        myThread.start(); 
        System.out.println( "Main Done" ); 
    } 
}

同步

让我们看两个线程增加相同计数器的例子。

class Counter {
    private int count = 0; // shared resource

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

public class MyThread extends Thread {
    private Counter counter;

    public MyThread(Counter counter) {
        this.counter = counter;
    }

    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            counter.increment();
        }
    }

    public static void main(String[] args) {
        Counter counter = new Counter();
        MyThread t1 = new MyThread(counter);
        MyThread t2 = new MyThread(counter);
        t1.start();
        t2.start();
        try {
            t1.join();
            t2.join();
        }catch (Exception e){

        }
        System.out.println(counter.getCount()); // Expected: 2000, Actual will be random <= 2000
    }
}

代码的输出不是 2000,因为 Counter 类中的increment方法不是同步的。当两个线程同时尝试增加count变量时,这会导致竞争条件。

如果没有同步,一个线程可能会在另一个线程完成写入其递增值之前读取值count。这可能导致两个线程读取相同的值、递增它并将其写回,从而实际上丢失其中一个增量。

我们可以通过使用synchronized关键字来解决这个问题

class  Counter { 
    private  int count = 0 ; // 共享资源

    public synchronized void  increase () { 
        count++; 
    } 

    public  int  getCount () { 
        return count; 
    } 
}

通过同步该increment方法,可以确保每次只有一个线程可以执行此方法,从而避免出现竞争条件。通过此更改,输出将始终为 2000。

Java 中的关键字synchronized提供了基本的线程安全性,但也有局限性:它会锁定整个方法或块,从而导致潜在的性能问题。它缺乏尝试锁定机制,导致线程无限期阻塞,增加了死锁的风险。此外,synchronized它不支持多个条件变量,每个对象仅提供一个具有基本等待/通知机制的监视器。相比之下,显式锁定(Lock接口)通过多个条件变量提供更细粒度的控制、尝试锁定功能以避免阻塞以及更复杂的线程协调,使其在复杂的并发场景中更加灵活和强大。

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class BankAccount {
    private int balance = 100;
    private final Lock lock = new ReentrantLock();

    public void withdraw(int amount) {
        System.out.println(Thread.currentThread().getName() + " attempting to withdraw " + amount);
        try {
            if (lock.tryLock(1000, TimeUnit.MILLISECONDS)) {
                if (balance >= amount) {
                    try {
                        System.out.println(Thread.currentThread().getName() + " proceeding with withdrawal");
                        Thread.sleep(3000); // Simulate time taken to process the withdrawal
                        balance -= amount;
                        System.out.println(Thread.currentThread().getName() + " completed withdrawal. Remaining balance: " + balance);
                    } catch (Exception e) {
                        Thread.currentThread().interrupt();
                    } finally {
                        lock.unlock();
                    }
                } else {
                    System.out.println(Thread.currentThread().getName() + " insufficient balance");
                }
            } else {
                System.out.println(Thread.currentThread().getName() + " could not acquire the lock, will try later");
            }
        } catch (Exception e) {
            Thread.currentThread().interrupt();
        }
    }
}

public class Main {
    public static void main(String[] args) {
        BankAccount sbi = new BankAccount();
        Runnable task = new Runnable() {
            @Override
            public void run() {
                sbi.withdraw(50);
            }
        };
        Thread t1 = new Thread(task, "Thread 1");
        Thread t2 = new Thread(task, "Thread 2");
        t1.start();
        t2.start();
    }
}

可重入锁

Java 中的可重入锁是一种锁,它允许线程多次获取同一个锁而不会导致死锁。如果线程已经持有该锁,它可以重新进入该锁而不会被阻塞。当线程需要在同一执行流程中重复进入同步块或方法时,这很有用。包中的 ReentrantLock 类java.util.concurrent.locks提供了此功能,它比关键字提供了更大的灵活性synchronized,包括尝试锁定、定时锁定和用于高级线程协调的多个条件变量。

public  class  ReentrantExample { 
    private  final  Lock  lock  =  new  ReentrantLock (); 

    public  void  outerMethod () { 
        lock.lock(); 
        try { 
            System.out.println( "外部方法" ); 
            innerMethod(); 
        } finally { 
            lock.unlock(); 
        } 
    } 

    public  void  innerMethod () { 
        lock.lock(); 
        try { 
            System.out.println( "内部方法" ); 
        } finally { 
            lock.unlock(); 
        } 
    } 

    public  static  void  main (String[] args) { 
        ReentrantExample  example  =  new  ReentrantExample (); 
        example.outerMethod(); 
    } 
}

 

ReentrantLock 的方法

lock()

  • 获取锁,阻塞当前线程直到锁可用。它会阻塞线程直到锁可用,这可能会导致线程无限期等待的情况。
  • 如果该锁已被另一个线程持有,则当前线程将等待,直到可以获取该锁。

tryLock()

  • 尝试获取锁而不等待。true如果已获取锁则返回,false否则返回。
  • 这是非阻塞的,意味着如果锁不可用,线程将不会等待。

tryLock(long timeout, TimeUnit unit)

  • 尝试获取锁,但会超时。如果锁不可用,线程将等待指定的时间,然后放弃。当您想尝试获取锁而不是无限期等待时,可以使用此方法。如果锁在指定时间内不可用,它允许线程继续执行其他工作。此方法可用于避免死锁情况,并且当您不希望线程永远阻塞以等待锁时。
  • true如果在超时时间内获取了锁,则返回,false否则返回。

unlock()

  • 释放当前线程持有的锁。
  • 必须在块中调用finally以确保即使发生异常,也始终释放锁。

lockInterruptibly()

  • 除非当前线程被中断,否则获取锁。当您想在获取锁时处理中断时,这很有用。

读写锁

读写锁是一种并发控制机制,允许多个线程同时读取共享数据,同时将写入权限限制为一次只能由一个线程访问。此锁类型由ReentrantReadWriteLockJava 中的 类提供,可在读取操作频繁而写入操作不频繁的场景中优化性能。多个读取者可以获取读取锁而不会相互阻塞,但当一个线程需要写入时,它必须获取写入锁,以确保独占访问。与在写入操作期间阻塞所有访问的传统锁相比,这可以防止数据不一致,同时提高读取效率。

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteCounter {
    private int count = 0;
    private final ReadWriteLock lock = new ReentrantReadWriteLock();
    private final Lock readLock = lock.readLock();
    private final Lock writeLock = lock.writeLock();

    public void increment() {
        writeLock.lock();
        try {
            count++;
            Thread.sleep(50);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        } finally {
            writeLock.unlock();
        }
    }

    public int getCount() {
        readLock.lock();
        try {
            return count;
        } finally {
            readLock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReadWriteCounter counter = new ReadWriteCounter();

        Runnable readTask = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + " read: " + counter.getCount());
                }
            }
        };

        Runnable writeTask = new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    counter.increment();
                    System.out.println(Thread.currentThread().getName() + " incremented");
                }
            }
        };

        Thread writerThread = new Thread(writeTask);
        Thread readerThread1 = new Thread(readTask);
        Thread readerThread2 = new Thread(readTask);

        writerThread.start();
        readerThread1.start();
        readerThread2.start();

        writerThread.join();
        readerThread1.join();
        readerThread2.join();

        System.out.println("Final count: " + counter.getCount());
    }
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class FairnessLockExample {
    private final Lock lock = new ReentrantLock(true);

    public void accessResource() {
        lock.lock();
        try {
            System.out.println(Thread.currentThread().getName() + " acquired the lock.");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
        } finally {
            System.out.println(Thread.currentThread().getName() + " released the lock.");
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        FairnessLockExample example = new FairnessLockExample();

        Runnable task = new Runnable() {
            @Override
            public void run() {
                example.accessResource();
            }
        };

        Thread thread1 = new Thread(task, "Thread 1");
        Thread thread2 = new Thread(task, "Thread 2");
        Thread thread3 = new Thread(task, "Thread 3");

        thread1.start();
        thread2.start();
        thread3.start();
    }
}

锁上下文中的公平性是指线程获取锁的顺序。公平锁可确保线程按照请求的顺序获取锁,从而防止线程饥饿。使用公平锁时,如果多个线程正在等待,则等待时间最长的线程将获得下一个锁。但是,由于维护顺序的开销,公平性可能会导致吞吐量降低。相比之下,非公平锁允许线程“插队”,这可能提供更好的性能,但如果其他线程频繁获取锁,则存在某些线程无限期等待的风险。

s

在并发编程中,当两个或多个线程永远被阻塞,每个线程都在等待对方释放资源时,就会发生死锁。这通常发生在线程持有资源锁并请求其他线程持有的其他锁时。例如,线程 A 持有锁 1 并等待锁 2,而线程 B 持有锁 2 并等待锁 1。由于两个线程都无法继续,它们将一直处于死锁状态。死锁会严重影响系统性能,并且在多线程应用程序中很难调试和解决。

class  Pen { 
    public  synchronized  void  writeWithPenAndPaper (Paper paper) { 
        System.out.println(Thread.currentThread().getName() + " 正在使用 pen " + this + " 并尝试使用 paper " + paper); 
        paper.finishWriting(); 
    } 

    public  synchronized  void  finishWriting () { 
        System.out.println(Thread.currentThread().getName() + " 已完成使用 pen " + this ); 
    } 
} 

class  Paper { 
    public  synchronized  void  writeWithPaperAndPen (Pen pen) { 
        System.out.println(Thread.currentThread().getName() + " 正在使用 paper " + this + " 并尝试使用 pen " + pen); 
        pen.finishWriting(); 
    } 

    public  synchronized  void  finishWriting () { 
        System.out.println(Thread.currentThread().getName() + " 已完成使用 paper " + this ); 
    } 
} 

class  Task1 实现 Runnable { 
    private Pen pen; 
    private Paper paper; 

    public  Task1 (Pen pen, Paper paper) { 
        this .pen = pen; 
        this .paper = paper; 
    } 

    @Override 
    public  void  run () { 
        pen.writeWithPenAndPaper(paper); // 线程 1 锁定 pen 并尝试锁定 paper
     } 
} 

class  Task2  implements  Runnable { 
    private Pen pen; 
    private Paper paper; 

    public  Task2 (Pen pen, Paper paper) { 
        this .pen = pen; 
        this .paper = paper; 
    } 

    @Override 
    public  void  run () { 
        synchronized (pen){ 
            paper.writeWithPaperAndPen(pen); // 线程 2 锁定 paper 并尝试锁定 pen
         } 
    } 
} 


public  class  DeadlockExample { 
    public  static  void  main(String[] args) { 
        Pen  pen  =  new  Pen(); 
        Paper  paper  =  new  Paper();

        线程 thread1  =  new  Thread(new  Task1(pen,paper),“Thread-1”);
        线程 thread2  =  new  Thread(new  Task2(pen,paper),“Thread-2”);

        thread1.start();
        thread2.start();
    } 
}

 线程通信

class SharedResource {
    private int data;
    private boolean hasData;

    public synchronized void produce(int value) {
        while (hasData) {
            try {
                wait();
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt();
            }
        }
        data = value;
        hasData = true;
        System.out.println("Produced: " + value);
        notify();
    }

    public synchronized int consume() {
        while (!hasData){
            try{
                wait();
            }catch (InterruptedException e){
                Thread.currentThread().interrupt();
            }
        }
        hasData = false;
        System.out.println("Consumed: " + data);
        notify();
        return data;
    }
}

class Producer implements Runnable {
    private SharedResource resource;

    public Producer(SharedResource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            resource.produce(i);
        }
    }
}

class Consumer implements Runnable {
    private SharedResource resource;

    public Consumer(SharedResource resource) {
        this.resource = resource;
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            int value = resource.consume();
        }
    }
}

public class ThreadCommunication {
    public static void main(String[] args) {
        SharedResource resource = new SharedResource();
        Thread producerThread = new Thread(new Producer(resource));
        Thread consumerThread = new Thread(new Consumer(resource));

        producerThread.start();
        consumerThread.start();
    }
}

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值