java线程同步问题

线程同步问题

一.引言

首先一点,我们要明确的是线程同步操作,是为了解决共享资源的冲突问题,保证数据的统一性。

举个例子:

	对于某一个数组资源:
		用户A想要读取其中的下标为0的数据,同时,
		用户B想要修改其中的下标为0的数据.

那么最后的结果很难说清楚。。。

这时候就要用到我们的线程同步操作了。

二、案例带入

​ 首先,我们来看这样一个售票案例:

例如马上要上映的新海诚新电影《铃芽户缔》要开始售票了。

​ A、B、C三个平台总共接到售票100张的任务,

​ 这一百张票是三个平台一共要售出的票数,在此可以看成是共享资源。

那么我们首先来看一个如果没加上线程同步,售票结果会是怎么样,代码如下:

线程任务部分:

public class MyThread implements Runnable {
    //定义总票数100,为共享资源
    private static int ticket = 100;

    @Override
    public void run() {
        Thread thread = Thread.currentThread();
        while (true) {
            if (ticket > 0) {
                System.out.println(thread.getName() + "售出票号为" + ticket + "的票");
                ticket -= 1;
            } else {
                //售罄,跳出循环
                System.out.println(thread.getName() + "售罄");
                break;
            }
        }
    }
}

测试:

public class Demo1 {
    public static void main(String[] args) {
        Thread t1 = new Thread(new MyThread(), "A");
        Thread t2 = new Thread(new MyThread(), "B");
        Thread t3 = new Thread(new MyThread(), "C");

        t1.start();
        t2.start();
        t3.start();
    }
}

测试结果:

B售出票号为100的票
B售出票号为99的票
B售出票号为98的票
B售出票号为97的票
B售出票号为96的票
B售出票号为95的票
C售出票号为100的票
C售出票号为93的票
C售出票号为92的票
A售出票号为100的票
A售出票号为90的票
C售出票号为91的票
B售出票号为94的票

可以看出,三个线程之间对资源的抢占,让电影票的售票及其不合理,这就需要线程同步了。

三、线程同步
1.锁对象和synchronized

​ 线程同步需要用到锁对象和synchronized的关键字,顾名思义,我们可以通过锁对象给某段代码上锁,保证每次访问这段代码的线程对象只有一个,这样就保证了数据的统一性。synchronized关键字是用于同步的关键字。

2.锁对象的设计

1.首先需要保证锁对象的唯一性

​ 锁唯一可以保证大家都用这一把锁,保证数据的统一性。
2.通常一个类内会设计一个独立的变量作为锁对象的使用

3.所有的包装类不可以用于锁对象的使用。

4.当前类的class对象也可以用于锁对象,但是这里不太推荐,因为这个锁的范围太大了,具体原因接下来我会讲到。

3.同步代码块

具体格式

synchronized (锁对象) {
    //同步的代码(原子性)
}

​ 当线程执行到同步代码块的时候,此段代码会自动落锁,执行完毕会自动开锁,用此种方法限制了此段的代码的访问对象同一时间有且只有一个。

案例代码


class SaleThread implements Runnable {

    //只有一份,用静态来表示票数
    private static Integer ticket = 100;

    //创建锁对象
    private static final Object LOCK = new Object();

    @Override
    public void run() {
        Thread thread = Thread.currentThread();

        while (true) {
            //同步代码块,锁最小操作
            synchronized (LOCK) {
                if (ticket > 0) {
                    System.out.println(thread.getName() + "售出了" + ticket + "号对应的票");
                    ticket -= 1;
                } else {
                    System.out.println(thread.getName() + "售空");
                    break;
                }
            }
        }
    }
}

/**
 * @author: renlon 2023/03/08 10:28
 */
public class Demo1 {
    public static void main(String[] args) {
        //创建线程对象
        Thread t1 = new Thread(new SaleThread(), "A");
        Thread t2 = new Thread(new SaleThread(), "B");
        Thread t3 = new Thread(new SaleThread(), "C");

        //设置优先级
        t1.setPriority(Thread.MIN_PRIORITY);

        t1.start();
        t2.start();
        t3.start();
    }
}

执行结果展示(为了方便展示,修改票数为10张)

A售出了10号对应的票
C售出了9号对应的票
C售出了8号对应的票
C售出了7号对应的票
C售出了6号对应的票
C售出了5号对应的票
C售出了4号对应的票
C售出了3号对应的票
C售出了2号对应的票
C售出了1号对应的票
C售空
A售空
B售空
4.同步方法

​ 同步方法分为静态修饰的和非静态的,都使用 synchronized 关键字修饰,他们的不同点是约束的范围不同,下面我将通过实例来介绍他们的不同。

1.非静态的同步方法

​ java代码中有很多类都使用的非静态的同步方法,例如:

ArrayList 和 Vector Vector使用非静态的方法修饰,线程安全
StringBuilder 和 StringBuffer StringBuffer使用非静态的方法修饰,线程安全
HashMap 和 HashTable HashTable使用非静态的方法修饰,线程安全

非静态同步方法的基本格式

public synchronized 返回值 方法名(参数列表) {
   同步代码块;
}

代码演示:

class MyThread {
    public synchronized void method1() {
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("方法1");
        }
    }

    public synchronized void method2() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        for (int i = 0; i < 10; i++) {
            System.out.println("方法2");
        }
    }

}

/**
 * @author: renlon 2023/03/10 23:40
 */
public class Demo2 {
    public static void main(String[] args) {

        MyThread t1 = new MyThread();
        MyThread t2 = new MyThread();

        //同一个实例对象抢占执行一个类的不同非静态同步方法
        new Thread(() -> t1.method1()).start();		
//        new Thread(() -> t1.method1()).start();

        new Thread(() -> t2.method2()).start();
    }
}

情况一:

​ 当执行t1.method1()和t1.method2()时,线程对象是同一个对象。执行不同的非静态同步方法,method1()中加入了休眠,增加了任务的执行时间长度。可以很清楚地看到,当t1执行MyThread类的其中一个非静态的同步方法时,另一个同步方法则无法执行。

情况二:

​ 当执行t1.method1()和t2.method2()的时候,两个线程不会互相影响,会出现抢占执行的情况

​ 非静态的同步方法是以当前实例化对象为锁对象 ,实例化对象调用任意的 synchronized 修饰的非静态同步方法,则所有其他 synchronized 同步方法无法执行。

2.静态同步方法

基本格式:

public static 返回值 方法名(参数列表) {
    //同步操作
}

代码演示:

class A {
    public static synchronized void test1() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程1");
        }
    }

    public static synchronized void test2() {
        for (int i = 0; i < 10; i++) {
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("线程2");
        }
    }
}

/**
 * @author: renlon 2023/03/11 16:02
 */
public class Demo3 {
    public static void main(String[] args) {

        //执行同步任务
        new Thread(() -> A.test1()).start();
        new Thread(() -> A.test2()).start();
        new Thread(() -> A.test1()).start();
    }
}

分析:

​ 结果显示三个任务不管怎样都是顺序执行完毕的,任务结果是当一个线程开始执行,其他两个线程任务必须等待当前线程任务执行完毕才可以继续执行。

5.简单总结

static 修饰的 synchronized同步方法,锁对象是当前的 类名.class 。任意线程或对象执行此类的 static 修饰的 synchronized同步方法,则此类其他的这种同步方法都会被锁住,其他任意对象,任意线程都无法执行。

举一个简单上公共厕所的例子:
	非静态的同步方法就好比你上公共厕所,锁住了一个坑位,而且同时你这个对象,同一时间内只可以锁一个厕所坑位,
	静态的同步方法就是你上一个公共厕所,直接把整个厕所的大门锁上了,任何人在你出来之前都无法再进入这个厕所。
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值