Java(线程安全)

为什么要使用线程同步?

当使用多个线程访问同一资源的时候,且多个线程对资源有写的操作,就容易出现线程安全问题,在Java中提供了同步机制(synchronized)来解决

一、完成线程同步的三种方法

1、同步代码块
2、同步方法
3、锁机制

1、同步代码块

1.1概念

synchronized关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问;

1.2格式

synchronized(同步锁){
需要同步操作对的代码(访问了共享数据的代码)

}

注意:
    1、通过代码块中的锁对象,可以使用任意的对象
    2、但是必须保证多个线程使用的锁对象是同一个
    3、锁对象作用:
        把同步代码块锁住,只让一个线程在同步代码块中执行

1.3同步锁

对象的同步锁:
    锁对象,可以是任意类型
    多个线程的:使用同一把锁

注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其余线程在外等待;

1.4 代码实现

//创建Runnableimpl 实现类
public class Runnableimpl implements Runnable {
    //定义一个多个线程共享的票原
    private int ticker = 100;
    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务
    @Override
    public void run() {
        //死循环,卖票操作重复执行
        while (true) {
            //同步代码块
            synchronized (obj) {
                if (ticker > 0) {
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    //票存在,卖票
                    System.out.println(Thread.currentThread().getName()
                            + "->正在卖第" + ticker + "张票");
                    ticker--;
                }
            }
        }
    }
}
  /**
   * 模拟卖票案例
   * 创建三个线程,同时开启,对共享的票进行出售
   * <p>
   * 卖票出现了线程安全问题,
   * 卖出了不存在和重复的票
   * <p>
   * 解决方案一:使用同步代码块
   */
  public class Test {
      public static void main(String[] args) {
          //创建Runnable接口的实现类对象
          Runnableimpl run = new Runnableimpl();
          //创建Thread类对象,构造方法中传递Runnable接口的实现类对
          Thread t0 = new Thread(run);
          Thread t1 = new Thread(run);
          Thread t2 = new Thread(run);
          //调用start方法 。开启多线程
          t0.start();
          t1.start();
          t2.start();
     }
}

1.5同步技术的原理

  使用了一个锁对象,这个锁对象叫做对象锁,也叫对象监视器

  3个线程一起抢夺cpu的执行权,谁抢到了谁执行run方法,进行卖票
      t0抢到了cpu的执行权,执行run方法,遇到synchronized代码块
          这时t0会检查synchronized代码块是否有锁对象
          发现有,就会获取到锁对象,进入到同步代码块中执行

      t1抢到了cpu的执行权,执行run方法,遇到synchronized代码块
          t1检查查synchronized代码块是否有锁对象
          发现没有,t1就会进入到阻塞状态,会一直等待t0线程归还锁对象
          一直到t0线程执行完同步中的代码,会把锁对象归还给同步代码块
          t1才能获取锁对象进入到同步中执行

      总结:
      1、同步中的线程,没有执行完毕不会释放同步锁,同步外的线程没有锁进不去同步
      2、同步保证了只能有一个线程在同步中执行共享数据
      3、同步保证了安全
      4、程序频繁的判断锁,释放锁,程序的效率会降低

2、同步方法

2.1概念

使用synchronized修饰的方法,就叫同步方法,
保证A线程执行该方法的时候,其他线程只能在方法外等着

2.2格式

修饰符 synchronized 返回值类型 方法名(参数列表){
    可能会产生线程安全问题的代码
  }

2.3实现步骤

1、把访问了共享数据的代码抽取出来,方法一个方法中
2、在方法上添加synchronized修饰符

2.4代码实现

//创建实现Runnable接口的类
public class Runnableimpl implements Runnable {
    //定义一个多个线程共享的票原
    private int ticker = 100;
    //创建一个锁对象
    Object obj = new Object();

    //设置线程任务
    @Override
    public void run() {
        //死循环,卖票操作重复执行
        while (true) {
            payTicket();
        }
    }

    //定义一个同步方法
    //同步方法会把方法内部的代码锁住,只让一个线程执行
    //同步方法的锁对象是谁?是实现类对象 new Runnableimple() 也就是this
    /*
    public synchronized void  payTicket() {
        //先判断票是否存在
        if (ticker > 0) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //票存在,卖票
            System.out.println(Thread.currentThread().getName()
                    + "->正在卖第" + ticker + "张票");
            ticker--;
        }
    }
    */

    //或者
    public void payTicket() {
        synchronized (this) {
            if (ticker > 0) {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //票存在,卖票
                System.out.println(Thread.currentThread().getName()
                        + "->正在卖第" + ticker + "张票");
                ticker--;
            }
        }
    }
}

/*
 * 解决方案二、:使用同步方法
 */
public class Test {
    public static void main(String[] args) {
        //创建Runnable接口的实现类对象
        Runnableimpl run = new Runnableimpl();
        //创建Thread类对象,构造方法中传递Runnable接口的实现类对
        Thread t0 = new Thread(run);
        Thread t1 = new Thread(run);
        Thread t2 = new Thread(run);
        //调用start方法 。开启多线程
        t0.start();
        t1.start();
        t2.start();
    }
}

附:静态的同步方法

锁对象是谁?
不能是this,this是创建对象之后产生的,静态方法优先于对象
静态方法的锁对象是本类的class属性-->class文件对象(反射)
锁对象使用:Runnbaleimpl.class

3、Lock锁

3.1概念

java.util.concurrent.locks.lock接口
锁机制提供了比synchronized代码块和syschronized方法更广泛的锁定操作。

3.2常用方法

void  lock()获得锁。
void  unlock()释放锁。

3.3使用步骤

java.util.concurrent.locks.ReentrantLock implements lock接口
1、在成员位置创建一个ReentrantLock对象
2、在可能会出现安全问题的代码前调用lock接口中的方法lock获取锁
3、在可能会出现安全问题的代码后调用lock接口中的方法unlock释放锁

3.4实现代码

public class Runnableimpl implements Runnable {
    //定义一个多个线程共享的票原
    private int ticker = 100;

    //1\在成员位置创建一个Reentrantlock对象
    Lock l = new ReentrantLock();
    //设置线程任务
    @Override
    public void run() {
        //死循环,卖票操作重复执行
        while (true) {
            //2\在可能出现安全问题的代码前调用lock接口中的方法lock获取锁
            l.lock();
            if (ticker > 0) {
                try {
                    Thread.sleep(100);
                    //票存在,卖票
                    System.out.println(Thread.currentThread().getName()
                            + "->正在卖第" + ticker + "张票");
                    ticker--;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {//无论是否出现异常,都会释放锁
                    //3\在可能出现安全问题的代码后调用lock接口中的方法unlock释放锁
                    l.unlock();
                }
            }
        }
    }
}

  /*
  * 解决方案三、:使用Lock锁
   */
  public class Test {
      public static void main(String[] args) {
          //创建Runnable接口的实现类对象
          Runnableimpl run = new Runnableimpl();
          //创建Thread类对象,构造方法中传递Runnable接口的实现类对
          Thread t0 = new Thread(run);
          Thread t1 = new Thread(run);
          Thread t2 = new Thread(run);
          //调用start方法 。开启多线程
          t0.start();
          t1.start();
          t2.start();
      }
  }
posted @ 2021-03-09 23:07  一名初学者  阅读(66)  评论(0)    收藏  举报