Java线程安全

/**
 * 同步代码块:synchronized(锁对象){ 被同步的代码}
 * 锁对象可以是任何对象,但是必须唯一,可以理解为锁的钥匙必须是同一把,这样别的线程才能打开
 */

public class TicketDemo2 {
    public static void main(String[] args) {
        TicketMethod ticket= new TicketMethod();
        new Thread(ticket).start();
        new Thread(ticket).start();
        new Thread(ticket).start();
    }
    static class Ticket implements Runnable{
        private int count=10;
        Object o=new Object();
        @Override
        public void run() {
            synchronized (o){//若是这种情况,那么任何一个线程
//抢占了CPU会一直循环直到票卖完
            
                while (count>0){
                    System.out.println(Thread.currentThread().getName()+"开始卖票");
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    count--;
                    System.out.println("当前票数为"+count);
                }
            }

        }
    }
    static class Ticket2 implements Runnable{
        private int count=10;
        Object o=new Object();//只有创建一个Ticket2对象,因此只有一个Object对象,可以当作锁
        //若将o创建在run方法中则会创建3次,不满足唯一性,无法作为锁(或者说是一把形同虚设的锁)
        @Override
        public void run() {
                while (true){
                    if (count>0){
                        synchronized (o) {//但是将同步代码块放在这里会导致到-2才停止
                            //因为此时三个线程都通过了count>0要争抢锁,当count=0时占用CPU的那个线程
                            //执行else跳出循环,run方法执行完毕线程死亡,但是另外两个线程依然处于count>0代码的后面,
                            //因此这两个线程会执行同步代码块的语句导致count=-2
                            System.out.println(Thread.currentThread().getName()+"开始卖票");
                            try {
                                Thread.sleep(1000);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                            count--;
                            System.out.println("当前票数为"+count);
                        }
                    }
                    else {

                        break;

                    }
                }
            }

    }
    static class Ticket3 implements Runnable{
        private int count=10;
        @Override
        public void run() {
            while (true){
                synchronized (this) {//正确
                if (count>0){
                        System.out.println(Thread.currentThread().getName()+"开始卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println("当前票数为"+count);
                    }
                else {
                    break;
                }   
               }
            }
        }
    }
    static class TicketMethod implements Runnable{
        private int count=10;
        @Override
        public void run() {
            while (true){
//需要特别注意的是method方法使用的锁为this,如果此时在此编写一个同步代码块线程进入此代码块,则method方法也被锁住无法执行,因为它们共享同一把锁,相当于同一把锁锁住了两扇门
                    boolean trueOrFalse= method();
                    if (!trueOrFalse){
                        break;
                    }
            }
        }

        private synchronized boolean method() {
            //同步方法的锁默认为this
            //若为静态方法则锁为TicketMethod.class
            if (count>0){
                System.out.println(Thread.currentThread().getName()+"开始卖票");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                count--;
                System.out.println("当前票数为"+count);
                return true;
            }
               return false;
        }
    }
    /*
    同步代码块和同步方法都是隐式锁,Lock为显式锁
     */
    static class TicketLock implements Runnable{
        private int count=10;
        private Lock lock=new ReentrantLock();//显示锁lock
        @Override
        public void run() {
            while (true){
                    lock.lock();
                    if (count>0){
                        System.out.println(Thread.currentThread().getName()+"开始卖票");
                        try {
                            Thread.sleep(1000);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        count--;
                        System.out.println("当前票数为"+count);
                    }
                    else {
                        break;
                    }
                    lock.unlock();
            }
        }
    }
}

公平锁讲究先来后到(排队),不公平锁则无需排队谁争抢到就上CPU运行(以上均为不公平锁)ReentryLock构造器中传入true就表示该锁为公平锁.

线程死锁

多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。由于线程被无限期地阻 塞,因此程序不可能正常终止。

死锁必须具备以下四个条件

  1. 互斥条件:该资源任意一个时刻只由一个线程占用。
  2. 请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不剥夺条件:线程已获得的资源在未使用完之前不能被其他线程强行剥夺,只有自己使用完毕后才释放资源。
  4. 循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

 如何避免线程死锁? 只要破坏产生死锁的四个条件中的其中一个就可以

  1.  破坏互斥条件 这个条件无法破坏,因为用锁本来就是想让他们互斥的(临界资源需要互斥访问)
  2. 破坏请求与保持条件 一次性申请所有的资源。
  3. 破坏不剥夺条件 占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
  4. 破坏循环等待条件 靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
  5. 锁排序法:(重点) 指定获取锁的顺序,比如某个线程只有获得A锁和B锁,才能对某资源进行操作,在多线程条件下, 通过指定锁的获取顺序,比如规定,只有获得A锁的线程才有资格获取B锁,按顺序获取锁就可以避 免死锁。这是解决死锁很好的一种方法。
  6. 使用显式锁中的ReentrantLock.try(long,TimeUnit)来申请锁

线程的通信

public static void main(String[] args) {
        Food f = new Food();
        new Cook(f).start();
        new Waiter(f).start();
    }
    static class Cook extends Thread{
        private Food f;
        public Cook(Food f) {
            this.f = f;
        }

        @Override
        public void run() {
            for(int i=0;i<100;i++){
                if(i%2==0){
                    f.setNameAndSaste("老干妈小米粥","香辣味");
                }else{
                    f.setNameAndSaste("煎饼果子","甜辣味");
                }
            }
        }
    }
    static class Waiter extends Thread{
        private Food f;
        public Waiter(Food f) {
            this.f = f;
        }
        @Override
        public void run() {
            for(int i=0;i<100;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                f.get();
            }
        }

    }
    static class Food{
        private String name;
        private String taste;
        public  void setNameAndSaste(String name,String taste){
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
        }
        public  void get(){
                System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
            }
        }

    }

两个线程都在操作共同资源(Food),给food的get与set方法设置为synchronized只能确保菜的名字与味道对应,但是无法保证上菜的顺序,可能连续几次都是初始抢到CPU,那做好的菜都无法端走,因为synchronized是不公平锁。因此需要线程的通信

static class Food{
        private String name;
        private String taste;

        //true 表示可以生产
        private boolean flag = true;

        public synchronized void setNameAndSaste(String name,String taste){
            if(flag) {
                this.name = name;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.taste = taste;
                flag = false;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        public synchronized void get(){
            if(!flag) {
                System.out.println("服务员端走的菜的名称是:" + name + ",味道:" + taste);
                flag = true;
                this.notifyAll();
                try {
                    this.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

sleep() 方法和 wait() 方法异同

  • sleep方法:是Thread类的静态方法,当前线程将睡眠n毫秒,线程进入阻塞状态。当睡眠时间到 了,会解除阻塞,进入可运行状态,等待CPU的到来。睡眠不释放锁(如果有的话)。wait方法:是Object的方法,必须与synchronized关键字一起使用,线程进入阻塞状态,当notify 或者notifyall被调用后,会解除阻塞。但是,只有重新占用互斥锁之后才会进入可运行状态。睡眠 时,会释放互斥锁。
  • sleep 方法不释放锁,而 wait 方法释放了锁 。
  • sleep 通常被用于暂停执行,wait 通常被用于线程通信
  • sleep() 方法执行完成后,线程会自动苏醒。或者可以使用 wait(long timeout)超时后线程会自动苏 醒。wait() 方法被调用后,线程不会自动苏醒,需要别的线程调用同一个对象上的 notify() 或者 notifyAll() 方法 相同 两者都可以暂停线程的执行。

两者都可以暂停线程的执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值