← 返回首页
Java学习

6.多线程

2026-09-21·2 次浏览
6.多线程

1 多线程

多线程是提升程序性能非常重要的一种方式,使用多线程可以让程序充分利用CPU资源,提高CPU的使用效率,

从而解决高并发带来的负载均衡问题

优点:

​ 资源得到更合理的利用

​ 程序设计更加简洁

​ 程序响应速度更快,运行效率更高

缺点:

​ 需要更多的内存空间来支持多线程

​ 多线程并发访问可能会影响数据准确性

​ 数据被多线程共享,可能会出现死锁的情况

1.1 进程和线程

进程:计算机正在运行的一个独立的应用程序

线程:线程是组成进程的基本单位,一个进程由一个或多个线程组成的

进程和线程都是应用程序在执行过程中的概念,如果应用程序没有运行,那就谈不上进程和线程的概念,这是一个动态的概念

进程和线程的区别在于进程运行时拥有独立的内存空间,每个进程锁占用的内存都是独立的,互不干扰

多个线程共享内存空间,彼此之间的运行是独立的

1.2 多线程

在一个进程汇总,多个线程同时执行,系统会自动为每个线程分配CPU资源,在某个具体的时间段内,CPU会被一个线程占用,在不同的时间段由不同的线程来使用CPU资源

java Copy
new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println("++++++++++" + i);
            }
        }).start();


 new Thread(()->{
            for (int i = 0; i < 100; i++) {
                System.out.println(i + "=============");
            }
        }).start();

1.3 Java中线程的使用

Thread类:用来创建线程对象的

Runable接口:表示线程要执行的任务

继承Thread类

Thread是JDK提供的一个类,专门用来创建线程对象 ,抽象化的描述,具体使用的时候要实现具体的类

Copy
public class MyThread1 extends Thread{
    @Override
    public void run(){
        for (int i = 0; i < 100; i++) {
            System.out.println("张三");
        }
    }
}

实现Runable对象

耦合度过高,一个线程只能执行一个任务,无法做到任务到线程的自由分配

把线程对象和任务解耦合,将线程和任务分离

​ 创建一个任务

​ 创建一个原生的线程对象,并将任务分配给线程对象

Java Copy
public class MyRunable1 implements Runnable {
    @Override
    public void run() {
        for (int i = 0; i < 100; i++) {
            System.out.println("取快递");

        }
    }
}
Java Copy
public class test {
    public static void main(String[] args) {
        MyRunable1 runable1=new MyRunable1();
        MyRunable2 runable2=new MyRunable2();

        Thread thread1=new Thread(runable1);
        Thread thread2=new Thread(runable2);

        thread1.start();
        thread2.start();
    }
}
java Copy
public class test {
    public static void main(String[] args) {
       new Thread(new MyRunable1()).start();
       new Thread(new MyRunable2()).start();
    }
}

lambda表达式:函数式编程,可以将方法的具体实现作为参数进行传递

java Copy
public class test {
    public static void main(String[] args) {
      new Thread(()->{
          for(int i=0;i<10;i++){
              System.out.println(i);
          }
      }).start();
    }
}

1.4 线程的状态

线程共有5种状态,在特定的情况下,线程可以在不同的状态之间切换,5种状态如下:

​ 创建状态:实例化了一个新的线程队形,还未启动

​ 就绪状态:创建好的线程对象调用了start()方法完成启动,进入线程池等待抢占CPU资源

​ 运行状态:线程对象获取了CPU资源,在一定的时间内执行任务

​ 阻塞状态:正在运行的线程暂停执行任务,释放所占用的CPU资源,并在接触阻塞之后也不能直接回到运行状态,而是重新回到就绪状态,等待获取CPU资源

​ 终止状态:线程运行完毕或因为异常导致线程终止执行

1.5 线程调度

线程休眠:让当前线程暂停执行任务,从运行状态进入阻塞状态,将CPU资源让给其他线程的一种调度方式,通过调用sleep方法来实现,线程休眠需要传入具体的休眠时间

线程合并,将指定的某个线程加入到当前线程中,合并为一个线程,由两个线程交替执行变成一个线程中的两个子线程顺序执行,使用join方法实现

合并之后两个任务编程顺序执行,且优先执行合并进来的任务

Java Copy
public class test {
    public static void main(String[] args) {
      Thread thread = new Thread(()->{
          for(int i=0;i<50;i++){
              System.out.println(i +"============");
          }
      });
      thread.start();

        for (int i = 0; i < 100; i++) {
            if(i == 10){
                try {
                    thread.join();
                }catch (InterruptedException e){
                    e.printStackTrace();
                }
            }
            System.out.println("++++++++++" + i);
        }
    }
}

1.6 线程同步

问题:Java中允许多线程并行访问,同一个时间段内多个线程同时完成各自的操作,这样就会带来一个问题,多个线程同时操作一个共享数据的时候,可能会导致数据不准确、

这就需要使用线程同步来解决

异步:多个线程同时执行,不需要排队顺序执行

同步:多个线程按顺序执行,每次只能执行一个线程,执行完之后才能继续下一个线程的执行

就是因为异步导致上述的问题,所以我们要将程序改为同步的模式即可解决问题

如何实现同步呢?用上锁的方式解决

synchronized关键字上锁

java Copy
public class Account implements Runnable{
    private static int num;


    @Override
    public synchronized void run() {
        ++num;

        try {
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是当前的第" + num + "访客");

    }
}

synchronized可以修饰实例方法(非静态,没有static修饰的方法),也可以修饰静态方法,还可以修饰代码块

1.7 线程安全的单例模式

单例模式是一种常见的软件设计模式,核心思想是一个类只有一个实例对象,由多个线程来共享该实例对象的资源

​ 将构造函数私有化

​ 提供共有的静态方法返回实例对象

java Copy
public class SingletonDemo {
    
    private  static SingletonDemo instance;
    
    
    private SingletonDemo(){
        System.out.println("创建了一个对象");
    }
    
    public synchronized static SingletonDemo getInstance(){
        if(instance==null){
            instance = new SingletonDemo();
        }
        return instance;
    }
}

1.8 死锁

使用synchronized可以实现线程同步,可以解决多线程并行访问数据带来的安全问题,synchronized在解决线程安全问题的同时也会带来新的问题,死锁。

多个线程争夺同一个资源而带来的互斥问题

例如:10个人围在一桌吃饭,但是每个人只有一个筷子,要求必须凑齐一双筷子才可以吃饭,也就是每个人必须要拿到其他人的筷子,但是每个人又不愿意把自己的筷子让出来给其他人使用,都在等待其他人让出筷子,这样就形成一个四局,如果一直保持这个状态,饭局就会一直僵下去,没有一个人能吃到饭。

如果把每个人都看成一个线程,筷子就是线程要获取的资源,现在每个线程都占用一个资源并且不远亿释放,而且任意一个线程想继续执行就必须获取其他线程的资源,那么所有的线程都处于阻塞状态,程序无法向下执行页无法结束

如何破解死锁呢?只有某个线程愿意作出让步,贡献出自己的资源给其他的线程使用,获取到资源的线程就可以执行自己的任务,执行完毕后就会释放占用的资源,这样其他的线程就可以依次获取资源来执行任务

java Copy
import javax.swing.plaf.TableHeaderUI;

public class DeadLockRunnable implements Runnable {
    public int num;

    private  static Chopsticks chopsticks1 = new Chopsticks();
    private  static Chopsticks chopsticks2 = new Chopsticks();

    @Override
    public void run() {
        //张三拿到筷子1,李四拿到筷子2
        if (num == 1){
            System.out.println(Thread.currentThread().getName() + "获取到chopsticks1,等待获取chopsticks2");
            synchronized (chopsticks1) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (chopsticks2) {
                    System.out.println(Thread.currentThread().getName() + "用餐完毕");
                }
            }
        }

        if (num == 2){
            System.out.println(Thread.currentThread().getName() + "获取到chopsticks2,等待获取chopsticks1");
            synchronized (chopsticks2) {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
                synchronized (chopsticks1) {
                    System.out.println(Thread.currentThread().getName() + "用餐完毕");
                }
            }
        }
    }
}
java Copy
public class test {
    public static void main(String[] args) {

        DeadLockRunnable deadLockRunnable1 = new DeadLockRunnable();
        deadLockRunnable1.num =  1;
        DeadLockRunnable deadLockRunnable2 = new DeadLockRunnable();
        deadLockRunnable2.num = 2;

        Thread t1 = new Thread(deadLockRunnable1);
        t1.setName("张三");

        Thread t2 = new Thread(deadLockRunnable2);
        t2.setName("李四");

        t1.start();

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }

        t2.start();
    }
}

最简单的解决方法,不要同时进行,例如这里使用了一个sleep让线程一做完了再来进行线程二

1.9 重入锁

ReentrantLock是对synchronized的升级,synchronized是通过JVM实现的,ReentrantLock是通过JDK实现的

重入锁就是可以重复上锁,可以给同一个资源添加多把锁,并且它的解锁方式和synchronized也不一样,synchronized的锁是线程执行完毕之后会自动释放,ReentrantLock的锁必须手动释放,加了几把锁就要手动解几把锁

java Copy
public class Account implements Runnable{
    private static int num;

    private ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {

        reentrantLock.lock();

        ++num;

        try {
            Thread.sleep(1000);
        }catch (InterruptedException e){
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+"是当前的第" + num + "访客");

        reentrantLock.unlock();
    }
}
Java Copy
public class test {
    public static void main(String[] args) {

       Account account = new Account();

       Thread t1 = new Thread(account);
       Thread t2 = new Thread(account);
       t1.start();
       t2.start();
    }
}

重入锁还有个限时性的特点

可以判断某个线程在一定时间内能否获取到锁,通过调用tryLock方法实现

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

public class TimeLock implements Runnable {

    public ReentrantLock reentrantLock = new ReentrantLock();

    @Override
    public void run() {

        try {
            if (reentrantLock.tryLock(3, TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + "获取到了锁");
                Thread.sleep(5000);
            }else {
                System.out.println(Thread.currentThread().getName() + "无法获取锁");
                           }
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }finally {
            if(reentrantLock.isHeldByCurrentThread()){
                reentrantLock.unlock();
            }
        }
    }
}
java Copy
import java.sql.Time;

public class test {
    public static void main(String[] args) {

        TimeLock lock = new TimeLock();

       Thread t1 = new Thread(lock,"线程1");
       Thread t2 = new Thread(lock,"线程2");
       t1.start();
       t2.start();
    }
}

1.10 生产者消费者模式

生产者消费者模式是指在一个生产环境中,生产者和消费者在同一个时间段内共享一块缓冲去,生产者负责向缓冲区中添加数据,消费者负责从缓冲区中取出数据

wait:让当前正在访问资源的线程暂停执行

notify:让该线程恢复执行

java Copy
public class Hamburger {

    private int id;

    public Hamburger(int id) {
        this.id = id;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    @Override
    public String toString() {
        return "Hamburger{" + "id=" + id + '}';
    }
}
java Copy
public class Container {

    public Hamburger[] hamburgers =  new Hamburger[10];
    public int index = 0;

    public synchronized void addHamburger(Hamburger hamburger) {
        while(index == hamburgers.length) {
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.notify();
        hamburgers[index] = hamburger;
        index++;
        System.out.println("生产了一个汉堡" + hamburger);
    }

    public synchronized Hamburger removeHamburger() {
        while(index == 0){
            try {
                this.wait();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.notify();
        index--;
        System.out.println("消费了一个汉堡" + hamburgers[index]);
        return hamburgers[index];
    }
}
java Copy
import java.util.function.Consumer;

public class Producer implements Runnable {
    private Container container = null;

    public Producer(Container container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {
            Hamburger hamburger = new Hamburger(i);
            this.container.addHamburger(hamburger);
            try {
                Thread.sleep(2000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
java Copy
public class Consumer implements Runnable {

    private Container container;
    public Consumer(Container container) {
        this.container = container;
    }

    @Override
    public void run() {
        for (int i = 0; i < 30; i++) {

            this.container.removeHamburger();
        }
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }
}
java Copy
import java.sql.Time;

public class test {
    public static void main(String[] args) {

       Container container = new Container();
       Producer producer = new Producer(container);
       Consumer consumer = new Consumer(container);

       new Thread(producer).start();
        new Thread(producer).start();
       new Thread(consumer).start();
        new Thread(consumer).start();
        new Thread(consumer).start();
    }
}

1.11 多线程的实际应用

一场球赛的球票分3个窗口出售,共15张票,用多线程模拟3个窗口的售票情况

java Copy
public class TicketRunnable implements Runnable {

    //剩余球票
    public int count1 = 15;

    //已售出球票
    public int count2 = 0;

    @Override
    public void run() {
        while(count1 > 0){
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            if(count1 == 0){
                return;
            }

            synchronized(TicketRunnable.class){
                count1--;
                count2++;
                if(count1 == 0){
                    System.out.println(Thread.currentThread().getName() + "售出了第" + count2 + "张票,票已经售尽");
                }else{
                    System.out.println(Thread.currentThread().getName() + "售出了第" + count2 + "张票,还剩余" + count1 + "张票");
                }
            }
        }
    }
}
java Copy
import java.sql.Time;

public class test {
    public static void main(String[] args) {

        TicketRunnable ticketRunnable = new TicketRunnable();
        new Thread(ticketRunnable,"窗口A").start();
        new Thread(ticketRunnable,"窗口B").start();
        new Thread(ticketRunnable,"窗口C").start();
    }
}
── 完 ──

评论

还没有评论,来说点什么
发表评论
0 / 1000