[基础/java]synchronized的基本思想

前言

一个进程内可以存在多个线程,一个线程也可以存在多个协程。
在一般的开发环境中,会经常利用到线程

线程的创建方式

  1. extends Thread
    直接继承Thread类,重写run()方法
  2. implements Runnable
    最常用的创建方式,实现Runnable接口,重写run()方法
  3. implements Callable<T>
    有返回值,重写run()方法

推荐使用implements的方法创建线程

synchronized

使用场景

  • 直接在创建的线程类中加关键字synchronized
  • 新建一个公有类,可作为一项服务,多个线程对该服务进行访问。在这个公有类中加关键字synchronized

在何处加

我们在这里定义一个场景:一个线程可以不停的抢票,直到票数为0

1. 不加synchronized

class MyThread implements Runnable{
	//定义100张票
    private int tickets = 100;
    //打印抢票信息
    public void print(){
        System.out.println(Thread.currentThread().getName()+"抢到了第"+this.tickets+"张票");
    }

    @Override
    public /*synchronized*/ void run() {
    	//这里的run方法未添加synchronized
        while(this.tickets > 0){
            this.print();
            this.tickets--;
        }
    }
}

public class RunnableDemo {

    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        new Thread(myThread1,"A").start();
        new Thread(myThread1,"B").start();
        new Thread(myThread1,"C").start();
        new Thread(myThread1,"D").start();
        new Thread(myThread1,"E").start();
    }
}

部分运行结果
不使用synchronized
不使用synchronized同步,会出现数据的脏读

2. 修饰方法


class MyThread implements Runnable{

    private int tickets = 100;
    public void print(){
        System.out.println(Thread.currentThread().getName()+"抢到了第"+this.tickets+"张票");
    }

    @Override
    public synchronized void run() {
    	//用synchronized修饰
        while(this.tickets > 0){
            this.print();
            this.tickets--;
        }
    }
}

public class RunnableDemo {
    public static void main(String[] args) {
        MyThread myThread1 = new MyThread();
        new Thread(myThread1,"A").start();
        new Thread(myThread1,"B").start();
        new Thread(myThread1,"C").start();
        new Thread(myThread1,"D").start();
        new Thread(myThread1,"E").start();
    }
}

部分运行结果
使用synchronized修饰方法
使用synchronized同步,数据按照我们所想的方向运行

3. 修饰this

在此部分,只改动run方法的代码

	@Override
    public void run() {
        synchronized (this){
            while(this.tickets > 0){
                this.print();
                this.tickets--;
            }
        }
    }

输出结果和2部分相同,仍是同步的

this 表示的是当前对象的引用,当前对象是谁?线程1、2都实现了同一个方法,自然他们的对象是相同的,this指向的对象都是同一个对象,自然会线程安全、同步输出。


那如果我加一行代码呢?
	@Override
    public void run() {
        System.out.println(Thread.currentThread().getName()+"是非synchronized修饰的方法内的输出");
        synchronized (this){
            while(this.tickets > 0){
                this.print();
                this.tickets--;
            }
        }
    }

看一下输出结果:

同步+异步执行

在《Java多线程编程核心技术》一书中,有这样一段话: “不在synchronized块中就是异步执行,在synchronized块中就会死同步执行”
上面的例子中,run()方法分为两部分,一部分是普通的输出print,另一部分是synchronized修饰的部分。
普通的输出print部分:异步执行。
synchronized修饰部分:同步执行

【抽象说】

线程A:我们start()了!!!冲!冲!冲!!!!!!!!!!
(众人):冲!!!!run()!run()!run()!
(众人):我输出了!我输出了!我输出了!我输出了!我输出了!...
(由于线程B身强体壮,起跑快,冲到了第一个)
线程B:耽误太多时间,事情可就做不完了。抱歉了,兄弟们,先走一步!(说完便拿起锁)
(此时的synchronized块,被线程B私有)
(众人):卧槽!别啊!给我们锁啊!别让我们阻塞!
线程B:哇哦!AWESOME MAN!!鹅鹅鹅!这是我独享的MOMENT!!!!!
(线程B抢完了所有的票)

4. 方法内修饰变量

synchronized() 修饰的应是Object类
此时我们换一个稍微复杂点的场景

有两个人前来买瓜,有个人卖瓜 ; 瓜摊上有两个瓜:一个10斤、一个15斤 ; 单价2块钱
买瓜的:我 和 华强
我买10斤的,华强买15斤的
我花20块钱,华强花30块钱
  1. 变量定义在类中 (类中的全局变量)

class Melon{
    private final int singlePrice = 2;												//单价
    private int weight;																//重量

    
    String words = Thread.currentThread().getName()+"说:挑个瓜,告诉我多少钱";

    public void setWeight(int weight) {

        try{
            synchronized (words){
                System.out.println(Thread.currentThread().getName()+"于"+System.currentTimeMillis()+"来了!"+words);
                this.weight = weight ;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"于"+System.currentTimeMillis()+ "买到了瓜,价格为"+this.weight * this.singlePrice);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }
}

//我的线程
class Me extends Thread{
    Melon melon;
    Me(Melon melon){
        super();
        this.melon = melon;
    }

    @Override
    public void run() {
        melon.setWeight(10);
    }
}

//华强的线程
class HuaQiang extends Thread{
    Melon melon;
    HuaQiang(Melon melon){
        super();
        this.melon = melon;
    }
    @Override
    public void run() {
        melon.setWeight(15);
    }
}

public class RunnableDemo {

    public static void main(String[] args) {
        Melon melon = new Melon();
        Thread me = new Me(melon);
        Thread huaqiang = new HuaQiang(melon);
        
        me.setName("我");
        huaqiang.setName("华强");
        
        me.start();
        huaqiang.start();
    }
}

输出结果
变量定义在方法外
可以看到时同步输出,排队

  1. 变量定义在方法中(方法内的局部变量)

将words变量放在方法内定义

public void setWeight(int weight) {
        String words = Thread.currentThread().getName()+"说:挑个瓜,告诉我多少钱";
        try{
            synchronized (words){
                System.out.println(Thread.currentThread().getName()+"于"+System.currentTimeMillis()+"来了!"+words);
                this.weight = weight ;
                Thread.sleep(1000);
                System.out.println(Thread.currentThread().getName() +"于"+System.currentTimeMillis()+ "买到了瓜,价格为"+this.weight * this.singlePrice);
            }
        }catch (InterruptedException e){
            e.printStackTrace();
        }
    }

变量定义在方法内
可以看到,此时两个线程同时到达函数体内部,是异步运行,而且注意到线程并不安全

【抽象说】

我和华强同时到了,准备了不同的话(String中的内容不同)
我抢先一句:老板,来个瓜
华强几乎同时说:给我挑一个
老板懵了,看到我们是两句话,使用了影分身。
老板1号给我的瓜称重,10斤,20块。
老板2号给华强的瓜称重,15斤,30块。(稍慢了0.00000001纳秒
⚠️此时秤砣是一个,30块覆盖了20块。
老板1号对我说:30块。
老板2号对华强说:30块。
我:???

5. synchronized修饰静态

无论是静态方法还是静态变量,在类的初始化中都会率先执行;同时也侧面说明了,静态方法/静态变量是和绑定的。
同时也可以推断出,synchronized修饰静态,也同时将class类修饰。
class类是单例的,当用synchronized修饰静态时,针对相同类、不同对象、不同方法,仍会同步运行。

总结

1. 只要修饰方法,就会锁类,同步运行

2. synchronized(x) 中
如果线程们访问的x是同一个,或者说x是同一个地址,则是同步,排队运行,保证线程安全;
如果线程们访问的x不是同一个,或者说x是不同的地址,则是异步,同时运行,不能保证线程安全。

3. 用static synchronized修饰时,将class类对象作为锁

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值