欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

线程安全问题、同步代码块、同步代码块的锁问题以及同步方法的应用和锁问题、Lock锁、死锁问题

程序员文章站 2022-04-17 14:39:17
...

线程安全问题、同步代码块、同步代码块的锁问题以及同步方法的应用和锁问题、Lock锁、死锁问题

线程安全问题

​ 前面讲解过电影院售票程序,从表面上看不出什么问题,但是在真实生活中,
​ 售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
​ 每次卖票延迟100毫秒,此时就有可能出现同票或者负票的情况

出现线程安全问题的前提条件:

​ 是否是多线程环境
​ 是否有共享数据
​ 是否有多条语句操作共享数据

同步代码块解决线程安全问题

格式:

synchronized(对象){ //同步代码代码块上的锁,是一个互斥锁。
死循环
需要同步的代码;
}

这个同步代码块保证数据的安全性的一个主要因素就是这个对象
注意这个对象 要定义为静态成员变量 才能被所有线程共享

需要这个对象被所有的线程对象所共享

这个对象其实就是一把锁.

这个对象习惯叫做监视器



public class CellRunnable implements Runnable{
    //这个票让三个线程共享
    static int piao=100;
    //确保这个锁对象,只有一个,多个线程公用一把锁
    static Object obj=new Object();
    @Override
    public void run() {
        while (true) {
         synchronized (obj){
             if (piao >= 1) {
                 try {
                     Thread.sleep(50);
                 } catch (InterruptedException e) {
                     e.printStackTrace();
                 }
                 System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
             }
         }
            //th1 执行完了,出了同步代码块,就会释放锁。释放锁了之后,多个线程再去争抢CPU的时间片
        }
    }
}

public class MyTest {
    public static void main(String[] args) {
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3= new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}

同步的好处: 同步的出现解决了多线程的安全问题。
同步的弊端: 当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。

java的内置锁:每个java对象都可以用做一个实现同步的锁,这些锁成为内置锁。
线程进入同步代码块或同步方法的时候会自动获得该锁,在退出同步代码块或同步方法时会释放该锁。
获得内置锁的唯一途径就是进入这个锁的保护的同步代码块或方法。
java内置锁是一个互斥锁,这就是意味着最多只有一个线程能够获得该锁,
当线程A尝试去获得线程B持有的内置锁时,线程A必须等待或者阻塞,
直到线程B释放这个锁,如果B线程不释放这个锁,那么A线程将永远等待下去。

同步方法解决线程安全问题

同步方法使用的是对象锁

java的对象锁和类锁:java的对象锁和类锁在锁的概念上基本上和内置锁是一致的,
但是,两个锁实际是有很大的区别的,对象锁是用于对象实例方法,或者一个对象实例上的,
类锁是用于类的静态方法或者一个类的class对象上的。
我们知道,类的对象实例可以有很多个,但是每个类只有一个class对象,
所以不同对象实例的对象锁是互不干扰的,但是每个类只有一个类锁。
但是有一点必须注意的是,其实类锁只是一个概念上的东西,并不是真实存在的,
它只是用来帮助我们理解锁定实例方法和静态方法的区别的.


public class CellRunnable implements Runnable{
    //这个票让三个线程共享
    static int piao=100;
    //确保这个锁对象,只有一个,多个线程公用一把锁
    static Object obj=new Object();
    int i=1;
    @Override
    public void run() {
        while (true) {
        if (i%2==0){
            synchronized (this){
                //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
                if (piao >= 1) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
                }
            }
        }else{
            maipiao();
        }
        i++;
        }
    }
    //同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
    //同步方法:默认用的锁对象是this
    private synchronized void maipiao() {
        //System.out.println(this);
        if (piao >= 1) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
        }

    }
}

public class MyTest {
    public static void main(String[] args) {
        //创建了一次任务 100
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3= new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();

    }
}

静态同步方法解决线程安全问题

静态同步方法使用的是类锁

类的对象实例可以有很多个,但是每个类只有一个class对象,故可以保证锁只有一把


public class CellRunnable implements Runnable{
    //这个票让三个线程共享
    static int piao=100;
    //确保这个锁对象,只有一个,多个线程公用一把锁
    static Object obj=new Object();
    int i=1;
    @Override
    public void run() {
        while (true) {
            //模拟一下真实的售票环境,有网络延迟。
        if (i%2==0){
            synchronized (CellRunnable.class){
                //当th1这个线程进来同步代码块后,就持有了这个锁,其他线程没有持有锁,那么就要处于等待状态,等在同步代码块的外面
                if (piao >= 1) {
                    try {
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
                }
            }
        }else{
            maipiao();
        }
        i++;
        }
    }
    //同步方法:我们可以把一个方法用synchronized这个关键字修饰,来封装一段代码,来解决线程安全问题
    //静态同步方法:默认用的锁对对象,用的是当前类的字节码对象
    private static synchronized void maipiao() {
        //System.out.println(this);
        if (piao >= 1) {
            try {
                Thread.sleep(50);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
        }

    }
}

public class MyTest {
    public static void main(String[] args) {
        //创建了一次任务 100
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3= new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();
    }
}


Lock锁

Lock锁的概述
虽然我们可以理解同步代码块和同步方法的锁对象问题,
但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,
为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock

Lock和ReentrantLock

static ReentrantLock lock=new ReentrantLock();

void lock() 加锁
void unlock() 释放


public class CellRunnable implements Runnable{
    //这个票让三个线程共享
    static int piao=100;
    //确保这个锁对象,只有一个,多个线程公用一把锁
    static ReentrantLock lock=new ReentrantLock();
    @Override
    public void run() {
        while (true) {
            //th1 th2 th3
            //加锁
            lock.lock();
            try{
                if (piao >= 1) {
                    try {
                        //模拟一下真实的售票环境,有网络延迟。
                        Thread.sleep(50);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName() + "正在出售第:" + (piao--) + " 张票");
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
}

public class MyTest {
    public static void main(String[] args) {
        //创建了一次任务 100
        CellRunnable cellRunnable = new CellRunnable();
        Thread th1 = new Thread(cellRunnable, "窗口1");
        Thread th2 = new Thread(cellRunnable, "窗口2");
        Thread th3= new Thread(cellRunnable, "窗口3");
        th1.start();
        th2.start();
        th3.start();

    }
}

死锁问题

如果出现了同步嵌套,就容易产生死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象

死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态


public interface ObjectUtils {
    //创建两个对象来充当两把锁
    public static final Object objA=new Object();
    public static final Object objB=new Object();
}


public class MyThread extends Thread{
    boolean flag;

    public MyThread(boolean flag) {
        this.flag=flag;
    }

    @Override
    public void run() {
        if (flag){
            synchronized (ObjectUtils.objA){
                System.out.println("true 线程持有了objA锁,进来执行了AAAA");
                synchronized (ObjectUtils.objB){
                    System.out.println("true 线程持有了objB锁,进来执行了BBB");
                }
            }
        }else {
            synchronized (ObjectUtils.objB){
                System.out.println("false线程持有了objB锁,进来执行BBB");
                synchronized (ObjectUtils.objA){
                    System.out.println("false 线程持有了objA锁,进来执行了AAA");
                }
            }
        }
    }
}


public class MyTest {
    public static void main(String[] args) {
        MyThread th1 = new MyThread(true);
        MyThread th2 = new MyThread(false);
        th1.start();
        th2.start();
    }
}

相关标签: java SE java