并发编程及synchronized关键字

/**
 * 并发编程
 * 什么是并发编程?
 * 并发编程是为了提高程序的执行速度,在宏观上使得多个任务同时执行,则需要启动多个线程
 * ,但事实启动多个线程之后,不管针对单核cpu还是多核cpu线程进行上下文切换(cpu 通过)
 * 给每一个线程分配时间片,只有说拿到时间片的线程才可以执行,通常时间片很短,所以才会
 * 感觉到多个线程在并行操作,存在线程不安全性。一个正确执行的并发程序,满足并发编程
 * 的三大特性,原子性、可见性、有序性
 * 1) 原子性
 * 所谓原子性是指一次或者多次操作中,要么所有的操作全部执行要么所有的操作都不执行
 * 原子操作是不可分割的操作,一个原子操作中间是不会被其他线程所打断的
 * int a = 10;   //1    10赋值给线程工作内存中的变量a 原子操作
 * a++; //2             拿a  进行a+1  赋值a     非原子操作
 * int b = a;  //3      拿a  b=a      非原子操作
 * a = a+1; //4         拿a  进行a+1  赋值a     非原子操作
 *
 * 2)可见性
 * 如果在一个线程对共享变量做了修改,那么另外的线程立即可以看到修改后的最新值
 * 3)有序性
 * 有序性指的程序代码在执行过程中的先后顺序,由于编译器或计算机的优化,导致代码
 * 的执行未必是开发者编写代码时的顺序
 * int x = 10; 1    1->3->2
 * int y = 0; 2
 * x++; 3
 *
 * x++在int y=0之前得到执行,这种情况叫做指令重排序
 * 在单线程环境中,无论怎么重排序,都不会影响最终的结果
 * 在多线程环境中,如果有序性得不到保证,最终结果也可能与预期不符
 *
 * 线程同步问题
 * 1)为什么需要同步?
 * 一块资源被多个线程同时操作,如果没有任何的同步操作,就会发生冲突,因为无法保证每个
 * 线程的执行结束时机,所以就无法控制最终结果
 * 2)临界资源:同一时刻只允许一个线程访问的资源,临界是不可剥夺资源
 *    临界区:访问临界资源的代码快
 *    临界区特点: 提供线程独占式访问,也就是说若有一个线程正在访问该代码段,其他线程
 *    想要访问,只有等待当前线程离开该代码段才可访问,这样就保证了线程安全
 * 3)银行叫号系统
 *  a.某个号码被略过
 *  线程A: index = index++ index=65++ -> 66 cpu将使用权转给了B
 *  线程B: index = index++ index=66++ -> 67 sout(67) 线程A再也没有得到执行,最终66被略过
 *  b.某个号码重复
 *  线程A:index = index++, 执行65++ -> 66,cpu将使用权给了B
 *  线程B: index = index++, 执行65++ -> 66, sout(66), cpu将使用权给A
 *  c.超过给定的最大值 500
 *  线程A: index=499, cpu将使用权给了B
 *  线程B: index=499++,sout(500),线程再次判断,最终导致号码超过给定的最大值
 *
 *  出现以上问题的原因为原先代码并没有满足并发编程三大特性
    private static int value = 0;
    public static void main(String[] args) {
    
    
        //改为线程安全
        new Thread("A"){
    
    
            @Override
            public void run() {
    
    
                int localValue = value;
                while(localValue < 5){
    
    
                    if(localValue != value){
    
    
                        System.out.println("the value is updated to "+value);
                        localValue = value;
                    }
                }
            }
        }.start();

        new Thread("B"){
    
    
            @Override
            public void run() {
    
    
                int localValue = value;

                while(localValue < 5){
    
    
                    System.out.println("the value will be changed to "+(++localValue));
                    value = localValue;

                    //短暂睡眠,使得A线程进行输出
                    try {
    
    
                        TimeUnit.SECONDS.sleep(1);
                    } catch (InterruptedException e) {
    
    
                        e.printStackTrace();
                    }
                }
            }
        }.start();

在这里插入图片描述

synchronzied关键字

 *  提供一种排他机制,在同一时间内去操作synchornized封装的代码块或者方法
 *
 *  用法:
 *  1)同步方法
 *  public synchornized void func(){
    
    
 *
 *  }
 *
 *  public synchornized static void func(){
    
    
 *
 *  }
 *  2)同步代码块
 * private final Object lock = new Object();
 * public void func(){
    
    
 *     synchornized(lock){
    
    
 *         ...
 *     }
 * }
 *
 * 重写叫号程序
 *

在这里插入图片描述
这张图是synchronized底层实现的两个重要部分
在这里插入图片描述

 * 1)同步方法
 * 常量池中多了ACC_SYNCHRONIZED标识符,标识当前的方法是一个同步方法,当方法被调用,调用指令会检查方法ACC_SYNCHRONIZED标识符是否被设置,如果设置,执行线程会先去获取monitor,获取成功之后才会去执行方法题,方法体执行完成之后或释放monitor。
 * 2)同步代码块
 * monitorenter
 * 每一个对象都与一个monitor相关联,一个monitor的lock只能被一个线程在同一时间所
 * 拥有,在一个线程尝试获取monitor的使用会发生以下事情:
 * a.如果monitor的entry count为0,意味monitor的lock还没有被获取,某个线程获取
 * 之后会对entry count+1,从此该线程就是这个monitor的所有者了
 * b.如果一个已经拥有该monitor使用权的线程再次进入,会导致monitor的entry count+1
 * (可重入锁)
 * c.如果monitor已经被其他线程所拥有,其他线程尝试获取该monitor的额所有权时,会被陷入阻塞,直到monitor的entry count为0,才能再次尝试去获取
 * monitorexit
 * 释放对monitor的使用权,要释放对某个对象的monitor的使用权前提是首先获取该monintor的所有权,将monitor的entry count-1,如果entry count为0,那就表示该线程不再拥有该monitor的使用

回顾:

 * 并发知识点
 * 1、并发指的是多个线程针对同一个资源,不是同时操作,而是交替操作
 * 2、临界资源 同一时间只允许一个线程访问
 *    临界区   访问临街资源的代码
 * 3、并发编程的三大特性
 * 1)原子性 一个操作是不可分割,那就是一个原子操作,就说这个操作具有原子性
 * 2)可见性 一个变量被多线程共享,如果一个线程修改了这个变量的值,其他线程
 * 能够立即得知修改,我们称这个修改具有可见性
 * 3)有序性 所有操作的执行有序的
 * 4、线程安全
 * 不考虑耗时和资源消耗,在单线程执行和多线程执行的情况下,最终得到的结果是
 * 相同的,那么称这样的操作是线程安全的
 *
 * synchronized关键字
 * 1)方法
 * 注意:调用不同的对象的同步方法,非线程安全的
 * 2)代码块
 * synchronized(){
    
    
 *
 * }
  • 课后练习:
  • 启动两个线程,顺序输出5,4,3,2,1,一个线程输出完成之后第二个线程输出
public class TestDemo7 {
    
    
    //同步方法
    //synchronzied修饰成员方法,synchornized获取this对象,this对象代表
    //当前对象
    public synchronized void test1(){
    
    
        获取test的monitor lock
        int i=5;
        while(i >=1){
    
    
            System.out.println(Thread.currentThread().getName()+"::"+i--);
            try {
    
    
                TimeUnit.MILLISECONDS.sleep(10);
            } catch (InterruptedException e) {
    
    
                e.printStackTrace();
            }
        }
        将test的monitor lock释放
    }

    public void test2(){
    
    
        //同步代码块
        synchronized (this){
    
    
            获取test的monitor lock
            int i=5;
            while(i >=1){
    
    
                System.out.println(Thread.currentThread().getName()+"::"+i--);
                try {
    
    
                    TimeUnit.MILLISECONDS.sleep(10);
                } catch (InterruptedException e) {
    
    
                    e.printStackTrace();
                }
            }
            test的monitor lock释放
        }
    }
    public static void main(String[] args) {
    
    

        TestDemo7 test = new TestDemo7();
   
        new Thread("A"){
    
    
            @Override
            public void run() {
    
    
                //test.test1();
                test.test2();
            }
        }.start();

        new Thread("B"){
    
    
            @Override
            public void run() {
    
    
                //test.test1();
                test.test2();
            }
        }.start();
    }

验证可重入锁

public static void main(String[] args) {
    
    
        Object obj  = new Object();
        //验证可重入锁
        new Thread(){
    
    
            @Override
            public void run() {
    
    
                synchronized (obj){
    
    
                    System.out.println("entry + 1");
                    synchronized (obj){
    
    
                        System.out.println("entry + 1 again");
                    }
                }
            }
        }.start();

synchornized的使用场景

 * 1、两个线程同时访问同一个对象的同步方法  安全
 * 2、两个线程同时访问两个对象的同步方法    不安全 test1.func1()  test2.func1()
 * 3、两个线程同时访问(一个或两个)对象的静态同步方法   安全
 * 4、两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法   不安全
 * 5、两个线程访问同一个对象中的同步方法,同步方法又调用另外一个非同步方法   不安全
 * 6、两个线程同时访问同一个对象的不同的同步方法    安全
 * 7、两个线程同时访问静态synchronized和非静态synchornized方法 不安全
 * 8、同步方法抛出异常,JVM会自动释放锁
  • synchornized的练习
 * 课堂练习:
 * synchornized同步锁实现3个线程循环打印数字,使用线程1,打印1,2,3,4,5.
 * 线程2,打印6,7,8,9,10.线程3,打印11,12,13,14,15.依次循环打印,直到
 * 打印至60(提示:会使用到wait/notify/notifyAll方法)
 
  • 1、两个线程同时访问同一个对象的同步方法 安全
    public synchronized void fun1(){
    
    
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
    }
        public static void main(String[] args) {
    
    
        TestDemo8 test1 = new TestDemo8();
        new Thread("A"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1();  //unlock
            }
        }.start();

        new Thread("B"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1(); //lock
            }
        }.start();

2、两个线程同时访问两个对象的同步方法 不安全 test1.func1() test2.func1()。
一个对象一个锁,因为两个对象 所以换锁就线程不安全

 public synchronized void fun1(){
    
    
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
    }
    public static void main(String[] args) {
    
    
        TestDemo8 test1 = new TestDemo8();
        TestDemo8 test2 = new TestDemo8();
        new Thread("A"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1();  //unlock
                test2.fun1();
            }
        }.start();

        new Thread("B"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1(); //lock
                test2.fun1();
            }
        }.start();
  • 3、两个线程同时访问(一个或两个)对象的静态同步方法 安全
    public synchronized static void fun2() throws Exception {
    
    
        //类锁  class对象
        System.out.println(Thread.currentThread().getName()+":: 静态同步方法func2开始");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 静态同步方法func2结束");
    }
    public static void main(String[] args) {
    
    
        XianCheng_safe test1 = new XianCheng_safe();
        new Thread("A"){
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    test1.fun2();
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }

            }
        }.start();

        new Thread("B"){
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    test1.fun2();
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                }
            }
        }.start();
    }

4、两个线程分别同时访问(一个或两个)对象的同步方法和非同步方法 不安全

 public synchronized void fun1(){
    
    
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
    }
    public void fun3(){
    
    
        System.out.println(Thread.currentThread().getName()+":: 非同步方法func3");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 非同步方法func3");
    }
        public static void main(String[] args) {
    
    
        TestDemo8 test1 = new TestDemo8();
        TestDemo8 test2 = new TestDemo8();
        new Thread("A"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1();  //unlock
                test2.fun3();
            }
        }.start();

        new Thread("B"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1(); //lock
                test2.fun3();
            }
        }.start();

5、两个线程访问同一个对象中的同步方法,同步方法又调用另外一个非同步方法 不安全。

6、两个线程同时访问同一个对象的不同的同步方法 安全

     public synchronized void fun1(){
    
    
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);

        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
    }
    public synchronized void fun4(){
    
    
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun4开始");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 同步方法func4结束");
    }
    public static void main(String[] args) {
    
    
        XianCheng_safe test1 = new XianCheng_safe();
        new Thread("A") {
    
    
            @Override
            public void run() {
    
    
                test1.fun1();
            }
        }.start();

        new Thread("B") {
    
    
            @Override
            public void run() {
    
    
                test1.fun4();
            }
        }.start();
    }

7、两个线程同时访问静态synchronized和非静态synchornized方法 不安全
静态synchronized 锁的是class对象
非静态synchornized 锁的是你创建的对象。

8、同步方法抛出异常,JVM会自动释放锁

 public synchronized void fun1(){
    
    
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1开始");
        try {
    
    
            TimeUnit.MILLISECONDS.sleep(1000);

        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+":: 同步方法fun1结束");
    }
    public static void main(String[] args) {
    
    
        XianCheng_safe test1 = new XianCheng_safe();
        Thread threadc = new Thread("C"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1();
            }
        };
        threadc.start();
        threadc.interrupt();
        new Thread("D"){
    
    
            @Override
            public void run() {
    
    
                test1.fun1();
            }
        }.start();

猜你喜欢

转载自blog.csdn.net/weixin_47198561/article/details/113602211