JAVA---解决线程安全问题的三种方式(同步代码块,同步方法,锁)

JAVA—解决线程安全问题的方式

怎么产生线程安全问题?当多个线程访问共享数据的时候会产生安全问题

解决安全问题的三种方式:

1.同步代码块
//格式:
/*
    * 同步代码块格式:锁对象可以是任意的但是必须要唯一
    * synchronized(锁对象){
    *   可能会出现线程安全的代码(访问了共享数据)
    * }
    * */

如下卖票案例所示;

Runnable实现类方法

public class RunnableImp implements Runnable {

    //创建一个任意的锁对象
    Object obj = new Object();
    /*
    * 同步代码块格式:锁对象可以是任意的但是必须要唯一
    * synchronized(锁对象){
    *   可能会出现线程安全的代码(访问了共享数据)
    * }
    * */
    private int ticket = 100;
    @Override
    public void run() {
        while (true){
            synchronized (obj){   //传递任意的锁对象,对代码进行上锁,只有一个线程能访问
                if (ticket > 0){
                    System.out.println(Thread.currentThread().getName() + ":" + ticket);
                    ticket--;
                }else {
                    return;
                }
            }

        }
    }
}

main方法:

public class Demo {
    public static void main(String[] args) {
        RunnableImp ri = new RunnableImp();  //创建实现类对象

        Thread t0 = new Thread(ri);  //将实现类对象传递给Thread方法
        Thread t1 = new Thread(ri);
        Thread t2 = new Thread(ri);

        //开启3个线程
        t0.start();
        t1.start();
        t2.start();
    }
}
同步代码块的原理:

同步代码块使用了锁对象,这个对象也叫同步锁,也叫对象锁,也叫对象监视器

如上3个线程t0,t1,t2一起抢夺cpu的执行权,谁抢到了谁执行run方法进行卖票

假设t0抢到了执行权,执行run方法,遇到了synchronized代码块,这是t0会检查synchronized代码块是否有锁对象,发现有锁对象,进入到同步代码块中进行卖票

假设t1又抢到了cpu的执行权,执行run方法,遇到了synchronized代码块,这是t1会检查synchronized代码块是否有锁对象,发现没有锁对象,t1就会进入到阻塞状态,回一直带等待t0执行完同步代码块中的代码后,归还锁对象,t1获取到锁对象后才会进入到同步代码块中。

总结:同步中的线程,没有执行完毕不会归还锁对象,同步外的线程,没有锁对象进不去同步代码块中
同步代码块的弊端:
同步保证了只能有一个线程在同步中执行共享数据,保证了安全,程序频繁的判断锁,释放锁,程序的效率降低
2.同步方法(普通同步方法和静态同步方法):
2.1:普通同步方法

使用步骤:1.把访问共享数据的代码抽离出来,放到一个方法中,方法上添加synchronized修饰词

案例:

实现类:

public class RunnableImp implements Runnable {

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

    private synchronized void sellTicket() {  //同步方法
        if (ticket > 0){
            System.out.println(Thread.currentThread().getName() + ":" + ticket);
            ticket--;
        }else {
            return;
        }
    }
}

main的方法:

public class Demo {
    public static void main(String[] args) {
        RunnableImp ri = new RunnableImp();

        Thread t0 = new Thread(ri);
        Thread t1 = new Thread(ri);
        Thread t2 = new Thread(ri);

        //开启线程
        t0.start();
        t1.start();
        t2.start();
    }
}
普通同步方法原理:同步方法会把方法内部代码锁住,只让一个线程执行,锁对象就是new RunnableImp();也就是this
2.2.静态的同步方法:锁对象就不能是this了,this是拆功能键对象后产生的,静态方法优先于对象,锁对象应是本类的class文件

实现类代码:

public class RunnableImp implements Runnable {

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

        }
    }

    private static void sellTicketStatic() {
        synchronized (RunnableImp.class){   //锁对象是本类的.class文件
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + ":" + ticket);
                ticket--;
            }else {
                return;
            }
        }
    }
}

main方法:

public class Demo {
    public static void main(String[] args) {
        RunnableImp ri = new RunnableImp();

        Thread t0 = new Thread(ri);
        Thread t1 = new Thread(ri);
        Thread t2 = new Thread(ri);

        //开启线程
        t0.start();
        t1.start();
        t2.start();
    }
}

3.Lock锁:

使用步骤

/*
    * 使用步骤:
    * 1.在成员变量的位置创建一个ReentrantLock对象(这个对象是Lock接口的子类对象)、
    * 2.在可能出现安全问题的代码前调用Lock接口的方法lock获取锁
    * 3.在可能出现安全问题的代码后调用Lock接口的方法unlock获取锁
    * */

实现类代码:

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class RunnableImp implements Runnable {
    private int ticket = 100;
    //1.在成员变量的位置创建一个ReentrantLock对象(这个对象是Lock接口的子类对象)、
    Lock l = new ReentrantLock();
    @Override
    public void run() {
        while (true){
            //2.在可能出现安全问题的代码前调用Lock接口的方法lock获取锁
            l.lock();      //获取锁
            if (ticket > 0){
                System.out.println(Thread.currentThread().getName() + ":" + ticket);
                ticket--;
            }else {
                return;
            }
            //3.在可能出现安全问题的代码后调用Lock接口的方法unlock获取锁
            l.unlock();  //释放锁

        }
    }
}

main方法代码:

public class Demo {
    public static void main(String[] args) {
        RunnableImp ri = new RunnableImp();

        Thread t0 = new Thread(ri);
        Thread t1 = new Thread(ri);
        Thread t2 = new Thread(ri);

        //开启线程
        t0.start();
        t1.start();
        t2.start();
    }
}

你可能感兴趣的:(JAVA---解决线程安全问题的三种方式(同步代码块,同步方法,锁))