Java基础二十七(线程安全、Lock锁)

例子:创建三个窗口买票,总票数100张,使用Runnable接口的方式

  • 1.问题:卖票过程中,出现了重票、错票–>出现了线程的安全问题

  • 2.问题出现的原因:当某个线程操作车票的过程中,尚未操作完成时,其他线程参与进来,也操作车票

  • 3.如何解决:当一个线程a在操作ticket时,其他线程不能参与进来。直到线程a操作完成ticket时,其他线程才可以开始操作ticket。这种情况即使线程a出现了阻塞,也不能改变

  • 4.在java中,我们通过同步机制,来解决线程的安全问题。
      方式一:同步代码块
      synchronized(同步监视器){
        //需要被同步的代码
      }

  • 说明:1.操作共享数据的代码,即为需要被同步的代码
      2.共享数据:多个线程共同操作的变量。比如:ticket就是共享数据
      3.同步监视器,俗称:锁。任何一个类的对象,都可以充当锁
      要求:多个线程必须要共用一把锁。

    方式二:同步方法
    如果操作共享数据的代码完成的声明在一个方法中,我们不妨将此方法声明为同步的

class Window1 implements Runnable{

    private int ticket = 100;
    Object obj = new Object();

    @Override
    public void run() {
        while (true){
            synchronized (obj) {
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
                    ticket--;
                }else{
                    break;
                }
            }
        }
    }
}
public class WindowTest1 {
    public static void main(String[] args) {
        Window1 w = new Window1();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

        t1.start();
        t2.start();
        t3.start();

    }

}

方式一:使用同步代码块解决继承Thread类的方式的线程安全问题

  • 例子:创建三个窗口买票,总票数为100张,使用继承Thread类的方式
  • 说明:在继承Thread类创建多线程的方式中,慎用this充当同步监视器,考虑使用当前类充当对象使用
class Window3 extends Thread{

    private static int ticket = 100;

    private static Object obj = new Object();
    @Override
    public void run() {
        while (true) {
            synchronized (obj) {
//            synchronized (Window3.class){
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }
        }
    }
}
public class WindowTest3 {
    public static void main(String[] args) {
        Window3 t1 = new Window3();
        Window3 t2 = new Window3();
        Window3 t3 = new Window3();



        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

方式二:使用同步方法解决实现Runnable接口的线程安全问题

  • 关于同步方法的总结:
      1.同步方法仍然涉及到同步监视器,只是不需要显示的声明。
      2.非静态的同步方法,同步监视器是:this
       静态的同步方法,同步监视器是:当前类本身
class Window4 implements Runnable{

    private int ticket = 100;

    @Override
//    public synchronized void run() {//一个窗口买票
    public void run() {
        while (true){
            show();
        }

    }
    private synchronized void show() {//同步监视器:this
//        synchronized (this){
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
            ticket--;
        }
//    }
    }
}
public class WindowTest4 {
    public static void main(String[] args) {
        Window4 w = new Window4();

        Thread t01 = new Thread(w);
        Thread t02 = new Thread(w);
        Thread t03 = new Thread(w);

        t01.setName("窗口一");
        t02.setName("窗口二");
        t03.setName("窗口三");

        t01.start();
        t02.start();
        t03.start();

    }

}

使用同步方法处理继承Thread类的方式中线程安全问题

class Window5 extends Thread{

    private static int ticket = 100;
    @Override
    public void run() {
        while (true){
            show();
        }
    }

//    private synchronized void show(){//同步监视器:t1, t2, t3
    private static synchronized void show(){//同步监视器:Window5
        if (ticket > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + ticket);
            ticket--;
        }
    }
}
public class WindowTest5 {
    public static void main(String[] args) {
        Window5 t1 = new Window5();
        Window5 t2 = new Window5();
        Window5 t3 = new Window5();

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

方式三:lock锁 —jdk5.0新增

  • 1.面试题:synchronized与lock异同?
      相同:二者都可以解决线程安全问题
      不同:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
            Lock需要手动的启动同步(Lock()),同时结束同步也需要手动的实现(Lock())
  • 2.优先使用顺序
      Lock -> 同步代码块(已经进入了方法体,分配了相应资源) ->同步方法(在方法体之外)
class Window implements Runnable{

    private int ticket = 100;
    //1.实例化ReentrantLock
    private ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        while (true){
            try {

                //2.调用锁定方法lock()
                lock.lock();
                if (ticket > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + ":售票,票号为" + ticket);
                    ticket--;
                } else {
                    break;
                }
            }finally {
                //3.调用解锁方法:unlock
                lock.unlock();
            }
        }
    }
}

public class LockTest {
    public static void main(String[] args) {
        Window w = new Window();

        Thread t1 = new Thread(w);
        Thread t2 = new Thread(w);
        Thread t3 = new Thread(w);

        t1.setName("窗口一");
        t2.setName("窗口二");
        t3.setName("窗口三");

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

演示线程的死锁问题

  • 1.死锁的理解:不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁。
  • 2.说明:
       ①出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
      ②使用同步时,要避免死锁。
public class ThreadTest {
    public static void main(String[] args) {

        StringBuffer s1 = new StringBuffer();
        StringBuffer s2 = new StringBuffer();

        new Thread(){
            @Override
            public void run() {
                synchronized (s1){
                    s1.append("a");
                    s2.append("1");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s2){
                        s1.append("b");
                        s2.append("2");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (s2){
                    s1.append("c");
                    s2.append("3");

                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                    synchronized (s1){
                        s1.append("d");
                        s2.append("4");

                        System.out.println(s1);
                        System.out.println(s2);
                    }
                }
            }
        }).start();

    }
}

死锁演示


class A{
    public synchronized void foo(B b){//同步监视器:A类的对象:a
        System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入了A实例的foo方法");//①
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:" + Thread.currentThread().getName() + "企图调用B实例的last方法");//③
        b.last();
    }
    public synchronized void last(){//同步监视器:A类的对象:a
        System.out.println("进入了A类的last方法内部");
    }


}

class B{
    public synchronized void bar(A a){//同步监视器:b
        System.out.println("当前线程名:" + Thread.currentThread().getName() + "进入了B实例的bar方法");//②
        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("当前线程名:" + Thread.currentThread().getName() + "企图调用A实例的last方法");//④
        a.last();

    }
    public synchronized void last(){//同步监视器:b
        System.out.println("进入了B类的last方法内部");
    }

}
public class DeadLock implements Runnable{
    A a = new A();
    B b = new B();

    public void init(){
        Thread.currentThread().setName("主线程 ");
        //调用a对象的foo方法
        a.foo(b);
        System.out.println("进入主线程之后");
    }

    @Override
    public void run() {
        Thread.currentThread().setName("副线程 ");
        //调用b对象的bar方法
        b.bar(a);
        System.out.println("进入副线程之后");
    }

    public static void main(String[] args) {
        DeadLock deadLock = new DeadLock();
        new Thread(deadLock).start();

        deadLock.init();
    }
}

练习:银行有一个账户

  • 有两个储户分别向同一个账户存3000元,每次存1000,存三次。每次存完打印账户余额
  • 分析:
  • 1.是否是多线程问题?是!两个储户线程
  • 2.是否共享数据 是!账户
  • 3.是否有线程安全问题?是!
  • 4.需要考虑如何解决线程安全问题? 同步问题:有三种方式

方式一:继承Thread类

class Account1{
    private double balance;
    public Account1(double balance){
        this.balance = balance;
    }

    //存钱
    public synchronized void deposit(double amt){
        if(amt > 0){
            balance += amt;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "存钱成功。余额为:" + balance);
        }
    }


}

class Customer1 extends Thread{
    private Account1 acct;

    public Customer1(Account1 acct){
        this.acct = acct;
    }

    @Override
    public void run() {
        for(int i = 0; i < 3; i ++){
            acct.deposit(1000);
        }
    }
}
public class AccountTest1 {
    public static void main(String[] args) {
        Account1 acct = new Account1(0);
        Customer1 c1 = new Customer1(acct);
        Customer1 c2 = new Customer1(acct);

        c1.setName("甲");
        c2.setName("乙");

        c1.start();
        c2.start();

    }
}

方式二:实现Runnable接口

class Account2{
    private double balance;
    public Account2(double balance){
        this.balance = balance;
    }

    public synchronized void deposit(double amt){
        if(amt > 0){
            balance += amt;
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "存钱成功。余额为:" + balance);
        }
    }

}

class Customer2 implements Runnable{
    private Account2 acct;

    public Customer2(Account2 acct){
        this.acct = acct;
    }

    @Override
    public void run() {
        for(int i = 0; i < 3; i ++){
            acct.deposit(1000);
        }
    }
}
public class AccountTest2 {
    public static void main(String[] args) {
        Account2 acct = new Account2(0);
        Customer2 cus = new Customer2(acct);

        Thread t1 = new Thread(cus);
        Thread t2 = new Thread(cus);

        t1.setName("甲");
        t2.setName("乙");

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

}

方式三:lock锁

class Account3{
    private double balance;
    private ReentrantLock lock = new ReentrantLock();

    public Account3(double balance){
        this.balance = balance;
    }

    public void deposit(double amt) {
        try {
            lock.lock();
            if (amt > 0) {
                balance += amt;
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "存款成功。余额为:" + balance);
            }
        }finally {
            lock.unlock();
        }
    }

}

class Customer3 implements Runnable{

    private Account3 acct;



    public Customer3(Account3 acct){
        this.acct = acct;
    }

    @Override
    public void run() {
        for(int i = 0; i < 3; i ++) {
            acct.deposit(1000);
        }

    }
}
public class AccountTest3 {
    public static void main(String[] args) {
        Account3 acct = new Account3(0);
        Customer3 cus = new Customer3(acct);

        Thread t1 = new Thread(cus);
        Thread t2 = new Thread(cus);

        t1.setName("甲");
        t2.setName("乙");

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

    }
}

练习:使用线程安全完成懒汉式单例模式

public class BankTest {

}

//懒汉式
class Bank{

    private Bank(){}

    private static Bank instance = null;

//    public static synchronized Bank getInstance(){

    public static Bank getInstance() {
        //方式一:效率稍差
//        synchronized (Bank.class) {
//            if (instance == null) {
//
//                instance = new Bank();
//            }
//            return instance;
//        }

        //方式二:效率稍高
        if(instance == null){
            synchronized (Bank.class){
                if(instance == null){
                    instance = new Bank();
                }
            }
        }
        return instance;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值