线程的Synchronized锁



1. 多线程可能出现的安全问题

public class Synchronizedtest implements Runnable {

    int i = 10;     //共享变量

    @Override
    public void run() {
        if( i == 10 ){
            System.out.println("i == 10");
            sys
            i++;
        }
    }
    
    public static void main(String[] args) throws InterruptedException {
        
        Synchronizedtest st = new Synchronizedtest();
        Thread t1 = new Thread(st,"线程1");
        Thread t2 = new Thread(st,"线程2");
        
        t1.start();
        // t1.sleep(1000);  第一次测试先注释掉,第二次测试打开,下面为两次测试结果
        t2.start();
    }
}
i == 10
i == 10
i == 10


问题分析:

  • i++ 这个操作是非原子性的,分为三步:
    • 读取 i 的值
    • 将读取的数值 +1
    • 将数值写回 i
  • 线程t1,读取了i 值为10,在把值写回 i (i++ = 11) 之前,线程t2就读取了 i 的值,此时t1并未修改 i 的值,所以 i 还是等于10
  • 因此二者判断 i 都是等于10,即都会输出内容
  • 第二次测试执行到t1.start()时先 “暂停” 1秒,t2线程还不开启,t1在1秒内绝对执行完之后才开启t2线程,这样 i 的值已经更新为11了,此时t2就不输出内容
  • 如果不把变量放在成员变量上,而是放在方法内,这样就不会共享了,因为方法是栈操作,独享空间




2. 解决方法

  • 不设置共享变量,放入方法体内成为栈的独享空间

  • 用final修饰基础变量,但引用变量用final修饰还是不行(指向不可变,但内容可变)

  • 加锁(内置锁,显示Lock锁),后面会有说明

  • 使用安全类

    • 原子性:Atomic包

    • 容器:ConcurrentHashMap

    • locks包




3. 准备知识点及关键字

  • 原子性:执行多个操作,其中一个操作没执行的话,全部操作也不执行;否则全部执行
  • 内存屏障:CPU有缓存,如果数据在缓存上,不能实时和内存发生信息交换,分在不同CPU执行的不同线程对同一个变量的缓存值不同
  • 有序性:代码的执行顺序按照代码的先后顺序执行,不用考虑重排序(指令执行前JVM会优化且重排序)
  • 可见性:一个线程对共享变量的修改,另一个线程能立刻看到
    • volatile:轻量级的同步机制,只修饰类变量和实例变量,仅保证可见性,不保证原子性,保证有序性,某变量被修改后所有进程知道该变量被修改,但如果重新赋值,这个还是非原子性分三步走(一旦完成写入操作,所有进程都会得到最新值)




4 锁


4.1 synchronized内置锁

  • 它是java的关键字,可以修饰方法,代码块,类
  • synchronized锁一次只能允许一个线程进入被锁住的代码块,java每个对象都有内置锁 / 监视器锁,synchronized就是使用对象的内置锁来锁定的
  • 保证锁内的原子性和可见性


4.1.1 方法锁

public class Synchronizedtest implements Runnable {
    
    //使用的是该类的锁
    @Override
    public synchronized void run() {
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
    
    public static void main(String[] args) {
        
        Synchronizedtest st = new Synchronizedtest();
        Thread t1 = new Thread(st,"线程1");
        Thread t2 = new Thread(st,"线程2");
        
        t1.start();
        t2.start();
    }
}
线程1------96
线程1------97
线程1------98
线程1------99    //获得锁,执行完才释放,t2线程不能执行该方法
线程2------0
线程2------1
线程2------2
线程2------3
线程2------4


4.1.2 代码块锁

public void run() {

    //使用的也是该类的锁,打印结果是一致的
    //也可以用一个对象作为锁,客户端锁,但不推荐
    synchronized(this){
        for(int i = 0;i < 100;i++){
            System.out.println(Thread.currentThread().getName() + "------" + i);
        }
    }
}


4.1.3 静态锁

public class test {

    //静态方法属于类,获取到的锁是属于类锁(类的字节码文件对象)
    public static synchronized void test() {
    }
}


4.1.4 类锁与对象锁

二者不会冲突,即即可获得对象锁,也可获得类锁

public class testLock {

    //对象锁
    public synchronized void lockOne() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }
    
    //类锁
    public static synchronized void lockTwo() throws InterruptedException {
        for (int i = 0; i < 100; i++) {
            System.out.println(Thread.currentThread().getName() + ": " + i);
        }
    }

    public static void main(String[] args) {
        testLock demo = new testLock();

       
        Thread t1 = new Thread(() -> {
            try {
                demo.lockOne();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        Thread t2 = new Thread(() -> {
            try {
                lockTwo();
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        });
        
        t1.start();
        t2.start();
    }
}
//两个线程都执行
Thread-1: 99
Thread-0: 35


4.1.5 内置锁的可重入性

public class Widget {

    // 锁住了
    public synchronized void doSomething() {
        System.out.println("Wigget--------------");
    }
}

public class LoggingWidget extends Widget {

    // 锁住了
    public synchronized void doSomething() {
        System.out.println("LoggingWidget------------");
        super.doSomething();
    }
}
  • 能运行,不会死锁
  • 线程运行LoggingWidget的dosomething()方法时,获得LoggingWidget的对象实例锁
  • 当调用super.doSomething();时,调用者还是LoggingWidget,再次获取LoggingWidget的对象实例锁,再次锁,即锁的重入
  • 上面的锁是在实例对象上的,不是类上的,锁都是同一个,但不是获得多把锁(每个锁有个关联对象和计数器,当某一线程请求锁成功后,JVM记下锁的持有线程,并且将计数器置为1;此时其它线程请求该锁,则必须等待;而如果同一个线程再次请求这个锁,就可以再次拿到这个锁,同时计数器会递增;当线程退出同步代码块时,计数器会递减,如果计数器为0,则释放该锁)

猜你喜欢

转载自www.cnblogs.com/Howlet/p/12230652.html