Java多线程(五)锁机制之synchronized 同步方法和同步块

1.锁是什么:

锁是为了控制共享的资源在被多个线程同时使用时产生冲突而产生的。

Java为了解决多线程带来的安全问题,采用了同步的方式来解决安全问题。

2.被锁住的时什么

被锁的资源只有两种:1) 类的对象  2) 或者类的Class

3.怎么锁

解决多线程的安全问题,Java提供了三种方式:

  • 同步方法
  • 同步块
  • java juc包(锁机制)

4.Synchronzied与Locks的区别

  • synchronized是Java的关键字,Locks是Java的一个接口。
  • synchroized无法获取判断锁的状态,Lock是可以判断获取锁
  • synchronized的自动的,会自动释放锁;lock要手动释放锁(一个是自动挡,另一个是手动档)
  • synchronized默认是可重入锁,非公平锁,因为是关键字,不可改变;lock是可重入锁,可以设置是否公平
  • synchronized适合锁少量代码;lock适合大量

5.本篇讲解怎么使用synchronized这个关键字(同步方法和同步代码块)

5.0例如以下会带来线程安全问题:

public class Cinema {

    public static void main(String[] args) {
        TicketOffice office = new TicketOffice();
        //创建3个售票员
        Thread seller1 = new Thread(office);
        Thread seller2 = new Thread(office);
        Thread seller3 = new Thread(office);
        seller1.start();
        seller2.start();
        seller3.start();
     }
}

class TicketOffice implements Runnable {
    //定义10张电影票
    int ticket = 10;

    @Override
    public void run() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //开始卖电影票,每售出一张电影票,就自动减去1
            System.out.println(Thread.currentThread().getName() + "正在卖第" + (ticket--) + "张票!");
        }
    }
}

结果:

出现了重复售票(一票多卖,一女多嫁)和票数为负数的情况。

加Thread.sleep()只是为了让出错的概率增大

Thread-0正在卖第10张票!
Thread-1正在卖第10张票!
Thread-2正在卖第10张票!
Thread-1正在卖第9张票!
Thread-2正在卖第9张票!
Thread-0正在卖第9张票!
Thread-1正在卖第8张票!
Thread-2正在卖第7张票!
Thread-0正在卖第8张票!
Thread-0正在卖第6张票!
Thread-1正在卖第4张票!
Thread-2正在卖第5张票!
Thread-0正在卖第3张票!
Thread-2正在卖第2张票!
Thread-1正在卖第3张票!
Thread-1正在卖第1张票!
Thread-2正在卖第-1张票!
Thread-0正在卖第0张票!

5.1同步代码块

下面是同步代码块的语法

synchronized(obj){
    //需要同步的地方
    //obj可以是任意共享的资源(对象),同步代码块同步的是对象
}

下面通过同步代码块来解决卖票问题

public class Cinema {


    public static void main(String[] args) {
        TicketOffice office = new TicketOffice();
        //创建3个售票员
        Thread seller1 = new Thread(office);
        Thread seller2 = new Thread(office);
        Thread seller3 = new Thread(office);
        seller1.start();
        seller2.start();
        seller3.start();
    }
}

class TicketOffice implements Runnable {
    //定义10张电影票
    int ticket = 10;
    Object obj = new Object();//这个一定要写在类里,不要写在方法里

    //写在类里面才是共享的资源,写在方法里面,每个线程都有自己的资源,就不是共享的了
    @Override
    public void run() {
        synchronized (obj) {
            while (ticket > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //开始卖电影票,没售出一张电影票,就自动减去1
                System.out.println(Thread.currentThread().getName() + "正在卖第" + (ticket--) + "张票!");
            }
        }
    }
}

5.2同步方法

修饰符 synchronized 返回值 方法名(){
    //方法体
}

通过同步方法来解决线程安全问题.

下面的代码有重大问题:线程是安全了,但却编程单线程的了。

因为,加入线程0获得执行权,线程0会循环买票,直到买完所有票才结束循环。而线程1,2虽然获得执行权,但不会进入到买票方法,因为被锁住了。

当解锁时,票已经买完,所以,所有票都是线程0卖完的

public class Cinema {


    public static void main(String[] args) {
        TicketOffice office = new TicketOffice();
        //创建3个售票员
        Thread seller1 = new Thread(office);
        Thread seller2 = new Thread(office);
        Thread seller3 = new Thread(office);
        seller1.start();
        seller2.start();
        seller3.start();
    }
}

class TicketOffice implements Runnable {
    //定义10张电影票
    int ticket = 10;
    Object obj = new Object();//这个一定要写在类离,不要写在方法李

    //写在类里面才是共享的资源,写在方法里面,每个线程都有自己的资源,就不是共享的了
    @Override
    public void run() {
        sellTicket();
    }

    //同步方法
    public synchronized void sellTicket() {
        while (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //开始卖电影票,没售出一张电影票,就自动减去1
            System.out.println(Thread.currentThread().getName() + "正在卖第" + (ticket--) + "张票!");
        }
    }
}

 对上面的代码进行改进,在run()方法上加个循环,在sellTicket()方法里面加个判断语句。

这个3个thread都会调用run()方法,调用run()方法时先判断票数是否大于0,如果大于0就执行卖票方法。

而在卖票方法里面也必须判断票数,如果没有判断票数就会出现票数为0或者负数的情况。

加入3个三线都同时到了run方法,此时票数时大于0的,当线程0卖了最后一张票时结束了卖票方法,并把票数设置为0;

而此时线程1和线程而暂停在run方法和卖票方法当中,它们得到ticket值分别为0和-1.

public class Cinema {

    public static void main(String[] args) {
        TicketOffice office = new TicketOffice();
        System.out.println("Cinema-->" + office);
        //创建3个售票员
        Thread seller1 = new Thread(office);
        Thread seller2 = new Thread(office);
        Thread seller3 = new Thread(office);
        seller1.start();
        seller2.start();
        seller3.start();
    }
}

class TicketOffice implements Runnable {
    //定义10张电影票
    int ticket = 10;
    Object obj = new Object();//这个一定要写在类离,不要写在方法李

    //写在类里面才是共享的资源,写在方法里面,每个线程都有自己的资源,就不是共享的了
    @Override
    public void run() {
        System.out.println("-----run----" + Thread.currentThread().getName());
        while (ticket > 0) {
            sellTicket();
        }
    }

    //同步方法
    public synchronized void sellTicket() {
        //  System.out.println("This in sellTicket()-->" + this);
        if (ticket > 0) {
            //开始卖电影票,没售出一张电影票,就自动减去1
            System.out.println(Thread.currentThread().getName() + "正在卖第" + (ticket--) + "张票!");
        }
    }
}

 运行结果

Cinema-->TicketOffice@1b6d3586
-----run----Thread-0
-----run----Thread-2
Thread-2正在卖第10张票!
Thread-0正在卖第9张票!
Thread-2正在卖第8张票!
Thread-2正在卖第7张票!
Thread-2正在卖第6张票!
Thread-2正在卖第5张票!
Thread-2正在卖第4张票!
Thread-2正在卖第3张票!
-----run----Thread-1
Thread-2正在卖第2张票!
Thread-2正在卖第1张票!

上面代码面临的问题:

有的线程执行次数很多,有的线程一次都没执行。

 我们可以采用线程间的通信机制来解决这个问题,即:wait和notify方法来暂停和唤醒线程,甚至可以让线程交替执行。

后记:

第一次写于:2020年3月7日 星期六 上海 晴

第二次编辑:2020年3月9日 星期一 上海 雨

参考资料:

https://www.geeksforgeeks.org/synchronized-in-java/

https://www.cnblogs.com/xckxue/p/8685675.html

阿里程序员:

https://www.jianshu.com/p/f9b1159d4fde

https://www.jianshu.com/p/2ed498b43628

https://segmentfault.com/a/1190000013512810

原文地址:https://www.cnblogs.com/majestyking/p/12437426.html