静态同步方法与解决线程安全问题_Lock锁
静态同步方法
Java提供了synchronized关键字用于修饰方法,使用synchronized修饰的方法被称为同步方法。当然,synchronized关键字除了修饰方法之外,还可以修饰普通代码块,使用synchronized修饰的代码块被称为同步代码块。
Java语法规定,任何线程进入同步方法、同步代码块之前,必须先获取同步方法、同步代码块对应的同步监视器。
对于同步代码块而言,程序必须为它显示的指定同步监视器(可为this也可以自定义Object类型的全局变量);对于同步非静态方法而言,该方法的同步
监视器是this----即调用该方法的java对象;对于静态的同步方法而言,该方法的同步监视器不是this而是该类本身。
下面程序提供了一个静态的同步方法及一个同步代码块。同步代码块使用this作为同步监视器,即这两个同步程序单元并没有使用相同的同步监视器,因此它们可以同时并发执行,相互之间不会有任何影响。
上面程序中定义了一个SynchronizedStatic类,该类实现了Runnable接口,因此可作为线程的target来运行.SynchronizedStatic类通过一个staticFlag旗标控制线程使用哪个方法作为线程执行体:
当staticFlag 为真时,程序使用test0()方法作为线程执行体;
当staticFlag 为假时,程序使用test1)方法作为线程执行体。
程序第一次执行SynchronizedStatic对象作为target 的线程时,staticFlag 初始值为true,因此程序将以testo()方法作为线程执行体,而且程序将会把staticFlag修改为false;这使得第二次执行SynchronizedStatic对象作为target 的线程时,程序将以test1()方法作为线程执行体。
程序主方法以SynchronizeciStatic对象作为target启动了2条线程,一条将以testO()方法作为线程执行体,另外一条将以test1()方法作为线程执行体。.
显然是并发执行,并没有达到同步执行(顺序执行)的效果。
静态同步方法可以和以this为同步监视器的同步代码块同时执行,当第一条线程(以test0()方法作为线程执行体的线程)进入同步代码块执行以后,该线程获得了对同步监视器(SynchronizedStatic类)的锁定,第二条线程(以test1()方法作为线程执行体的线程)尝试进入同步代码块执行,进入同步代码块之前,该线程必须获得对this引用(也就是ss变量所引用的对象)的锁定。因为第一条线程锁定的是SynchronizedStatic类,而不是ss变量所引用的对象,所以第二条线程完全可以获得对ss变量所引用的对象的锁定,因此系统可以切换到执行第二条线程
为了更好地证明静态的同步方法的同步监视器是当前类,可以将上面程序中同步代码块的同步监视器改为SynchronizedStatic类。也就是将上面test1()方法定义改为如下形式。
package Synchronized_demo; public class SynchronizedStatic implements Runnable { static boolean staticFlag = true; public static synchronized void test0(){ for(int i=0;i<5;i++){ System.out.println("test0:"+Thread.currentThread().getName() + " "+ i); } } public void test1(){ synchronized (SynchronizedStatic.class) { for(int i=0;i<5;i++){ System.out.println("test1:"+Thread.currentThread().getName() + " "+ i); } } } public void run() { if(staticFlag){ staticFlag = false; test0(); }else{ staticFlag = true; test1(); } } public static void main(String[] args) throws InterruptedException { SynchronizedStatic ss = new SynchronizedStatic(); new Thread(ss).start(); //保证第一条线程开始运行 // Thread.sleep(1); new Thread(ss).start(); } }
解决线程安全问题_Lock锁
多线程 首先自问什么是线程
自答 线程就是应用程序启动时候在cpu执行的时候的一个桥梁路径 称之为线程
再问 怎么创建线程
自答 两种方式 一种继承Thread 一种实现Runnable接口
还问 怎么启动线程
针对继承Thread的方式 可以直接Thread t = new Thread(),重写run方法 然后t.start方法启动
再对实现Runnable接口 可以多态Runnable run = new RunnableImpl(),然后在实现
Thread t = new Thread (run),再次t.start 启动 第二种看似多出一步,但是因为是实现接口 所以扩展性 灵活性都要优于继承。
明天再问 准备回家
接着问
线程安全有几种方式?
1.synchronized代码块
2. synchronized方法 静态synchronized方法
3.使用Lock锁
package Synchronized_demo; /** * @author admin * @version 1.0.0 * @ClassName RunnableImpl.java * @Description TODO * @createTime 2021年09月28日 18:47:00 */ import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; /** * 卖票案例出现了线程安全问题 * 卖出了不存在的票和重复的票 * * 解决线程安全问题的三种方案:使用lock锁 * java.util.concurrent.locks.Lock接口 * * Lock实现提供了比使用synchronized方法和语句获得更广泛的锁定操作 * Lock接口中的方法 * void Lock()获取锁 * void unlock() 释放锁 * java.util.concurrent.Locks.ReentrantLock implements Lock接口 * * 使用步骤 * 1.在成员位置创建一个ReentrantLock对象 * 2.在有可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁 * 3.有可能会出现安全问题的代码后调用Lock接口中的方法unlock关闭所 * * * */ public class RunnableImpl implements Runnable { // 定义一个多线程共享资源 private int ticket = 100; // 1.在成员位置创建一个ReentrantLock对象 Lock l = new ReentrantLock(); // 设置线程任务 卖票 @Override public void run() { // 使用死循环 让卖票操作重复操作 while (true) { // 2.在有可能会出现安全问题的代码前调用Lock接口中的方法Lock获取锁 l.lock(); // 先判断票是否存在 if (ticket > 0) { // 提高安全问题出现的概率,让程序睡眠 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在卖第" + ticket + "张票"); ticket--; } l.unlock(); } } }
package cn.itcast.day12.demo09; /** * @author admin * @version 1.0.0 * @ClassName Demo01Ticket.java * @Description TODO * @createTime 2021年09月28日 14:23:00 */ /** * 模拟卖票案例 * 创建3个线程,同时开启,对共享的票进行出售 * */ public class Demo01Ticket { public static void main(String[] args) { // 创建Runnable接口的实现类 Runnable run = new RunnableImpl(); // 创建Thread类对象 构造方法中传递Runnable接口的实现类对象 Thread t = new Thread(run); Thread t1 = new Thread(run); Thread t2 = new Thread(run); // 调用start方法开启多线程 t.start(); t1.start(); t2.start(); } }