先看看这个卖票实例:
- package com.wangxing.test6;
- public class MyThread1 implements Runnable{
- private int piao=5;
- @Override
- public void run() {
- //得到线程名称
- String name=Thread.currentThread().getName();
- //持续买票
- boolean flag=true;
- while(flag){
- //判断有没有可卖的票
- if(piao>0){
- //收钱--找钱--打印票
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");
- }else{
- flag=false;
- }
- }
- }
- }
-
- package com.wangxing.test6;
- public class Main1 {
- public static void main(String[] args) {
- MyThread1 my=new MyThread1();
- Thread th1=new Thread(my);
- Thread th2=new Thread(my);
- Thread th3=new Thread(my);
- th1.setName("窗口1");
- th2.setName("窗口2");
- th3.setName("窗口3");
- th1.start();
- th2.start();
- th3.start();
- }
- }
-
- 运行结果:
- 窗口3,卖出1张票,还剩3张
- 窗口1,卖出1张票,还剩4张
- 窗口2,卖出1张票,还剩2张
- 窗口2,卖出1张票,还剩1张
- 窗口1,卖出1张票,还剩0张
- 窗口3,卖出1张票,还剩-1张
分析结果:当窗口1卖最后一张票的时候,在收钱打印票的时候,还没有来得及对票数进行减1之前,线程就切换给了窗口3,窗口3认为还有一张票,窗口3就收钱收钱打印票的时候,还没有来得及对票数进行减1之前,线程有切换给了窗口1,窗口1就对票数进减1,完成以后线程切换给窗口3,窗口3对对票数进行减1此时就得到-1这个值。
经过上面运行程序的分析,我得到的结果是:当多条线程,同时访问同一个资源的时候,会产生数据不一致的错误情况。
为了解决这种数据不一致的错误情况,我们才学习线程同步。
什么是线程同步/线程安全?
线程同步也叫线程安全,当多条线程,同时访问同一个资源的时候,每一次只能由多条线程中的其中一条访问公共资源,当这一条线程访问公共资源的时候,其他的线程都处于等待状态,不能访问公共资源,当这一条线程访问完了公共资源以后,其他线程中的一条线程才能访问资源,剩下的线程继续等待,等待当前线程访问结束,实现这个过程就是线程同步。【排队访问资源】
线程同步/线程安全的实现方式
1.Synchronized关键字 【同步代码块/同步方法】
1.1.同步代码块
格式:synchronized(同步对象){
}
- package com.wangxing.test6;
- public class MyThread2 implements Runnable {
- private int piao = 5;
- @Override
- public void run() {
- // 得到线程名称
- String name = Thread.currentThread().getName();
- // 持续买票
- boolean flag = true;
- while (flag) {
- // 同步代码块
- synchronized (this) {
- // 判断有没有可卖的票
- if (piao > 0) {
- // 收钱--找钱--打印票
- try {
- Thread.sleep(2000);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(name + ",卖出1张票,还剩" + (--piao) + "张");
- } else {
- flag = false;
- }
- }
- }
- }
- }
同步代码块虽然可以实现买票的效果,但是它在使用的时候,需要设置一个同步对象,由于我们很多时候都不知道这个同步对象应该是谁,容易写错,造成死锁的情况。正是应为这个缺点,我们很少使用同步代码块来实现线程同步。
1.2.同步方法
同步方法的定义格式: 访问限制修饰符 synchronized 方法返回值类型 方法名称(){}
- package com.wangxing.test6;
-
- public class MyThread3 implements Runnable{
- private int piao=5;
- //持续买票
- boolean flag=true;
- @Override
- public void run() {
- //得到线程名称
- String name=Thread.currentThread().getName();
- while(flag){
- sellpiao(name);
- }
- }
- //同步方法
- public synchronized void sellpiao(String name){
- //判断有没有可卖的票
- if(piao>0){
- //收钱--找钱--打印票
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");
- }else{
- flag=false;
- }
- }
- }
2.通过Lock接口
public interface Lock
常用的接口方法
void lock() 获得锁。
void unlock() 释放锁。
由于上面的锁方法是Lock接口,我们要使用就得先创建出Lock接口对象,由于Lock是个接口不能new ,我们就得使用它的子类来创建对象。
Lock接口得子类ReentrantLock
- package com.wangxing.test6;
- import java.util.concurrent.locks.Lock;
- import java.util.concurrent.locks.ReentrantLock;
-
- public class MyThread4 implements Runnable{
- private int piao=5;
- //定义Lock对象
- private Lock mylock=new ReentrantLock();
- @Override
- public void run() {
- //得到线程名称
- String name=Thread.currentThread().getName();
- //持续买票
- boolean flag=true;
- while(flag){
- //void lock() 获得锁。
- mylock.lock();
- //判断有没有可卖的票
- if(piao>0){
- //收钱--找钱--打印票
- try {
- Thread.sleep(500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println(name+",卖出1张票,还剩"+(--piao)+"张");
- }else{
- flag=false;
- }
- //void unlock() 释放锁。
- mylock.unlock();
- }
- }
- }
Synchronized关键字与Lock接口的区别?
synchronized:
1.synchronized关键字
2.自动锁定资源,不灵活
3.异常时会自动释放锁
4.不能中断锁,必须等待线程执行完成释放锁。
Lock:
1.Lock接口
2.手动锁定资源,灵活
3.异常时不会自动释放锁,所以需要在finally中实现释放锁
4.可以中断锁