WangJiQing

导航

synchronized介绍

synchronized

1、多线程之间容易出现线程安全问题

一个数由两个线程计算,一个线程加5000,另一个线程减5000,得出结果不为0

static int count = 0;
public static void main(String[] args) throws InterruptedException {
    Thread t1 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            count++;
        }
    }, "t1");

    Thread t2 = new Thread(() -> {
        for (int i = 0; i < 5000; i++) {
            count--;
        }
    }, "t2");

    t1.start();
    t2.start();
    t1.join();
    t2.join();

    System.out.println("结果:" + count);
}

    /**
     * 结果:-3826
     *
     * 进程已结束,退出代码为 0
     */

为什么要5000而不是+1、-1呢,我多次启动后发现每次结果都是0,怀疑是跟jvm热点数据有关,后续再来研究

1、原因分析

两个线程,线程1获取数据,加1操作;就在这时,加1操作完成之后,写入主内存之前,CPU时间片用完,发生上下文切换,线程2计算count,把结果写入主内存。线程1分配到CPU时间片,继续执行将count的值写入主内存,最后main线程得出来的结果不为0

2、临界区&临界资源

  • 临界区(Critical Section)

    • 一个程序运行多个线程本身是没有问题的
    • 问题出在多个线程访问共享资源
    • 多个线程共享资源其实也没有问题
    • 在多个线程对共享资源读写操作时发生指令交错,就会出现问题
    • 一段代码块内如果存在对共享资源的多线程读写操作,称这段代码块为临界区
    • 每个进程中访问临界资源的那段程序称为临界区

  • 临界资源

    • 一次仅允许一个进程使用的共享资源

例如上面线程t1、t2对对类变量count进行写操作,改变count的值的那段for循环就是临界区,count就是临界资源

3、竟态条件

多个线程再临界区执行,由于代码的执行序列不同而导致结果无法预测,称之为发生了竟态条件

4、解决

为了避免临界区的竟态条件发生,有多种手段可以达到目的

  • 阻塞式解决方案:synchronized,Lock
  • 非阻塞式解决方案:原子变量

synchronized:对象锁,采用互斥的方式保证同一时刻只有一个线程能够持有对象锁, 其它线程再想获取对象锁时就会被阻塞住,这样就能保证拥有对象锁的线程能够安全的执行临界区的代码,不用担心线程上下文切换

Java中synchronized可以保证互斥和同步

  • ​ 互斥:同一时刻只能有一个线程执行临界区代码
  • ​ 同步:由于线程执行的先后顺序不同,需要一个线程等待其它线程运行到某个点

语法:

synchronized(object){ //线程1获取到锁,线程2阻塞

}

1、解决开头问题

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (object){//线程1获取锁
                    count++;
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                synchronized (object){//线程2获取锁
                    count--;
                }
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("结果:" + count);
    }

	/**
     * 结果:0
     */

synchronized理解

  • synchronized(object)中的object可以看作是房间,线程t1和t2都要进到房间内操作count
  • t1进入到房间,并锁住了门拿走了钥匙,t2只能在门外等待t1执行完synchronized{}内的代码
  • t1执行期间,并不是锁住了对象就能一直执行synchronized{}里的代码,CPU分配的时间片用完,还是要被踢出门外,但是门是锁住的,t1仍拿着钥匙。t1再次分配到时间片,就会被继续执行代码
  • t1执行完synchronized{}内的代码,会打开门,唤醒t2并把钥匙给t2。t2拿到钥匙,进入房间,锁住门,执行count--操作

synchronized实际是用对象锁保证了临界区内代码的原子性,临界区内的代码对外是不可分割的,不会被线程切换所打断。

2、锁对象面向对象改进

把锁对象放入一个类

public static void main(String[] args) throws InterruptedException {
        Room room = new Room();//使用对象,相当于原子类

        Thread t1 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                room.increment();
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            for (int i = 0; i < 5000; i++) {
                room.decrement();
            }
        }, "t2");

        t1.start();
        t2.start();
        t1.join();
        t2.join();

        System.out.println("结果:" + room.getCount());
    }

class Room {
    int count;

    public void increment() {
        synchronized (this) {
            count++;
        }
    }

    public void decrement() {
        synchronized (this) {
            count--;
        }
    }

    public int getCount() {
        synchronized (this){
            return count;
        }
    }
}

结果还是0

3、加在方法上

相当于锁住了实例对象

    public void increment(){
        synchronized (this){
            
        }
    }

    //等价于
    public synchronized void increment(){
        
    }

相当于锁住了Class类对象(只有一个,实例对象可以有多个)

class Test{
    
    public static void increment(){
        synchronized (Test.class){

        }
    }

    //等价于
    public synchronized static void increment(){

    }
}

5、线程八锁

就是考察锁住了哪个对象

情况一:12(大概率)或者21

@Slf4j
public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n = new Number();

        Thread t1 = new Thread(() -> n.a());
        Thread t2 = new Thread(() -> n.b() );

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

@Slf4j
class Number{
    public synchronized void a(){//成员方法
        log.debug("1");
    }

    public synchronized void b(){//成员方法
        log.debug("2");
    }
}

情况二:1s 12或者2 1s 1

public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n = new Number();

        Thread t1 = new Thread(() -> {
			try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}
        });
        Thread t2 = new Thread(() -> n.b() );

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

@Slf4j
class Number{
    public synchronized void a() throws InterruptedException {//成员方法
        Thread.sleep(1000);//睡眠1秒
        log.debug("1");
    }

    public synchronized void b(){//成员方法
        log.debug("2");
    }
}

情况三:3 1s 12 或 32 1s 1 或 23 1s 1

public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n = new Number();

        Thread t1 = new Thread(() -> {
			try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}
        });
        Thread t2 = new Thread(() -> n.b() );
        Thread t3 = new Thread(() -> n.c() );

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

@Slf4j
class Number{
    public synchronized void a() throws InterruptedException {
        Thread.sleep(1000);//睡眠1秒
        log.debug("1");
    }

    public synchronized void b(){log.debug("2");}//成员方法

    public void c(){ log.debug("3"); }//成员方法
}

情况四:2 1s 1

public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n1 = new Number();
        Number n2 = new Number();

        Thread t1 = new Thread(() -> {
			try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}
        });
        Thread t2 = new Thread(() -> n2.b() );

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

@Slf4j
class Number{
    public synchronized void a() throws InterruptedException {//成员方法
        Thread.sleep(1000);//睡眠1秒
        log.debug("1");
    }

    public synchronized void b(){log.debug("2");}//成员方法
}

情况五:2 1s 1

public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n1 = new Number();

        Thread t1 = new Thread(() -> {
			try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}
        });
        Thread t2 = new Thread(() -> n1.b() );

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

@Slf4j
class Number{
    public static synchronized void a() throws InterruptedException {//静态方法
        Thread.sleep(1000);//睡眠1秒
        log.debug("1");
    }

    public synchronized void b(){log.debug("2");}//成员方法
}

情况六:1s 12 或 2 1s 1

public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n1 = new Number();

        Thread t1 = new Thread(() -> {
            try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}
        });
        Thread t2 = new Thread(() -> n1.b() );

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

@Slf4j
class Number{
    public static synchronized void a() throws InterruptedException {//静态方法
        Thread.sleep(1000);//睡眠1秒
        log.debug("1");
    }

    public static synchronized void b(){log.debug("2");}//静态方法
}

情况七:2 1s 1

public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n1 = new Number();
        Number n2 = new Number();

        Thread t1 = new Thread(() -> {
            try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}
        });
        Thread t2 = new Thread(() -> n2.b() );

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

@Slf4j
class Number{
    public static synchronized void a() throws InterruptedException {//静态方法
        Thread.sleep(1000);//睡眠1秒
        log.debug("1");
    }

    public synchronized void b(){log.debug("2");}//成员方法
}

情况八:1s 12 或 2 1s 1

public class Secure {

    public static void main(String[] args) throws InterruptedException {
        Number n1 = new Number();
        Number n2 = new Number();

        Thread t1 = new Thread(() -> {
            try {n1.a();} catch (InterruptedException e) {e.printStackTrace();}
        });
        Thread t2 = new Thread(() -> n2.b() );

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

@Slf4j
class Number{
    public static synchronized void a() throws InterruptedException {//静态方法
        Thread.sleep(1000);//睡眠1秒
        log.debug("1");
    }

    public static synchronized void b(){log.debug("2");}//静态方法
}

posted on 2022-12-20 12:32  如梦幻泡影  阅读(19)  评论(0编辑  收藏  举报