标签:ble 进入 ack 电影院 get 实现 互斥 main 技术
如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。
我们通过一个案例,演示线程的安全问题:
电影院要卖票,我们模拟电影院的卖票过程。假设要播放的电影是 “葫芦娃大战奥特曼”,本次电影的座位共100个(本场电影只能卖100张票)。
我们来模拟电影院的售票窗口,实现多个窗口同时卖 “葫芦娃大战奥特曼”这场电影票(多个窗口一起卖这100张票)需要窗口,采用线程对象来模拟;需要票,Runnable接口子类来模拟
多线程访问了共享的数据,会产生线程安全问题。
代码示例:
RunnableImpl.java:
1 /* 2 实现卖票案例 3 */ 4 public class RunnableImpl implements Runnable{ 5 //定义一个多个线程共享的票源 6 private int ticket = 100; 7 8 9 //设置线程任务:卖票 10 @Override 11 public void run() { 12 //使用死循环,让卖票操作重复执行 13 while(true){ 14 //先判断票是否存在 15 if(ticket>0){ 16 //提高安全问题出现的概率,让程序睡眠 17 try { 18 Thread.sleep(10); 19 } catch (InterruptedException e) { 20 e.printStackTrace(); 21 } 22 23 //票存在,卖票 ticket-- 24 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 25 ticket--; 26 } 27 } 28 } 29 }
Demo01Ticket.java(非线程安全):
1 /* 2 模拟卖票案例 3 创建3个线程,同时开启,对共享的票进行出售 4 */ 5 public class Demo01Ticket { 6 public static void main(String[] args) { 7 //创建Runnable接口的实现类对象 8 RunnableImpl run = new RunnableImpl(); 9 //创建Thread类对象,构造方法中传递Runnable接口的实现类对象 10 Thread t0 = new Thread(run); 11 Thread t1 = new Thread(run); 12 Thread t2 = new Thread(run); 13 //调用start方法开启多线程 14 t0.start(); 15 t1.start(); 16 t2.start(); 17 } 18 }
线程安全问题的概述
线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
当我们使用多个线程访问同一资源的时候,且多个线程中对资源有写的操作,就容易出现线程安全问题。
要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票问题,Java中提供了同步机制(synchronized)来解决。
根据案例简述:
窗口1线程进入操作的时候,窗口2和窗口3线程只能在外等着,窗口1操作结束,窗口1和窗口2和窗口3才有机会进入代码去执行。也就是说在某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕同步之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。
为了保证每个线程都能正常执行原子操作,Java引入了线程同步机制。
那么怎么去使用呢?有三种方式完成同步操作:
1. 同步代码块。
2. 同步方法。
3. 锁机制。
格式:
synchronized(同步锁){ 需要同步操作的代码 }
同步锁:
对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁.
1. 锁对象 可以是任意类型。
2. 多个线程对象 要使用同一把锁。
注意:在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着(BLOCKED)。
代码示例:
RunnableImpl.java:
1 /* 2 卖票案例出现了线程安全问题 3 卖出了不存在的票和重复的票 4 5 解决线程安全问题的一种方案:使用同步代码块 6 格式: 7 synchronized(锁对象){ 8 可能会出现线程安全问题的代码(访问了共享数据的代码) 9 } 10 11 注意: 12 1.同步代码块中的锁对象,可以使用任意的对象 13 2.但是必须保证多个线程使用的锁对象是同一个 14 3.锁对象作用: 15 把同步代码块锁住,只让一个线程在同步代码块中执行 16 */ 17 public class RunnableImpl implements Runnable{ 18 //定义一个多个线程共享的票源 19 private int ticket = 100; 20 21 //创建一个锁对象 22 Object obj = new Object(); 23 24 //设置线程任务:卖票 25 @Override 26 public void run() { 27 //使用死循环,让卖票操作重复执行 28 while(true){ 29 //同步代码块 30 synchronized (obj){ 31 //先判断票是否存在 32 if(ticket>0){ 33 //提高安全问题出现的概率,让程序睡眠 34 try { 35 Thread.sleep(10); 36 } catch (InterruptedException e) { 37 e.printStackTrace(); 38 } 39 40 //票存在,卖票 ticket-- 41 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 42 ticket--; 43 } 44 } 45 } 46 } 47 }
Demo01Ticket.java:
1 /* 2 模拟卖票案例 3 创建3个线程,同时开启,对共享的票进行出售 4 */ 5 public class Demo01Ticket { 6 public static void main(String[] args) { 7 //创建Runnable接口的实现类对象 8 RunnableImpl run = new RunnableImpl(); 9 //创建Thread类对象,构造方法中传递Runnable接口的实现类对象 10 Thread t0 = new Thread(run); 11 Thread t1 = new Thread(run); 12 Thread t2 = new Thread(run); 13 //调用start方法开启多线程 14 t0.start(); 15 t1.start(); 16 t2.start(); 17 } 18 }
同步的原理:
格式:
public synchronized void method(){ 可能会产生线程安全问题的代码 }
同步锁是谁?
对于非static方法,同步锁就是this。
对于static方法,我们使用当前方法所在类的字节码对象(类名.class)。
代码示例:
RunnableImpl.java:
1 /* 2 卖票案例出现了线程安全问题 3 卖出了不存在的票和重复的票 4 5 解决线程安全问题的二种方案:使用同步方法 6 使用步骤: 7 1.把访问了共享数据的代码抽取出来,放到一个方法中 8 2.在方法上添加synchronized修饰符 9 10 格式:定义方法的格式 11 修饰符 synchronized 返回值类型 方法名(参数列表){ 12 可能会出现线程安全问题的代码(访问了共享数据的代码) 13 } 14 */ 15 public class RunnableImpl implements Runnable{ 16 //定义一个多个线程共享的票源 17 private static int ticket = 100; 18 19 20 //设置线程任务:卖票 21 @Override 22 public void run() { 23 System.out.println("this:"+this);//this:com.itheima.demo08.Synchronized.RunnableImpl@58ceff1 24 //使用死循环,让卖票操作重复执行 25 while(true){ 26 payTicketStatic(); 27 } 28 } 29 30 /* 31 静态的同步方法 32 锁对象是谁? 33 不能是this 34 this是创建对象之后产生的,静态方法优先于对象 35 静态方法的锁对象是本类的class属性-->class文件对象(反射) 36 */ 37 public static /*synchronized*/ void payTicketStatic(){ 38 synchronized (RunnableImpl.class){ 39 //先判断票是否存在 40 if(ticket>0){ 41 //提高安全问题出现的概率,让程序睡眠 42 try { 43 Thread.sleep(10); 44 } catch (InterruptedException e) { 45 e.printStackTrace(); 46 } 47 48 //票存在,卖票 ticket-- 49 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 50 ticket--; 51 } 52 } 53 54 } 55 56 /* 57 定义一个同步方法 58 同步方法也会把方法内部的代码锁住 59 只让一个线程执行 60 同步方法的锁对象是谁? 61 就是实现类对象 new RunnableImpl() 62 也是就是this 63 */ 64 public /*synchronized*/ void payTicket(){ 65 synchronized (this){ 66 //先判断票是否存在 67 if(ticket>0){ 68 //提高安全问题出现的概率,让程序睡眠 69 try { 70 Thread.sleep(10); 71 } catch (InterruptedException e) { 72 e.printStackTrace(); 73 } 74 75 //票存在,卖票 ticket-- 76 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 77 ticket--; 78 } 79 } 80 81 } 82 }
java.util.concurrent.locks.Lock 机制提供了比synchronized代码块和synchronized方法更广泛的锁定操作,
同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。
Lock锁也称同步锁,加锁与释放锁方法化了,如下:
解决线程安全问题的三种方案:使用Lock锁
java.util.concurrent.locks.Lock接口
Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。
Lock接口中的方法:
java.util.concurrent.locks.ReentrantLock implements Lock接口
RunnableImpl.java:
1 /* 2 卖票案例出现了线程安全问题 3 卖出了不存在的票和重复的票 4 5 解决线程安全问题的三种方案:使用Lock锁 6 java.util.concurrent.locks.Lock接口 7 Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。 8 Lock接口中的方法: 9 void lock()获取锁。 10 void unlock() 释放锁。 11 java.util.concurrent.locks.ReentrantLock implements Lock接口 12 13 14 使用步骤: 15 1.在成员位置创建一个ReentrantLock对象 16 2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁 17 3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁 18 */ 19 public class RunnableImpl implements Runnable{ 20 //定义一个多个线程共享的票源 21 private int ticket = 100; 22 23 //1.在成员位置创建一个ReentrantLock对象 24 Lock l = new ReentrantLock(); 25 26 //设置线程任务:卖票 27 @Override 28 public void run() { 29 //使用死循环,让卖票操作重复执行 30 while(true){ 31 //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁 32 l.lock(); 33 34 //先判断票是否存在 35 if(ticket>0){ 36 //提高安全问题出现的概率,让程序睡眠 37 try { 38 Thread.sleep(10); 39 //票存在,卖票 ticket-- 40 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 41 ticket--; 42 } catch (InterruptedException e) { 43 e.printStackTrace(); 44 }finally { 45 //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁 46 l.unlock();//无论程序是否异常,都会把锁释放 47 } 48 } 49 } 50 } 51 52 /*//设置线程任务:卖票 53 @Override 54 public void run() { 55 //使用死循环,让卖票操作重复执行 56 while(true){ 57 //2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁 58 l.lock(); 59 60 //先判断票是否存在 61 if(ticket>0){ 62 //提高安全问题出现的概率,让程序睡眠 63 try { 64 Thread.sleep(10); 65 } catch (InterruptedException e) { 66 e.printStackTrace(); 67 } 68 69 //票存在,卖票 ticket-- 70 System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票"); 71 ticket--; 72 } 73 74 //3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁 75 l.unlock(); 76 } 77 }*/ 78 }
标签:ble 进入 ack 电影院 get 实现 互斥 main 技术
原文地址:https://www.cnblogs.com/116970u/p/11487448.html