多线程(二)——临界资源、同步和死锁

临界资源问题

临界资源

在一个进程中,多个线程之间是可以资源共享的。如果在一个进程中的一个资源同时被多个线程访问,这个资源就是一个临界资源。如果多个线程同时访问临界资源,会对这个资源的值造成影响。

临界资源问题

多个线程同时访问一个资源的情况下,一个线程在操作这个资源的时候,将值取出进行运算,在还没来得及进行修改这块空间的值之前,值又被其他的线程取走了。此时就会出现临界资源的问题,造成这个资源的值出现不是我们预期的值。

解决方案

临界资源问题出现的原因就是多个线程在同时访问一个资源,因此解决方案也很简单, 就是不让多个线程同时访问即可。

在一个线程操作一个资源的时候,对这个资源进行“上锁”,被锁住的资源,其他的线程无法访问。

线程锁

线程锁,就是用来“锁住”一个临界资源,其他的线程无法访问。在程序中,可以分为对象锁类锁

  • 对象锁:任何的对象,都可以被当做是一把锁来使用。但是需要注意,必须要保证不同的线程看到的锁,需要是同一把锁才能生效。如果不同的线程看到的锁对象是不一样的,此时这把锁将没有任何意义。
  • 类锁:可以将一个类做成锁,使用类.class 来作为锁。

同步代码段

同步代码段,是来解决临界资源问题最常见的方式。将一段代码放入到同步代码段中,将这段代码上锁。

第一个线程抢到了锁标记后,可以对这个临界资源上锁,操作这个临界资源。此时其他的线程再执行到synchronized的时候,会进入到锁池,直到持有锁的线程使用结束后,对这个资源进行解锁。此时,处于锁池中的线程都可以抢这个锁标记,哪一个线程抢到了,就进入到就绪态,没有抢到锁的线程,依然处于锁池中。

public class Test {
    private static int ticket=100;
    public static void main(String[] args) {
        Runnable runnable =new Runnable() {
            @Override
            public void run() {
                while (ticket > 0) {
                    /*
                     * 同步代码段,这里的逻辑执行,会被上锁。当这里的逻辑执行结束之后,会自动的解锁。
                     * 小括号中需要写的是:锁。
                     * 这里的锁,可以分为:类锁 和 对象锁
                     */
                    synchronized (Thread.class){
                            if(ticket<=0){
                                break;
                            }
                        System.out.println(String.format("售票员%s卖出一张票,剩余:%d",Thread.currentThread().getName(),--ticket));
                    }
                }
            }
        };
        
        Thread t1=new Thread(runnable,"周杰伦");
        Thread t2=new Thread(runnable,"林俊杰");
        Thread t3=new Thread(runnable,"张杰");
        Thread t4=new Thread(runnable,"薛之谦");
        Thread t5=new Thread(runnable,"陈奕迅");
        t1.start();
        t2.start();
        t3.start();
        t4.start();
        t5.start();
    }

同步方法

如果在一个方法中,所有的逻辑,都需要放到同一个同步代码段中执行。 这样的方法,可以直接做成同步方法。
同步方法中的所有的逻辑,都是在一个同步代码段中执行的。
如果是一个静态方法,使用当前类做类锁;如果是一个非静态方法,使用this做对象锁。

public static synchronized Chairman getInstance() {
	if (Instance == null) {
		Instance = new Chairman();
 	}
	return Instance; 
}

单例设计模式

饿汉式和懒汉试

public class Test {
    public static void main(String[] args) {
        Boss1 boss1=Boss1.getInstance();
        Boss2 boss2=Boss2.getInstance();
    }
}
class Boss1{
    //私有化构造方法,杜绝从外界通过new的⽅式实例化对象的可能性。
    private Boss1(){
        System.out.println("懒汉式");
    }
    //声明一个私有的静态的Boss1对象
    private static Boss1 instance=null;
    //提供一个公有的,静态的,能够获取当前类对象的方法
    public static Boss1 getInstance(){
     if(instance==null){
         instance=new Boss1();
     }
     return instance;
    }
}

class Boss2{
    //私有化构造方法,杜绝从外界通过new的⽅式实例化对象的可能性。
    private Boss2(){
        System.out.println("饿汉式");
    }
    //实例化一个私有的静态的Boss2对象
    private static Boss2 instance=new Boss2();
    //提供一个公有的,静态的,能够获取当前类对象的方法
    public static Boss2 getInstance(){
        return instance;
    }
}

懒汉式单例,在多线程的环境下,会出现问题。由于临界资源问题的存在,单例对象可能会被实例化多次。
因此,单例设计模式,尤其是懒汉式单例,需要针对多线程的环境进行处理。

public class Boss {
   private Boss() {}
   private static Boss instance ;
   public static synchronized Boss getInstance() {
       if (instance == null) {
           instance = new Boss();
       }
       return instance;
   }
}

死锁

多个线程,同时持有对方需要的锁标记,等待对方释放自己需要的锁标记。

此时就是出现死锁,线程之间彼此持有对方需要的锁标记,而不进行释放,都在等待。

public class Test {
    public static void main(String[] args) {
        Runnable runnableA=()->{
            synchronized ("a"){
                System.out.println("线程A获取了a锁,等待获取b锁");
                synchronized("b"){
                    System.out.println("线程A同时获取了a锁和b锁");
                }
            }
        };

        Runnable runnableB=()->{
            synchronized ("b"){
                System.out.println("线程B获取了b锁,等待获取a锁");
                synchronized("a"){
                    System.out.println("线程B同时获取了b锁和a锁");
                }
            }
        };

        new Thread(runnableA,"A").start();
        new Thread(runnableB,"B").start();
    }
}

wait、notify

Object类中几个方法如下:

  • wait()
    • 等待,让当前的线程,释放自己持有的指定的锁标记,进入到等待队列。
    • 等待队列中的线程,不参与CPU时间⽚的争抢,也不参与锁标记的争抢。
  • notify()
    • 通知、唤醒。唤醒等待队列中,⼀个等待这个锁标记的随机的线程
    • 被唤醒的线程,进⼊到锁池,开始争抢锁标记。
  • notifyAll()
    • 通知、唤醒。唤醒等待队列中,所有的等待这个锁标记的线程。
    • 被唤醒的线程,进⼊到锁池,开始争抢锁标记。

wait和sleep的区别

  • sleep()方法,在休眠时间结束后,会自动的被唤醒。而wait()进入到的阻塞态,需要被notify/notifyAll手动唤醒。
  • wait()会释放自己持有的指定的锁标记,进入到阻塞态。sleep()进入到阻塞态的时候,不会释放自己持有的锁标记。
public class Test {
    public static void main(String[] args) {
        Runnable runnableA=()->{
            synchronized ("a"){
                System.out.println("线程A获取了a锁,等待获取b锁");
                synchronized("b"){
                    System.out.println("线程A同时获取了a锁和b锁");
                    // 当 "b" 锁使用结束之后,通知另外⼀个线程使用结束了
                    "b".notify();
                }
                // 当 "a" 锁使用结束之后,通知另外⼀个线程使用结束了
                "a".notify();
            }
        };

        Runnable runnableB=()->{
            synchronized ("b"){
                System.out.println("线程B获取了b锁,等待获取a锁");
                try {
                    // 释放自己持有的 "b" 锁标记,进入等待队列
                    "b".wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized("a"){
                    System.out.println("线程B同时获取了b锁和a锁");
                }
            }
        };

        new Thread(runnableA,"A").start();
        new Thread(runnableB,"B").start();
    }
}

public class Test {
    public static void main(String[] args) {
        Runnable runnableA=()->{
            synchronized ("a"){
                System.out.println("线程A获取了a锁,等待获取b锁");
                try {
                    // 让当前线程释放自己持有的 "a" 锁
                    // 进入到等待队列
                    "a".wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized("b"){
                    System.out.println("线程A同时获取了a锁和b锁");
                    // 当 "b" 锁使用结束之后,通知另外⼀个线程使用结束了
                }
            }
        };

        Runnable runnableB=()->{
            synchronized ("b"){
                System.out.println("线程B获取了b锁,等待获取a锁");
                synchronized("a"){
                    System.out.println("线程B同时获取了b锁和a锁");
                    // 唤醒等待队列中,一个等待 "a" 锁的线程
                    "a".notify();
                }
            }
        };

        new Thread(runnableA,"A").start();
        new Thread(runnableB,"B").start();
    }
}

练习1

实例化两个线程,同时对一个数字进行操作。一个线程对这个数字进行加1,另外一个线程对这个数字进行减一。输出每一次的操作之后的这个数字的值。

class Program {
    
    private static int number = 10;
    
    public static void main(String[] args) {
        
        Runnable runnable1 = () -> {
            while (true) {
				System.out.println(Thread.currentThread().getName() + "对数字加1,结果是: " + ++number);
            }
        };
        
        Runnable runnable2 = () -> {
            while (true) {
				System.out.println(Thread.currentThread().getName() + "对数字减1,结果是: " + --number);
            }
        };
        
        Thread thread0 = new Thread(runnable1, "加线程");
        Thread thread1 = new Thread(runnable2, "减线程");
        
        thread0.start();
        thread1.start();   
    }
}

练习2

有五个人同时过一个独木桥,一个独木桥同时只能允许一个人通过。每一个人通过独木桥的时间是随机在 [5,10] 秒,输出这个独木桥上每一个人的通过详情,例如:张三开始过独木桥了... 张三通过独木桥了!

public static void main(String[] args) {
        // 有五个人同时过一个独木桥,一个独木桥同时只能允许一个人通过。每一个人通过独木桥的时间是随机在 [5,10] 秒,输出这个独木桥上每一个人的通过详情,例如:张三开始过独木桥了... 张三通过独木桥了!
        Runnable runnable = () -> {
            synchronized ("") {
                // 模拟人通过桥的情况
                System.out.println("【" + Thread.currentThread().getName() + "】开始过独木桥了...");
                Random random = new Random();
                double time = Math.random() * 5 + 5;
                try {
                    Thread.sleep((long)(time * 1000));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("【" + Thread.currentThread().getName() + "】已经通过独木桥了,用时: " + time + "秒");
            }
        };

        Thread thread1 = new Thread(runnable, "小明");
        Thread thread2 = new Thread(runnable, "小美");
        Thread thread3 = new Thread(runnable, "小娟");
        Thread thread4 = new Thread(runnable, "小灰");
        Thread thread5 = new Thread(runnable, "老王");


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

练习3

小明、小美、小娟共用一张银行卡,其中,小明每次向银行卡里面存 [1000,2000] 块钱,小美、小娟每次从银行卡里面取 [300, 600] 块钱。当卡里没钱的时候,会通知小明存钱。小明存完钱之后,通知他们两个人取钱。设计程序,实现这个场景。

public class Three {

    public static void main(String[] args) {

        Random random = new Random();
        BankCard card = new BankCard();


        Thread xiaoming = new Thread(() -> {
            while (true) {
                card.saveMoney(random.nextInt(1000) + 1000);
            }
        }, "小明");

        Runnable runnable = () -> {
            while (true) {
                card.getMoney(random.nextInt(300) + 300);
            }
        };

        xiaoming.start();
        new Thread(runnable, "小美").start();
        new Thread(runnable, "小娟").start();

    }

    /**
     * 银行卡类
     */
    private static class BankCard {
        // 钱的数量
        private int money;

        /**
         * 将指定的金额存入到银行卡中
         * @param money 需要存的金额
         */
        public synchronized void saveMoney(int money) {
            this.money += money;
            System.out.println("小明向银行卡中存了" + money + "元,剩余数量" + this.money + "元");
            // 存完钱
            this.notifyAll();
            // 等待下次存钱
            try {
                this.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

        /**
         * 从银行卡取钱
         * @param money 希望取的钱
         */
        public synchronized void getMoney(int money) {
            // 找到最小值
            money = Math.min(money, this.money);
            // 取钱
            this.money -= money;
            System.out.println("【"+Thread.currentThread().getName()+"】取了【"+money+"】元,剩余: 【"+this.money+"】");

            if (this.money <= 0) {
                // 通知小明存钱
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

猜你喜欢

转载自www.cnblogs.com/dch-21/p/12816854.html