多线程相关算法

本文详细介绍了如何通过多种同步机制实现死锁,包括synchronized、wait/notify、LockSupport、ReentrantLock和condition,以及生产者消费者问题和哲学家就餐问题的解决方案。此外,还展示了基于CAS和银行家算法的线程交互。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

写个死锁

思路:死锁要两个线程互相占用对方的资源,所以需要定义两个对象作为共享资源,然后两个类分别实现Runable接口,然后重写run的时候嵌套用两次同步块。

  • 线程1和线程2均需要获取对象A和B,线程1先获取对象A,再获取对象B;
  • 线程2先获取对象B,再获取对象A。
  • 双方都在等地对方释放已拥有的对象,且不放弃自己已拥有的对象。
public class DeadLock {
    public static String objA = "objA";//资源1
    public static Integer objB = 1;//资源2
    public static void main(String[] arg) {
        Thread1 thread1=new Thread1();
        Thread th1=new Thread(thread1);
        Thread2 thread2=new Thread2();
        Thread th2=new Thread(thread2);
        th1.start();
        th2.start();
    }
}
class Thread1 implements Runnable {
    @Override
    public void run() {
        try {
            System.out.println("Thread1 is running ...");
            synchronized (DeadLock.objA) {// Thread1先获取objA
                System.out.println("Thread1 got the DeadLock.objA");
                Thread.sleep(3000); // 等待Thread2获取资源objB
                synchronized (DeadLock.objB){// Thread1再获取objB
                    System.out.println("Thread1 got the DeadLock.objB");
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Thread2 implements Runnable{
    @Override
    public void run() {
        try {
            System.out.println("Thread2 is running ...");
            synchronized (DeadLock.objB){// Thread2先获取objB
                System.out.println("Thread2 got the DeadLock.objB");
                Thread.sleep(3000);// 等待Thread1获取资源objA
                synchronized (DeadLock.objA){// Thread2再获取objA
                    System.out.println("Thread2 got the DeadLock.objA");
                }
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

结果:都执行不到第二个synchronized块内的代码。

Thread1 is running …
Thread1 got the DeadLock.objA
Thread2 is running …
Thread2 got the DeadLock.objB

三个线程同时启动,保证先后顺序:t1>t2>t3

利用join,在一个线程中加入另一个线程。

public class JoinTest2 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("t1");
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 引用t1线程,等待t1线程执行完
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2");
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    // 引用t2线程,等待t2线程执行完
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t3");
            }
        });
        t2.start();//这里三个线程的启动顺序可以任意
        t3.start();
        t1.start();
    }
}

两个线程交替打印0和1、交替1~100

参考:线程交替打印的几种实现方式

写法0:只用一个线程类+synchronized

class MyPrint implements Runnable{
    int i = 1;
    @Override
    public void run() {
        while (true){
            synchronized (this){
                this.notify();//在这里唤醒的目的是为保证拿到锁的线程只有一个 不
                // 会立即释放锁 退出代码块才会释放锁
                if (i <= 100 ){
                    System.out.println(Thread.currentThread().getName()+" "+i++);
                }else {
                    break;
                }
                try {
                    this.wait();//打印过数据的线程等待 必须等到没打印过数字的拿到锁了才能唤醒
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
public class JiaotiPrint {
    public static void main(String[] args) {
        MyPrint print = new MyPrint();
        Thread thread1 = new Thread(print);
        Thread thread2 = new Thread(print);
//        Thread thread3 = new Thread(print);
        thread1.start();
        thread2.start();
//        thread3.start();
    }
}

写法一:wait和notify方法配合

public class WaitNotifyToPrint {
    static volatile int i = 0;
    public static void main(String[] args) {
        final Object object = new Object();
        new Thread(() -> {
            synchronized (object){
                while (true){
                    if(i <= 100) 
                        System.out.println(Thread.currentThread().getName() + ":  0   " + i++);
                    try{
                        object.notify();
                        Thread.sleep(500);
                        object.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        },"t1").start();

        new Thread(() -> {
            synchronized (object){
                while (true){
                    if(i <= 100)
                        System.out.println(Thread.currentThread().getName() + ":  1   " + i++);
                    try{
                        object.notify();
                        Thread.sleep(500);
                        object.wait();
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                }
            }
        },"t2").start();
    }
}

写法二:用LockSupport类的park和unpack方法配合

这里没有用匿名类是因为需要有变量名,作为参数传给unpark方法。

public class LockSupportToPrint {
    private static Thread t1;
    private static Thread t2;
    private static volatile int i = 0;
    public static void main(String[] args) {
        t1 = new Thread(() -> {
            while(true){
                if(i <= 100)
                    System.out.println(Thread.currentThread().getName() + "  0  " + i++);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                LockSupport.unpark(t2);//释放t2线程 设置锁标志位
                LockSupport.park();//阻塞当前线程
            }
        },"t1");

        t2 = new Thread(() -> {
            while(true){
                LockSupport.park();//阻塞当前线程
                if(i <= 100)
                    System.out.println(Thread.currentThread().getName() + "  1  " + i++);
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                LockSupport.unpark(t1);//释放t1线程
            }
        },"t2");

        t1.start();
        t2.start();
    }
}

写法三:ReentrantLock和condition(await和signal方法)

public class LockConditionToPrint {
    public static void main(String[] args) {
        char[] a1 = "1234567".toCharArray();
        char[] a2 = "ABCDEFG".toCharArray();
        Lock lock = new ReentrantLock();//锁
        Condition t1 = lock.newCondition();//t1队列
        Condition t2 = lock.newCondition();//t2队列

        new Thread(() ->{
            try{
                lock.lock();
                for(char c : a1){
                    System.out.print(c);
                    t2.signal();//唤醒t2队列中等待的线程
                    t1.await();//进入t1队列自旋等待
                }
                t1.signal();//避免有线程未被唤醒
                t2.signal();//避免有线程未被唤醒
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        },"t1").start();

        new Thread(() ->{
            try{
                lock.lock();
                for(char c : a2){
                    System.out.print(c);
                    t1.signal();//唤醒t1队列中等待的线程
                    t2.await();//进入t2队列自旋等待
                }
                t1.signal();//避免有线程未被唤醒
                t2.signal();//避免有线程未被唤醒
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        },"t2").start();
    }
}

写法四:基于cas机制实现线程交替打印()

public class CasToPrint {
    enum ReadyToRun{
        T1,T2
    }
    private static volatile ReadyToRun readyToRun = ReadyToRun.T1;
    public static void main(String[] args) {
        char[] a1 = "1234567".toCharArray();
        char[] a2 = "ABCDEFG".toCharArray();
        new Thread(() ->{
            for(char c : a1){
                while (readyToRun != ReadyToRun.T1){}//cas自旋
                System.out.print(c);
                readyToRun = ReadyToRun.T2;//线程可见性
            }
        },"t1").start();
        new Thread(() ->{
            for(char c : a2){
                while (readyToRun != ReadyToRun.T2){}//cas自旋
                System.out.print(c);
                readyToRun = ReadyToRun.T1;//线程可见性
            }
        },"t2").start();
    }
}

生产者与消费者问题

这个问题是来模拟操作系统中多个线程之间的同步和互斥问题,是多线程同步问题的经典案例。该问题描述了共享固定大小缓冲区的两个线程——即所谓的“生产者”和“消费者”——在实际运行时会发生的问题。生产者的主要作用是生成一定量的数据放到缓冲区中,然后重复此过程。与此同时,消费者也在缓冲区消耗这些数据。该问题的关键就是要保证生产者不会在缓冲区满时加入数据,消费者也不会在缓冲区中空时消耗数据。
要解决该问题,就必须让生产者在缓冲区满时休眠(要么干脆就放弃数据),等到下次消费者消耗缓冲区中的数据的时候,生产者才能被唤醒,开始往缓冲区添加数据。同样,也可以让消费者在缓冲区空时进入休眠,等到生产者往缓冲区添加数据之后,再唤醒消费者。通常采用进程间通信的方法解决该问题。如果解决方法不够完善,则容易出现死锁的情况。出现死锁时,两个线程都会陷入休眠,等待对方唤醒自己。该问题也能被推广到多个生产者和消费者的情形。

package com.luowei.Algorithm;

public class ProducerComsumer {
    public static void main(String[] args) {
        Cherk cherk = new Cherk();
        new Thread(new Producer(cherk),"生产者A").start();
        new Thread(new Consumer(cherk),"消费者A").start();
    }
}

class Cherk{//工具类,生产与消费的方法
    private int product = 0;    // 商品库存
    public synchronized void product() {
        if (product==10){// 10个商品为最大库存量,为此生产出一个商品
            System.out.println("库存已满");
            try {
                this.wait();// 当库存满的时候,线程调用wait方法,会释放锁
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        product++;
        System.out.println(Thread.currentThread().getName()+"生产了一个商品,当前库存为:"+product);
        System.out.println("仓库有货,数量为:"+product);
        this.notifyAll();// 唤醒其他等待的线程
    }
    // 消费方法,每次消费商品数量减一
    public synchronized void consumer()  {
        if (product<=0){
            System.out.println("没货了");
            try {
                this.wait();// 当缺货了,就不让消费了,等待生产者生产商品
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        product--;
        System.out.println(Thread.currentThread().getName()+"消费了一个商品");
        // 唤醒等待的线程
        this.notifyAll();
    }
}

class Producer implements Runnable{
    private Cherk cherk;// 有一个Cherk类,供线程调用方法
    public Producer(Cherk cherk) {
        this.cherk = cherk;
    }
    public void run() {// 假设生产20次
        for(int i = 0; i<20; i++){
            cherk.product();
        }
    }
}
class Consumer implements Runnable{
    private Cherk cherk;// 有一个Cherk类,供线程调用方法
    public Consumer(Cherk cherk) {
        this.cherk = cherk;
    }
    public void run() {
        for(int i = 0; i<20; i++){
            cherk.consumer();
        }
    }
}

哲学家就餐问题

多线程并发处理共享资源问题的模拟。
添加一个服务生,只有经过服务生允许后哲学家才能拿叉子,由服务生负责避免死锁。哲学家必须确定左右两边的叉子都可用后,才能同时拿起左右两边的叉子。给叉子编号,每位哲学家每次只能拿起编号较小的叉子,最后一位哲学家无法找到编号小的叉子,则不能拿起叉子。多余的叉子将由其他哲学家拿起。这种方式虽然避免了死锁,但是资源利用率不高。设计思想:

  • 一个哲学家是一个线程,它不停地进行思考、吃饭。
  • 为了模拟思考和吃饭的过程,该线程需要休眠一定时间。
  • 共有5把叉子,每把叉子的状态为可用或不可用。
  • 吃饭时,要求哲学家左右两边的叉子都是可用的,才能同时拿起叉子(将叉子的状态设置为不可用)。
  • 哲学家放下叉子时,需要将叉子状态设置为可用,并通知在等待的其他哲学家。
public class PhilosopherProblem {
    public static void main(String[] arg){
        Fork fork=new Fork();
        Philosopher philosopher0=new Philosopher("0",fork);
        Philosopher philosopher1=new Philosopher("1",fork);
        Philosopher philosopher2=new Philosopher("2",fork);
        Philosopher philosopher3=new Philosopher("3",fork);
        Philosopher philosopher4=new Philosopher("4",fork);
        philosopher0.start();
        philosopher1.start();
        philosopher2.start();
        philosopher3.start();
        philosopher4.start();
    }
}
class Philosopher extends Thread {
    private String name;
    private Fork fork;
    public Philosopher(String name, Fork fork) {
        super(name);// 更新自身线程名,同时需要设置自己的name
        this.name=name;
        this.fork = fork;
    }
    @Override
    public void run() {
        while (true) {
            think();
            fork.takeFork();
            eat();
            fork.putFork();
        }
    }
    public void think() {
        System.out.println("Philosopher_" + name + ": I'm thinking...");
        try {
            Thread.sleep(1000);// 模拟思考
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public void eat() {
        System.out.println("Philosopher" + name + ": I'm eating...");
        try {
            Thread.sleep(1000);// 模拟吃饭
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class Fork {
    // 初始时,五支叉子都未被使用
    private boolean[] fork = new boolean[5];
    // 只有当左右两边的叉子都同时可用时,才能拿起叉子
    public synchronized void takeFork() {
        String name = Thread.currentThread().getName();
        int i = Integer.valueOf(name);
        while (fork[i] || fork[(i + 1) % 5]) {
            try {
                wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        // 同时拿起叉子,更新叉子状态
        fork[i] = true;
        fork[(i + 1) % 5] = true;
    }
    // 同时释放左右手的叉子并通知等待的哲学家
    public synchronized void putFork() {
        String name = Thread.currentThread().getName();
        int i = Integer.valueOf(name);
        // 更新叉子的状态
        fork[i] = false;
        fork[(i + 1) % 5] = false;
        notifyAll(); // 通知在等待的哲学家
    }
}

银行家算法

银行家算法是经典的避免死锁的算法。
要掌握银行家算法中的四个数据结构,算法流程,安全性算法原理。
避免死锁-----银行家算法详解
然后会写个简单的模拟。
Java模拟实现银行家算法

其他问题

实现一个容器,提供两个方法,add,size。写两个线程,线程1添加10个元素到容器中, 线程2实现监控元素的个数,当个数到5个时,线程2给出提示并结束。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值