线程的Synchronized锁
Posted howlet
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了线程的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,则释放该锁)
以上是关于线程的Synchronized锁的主要内容,如果未能解决你的问题,请参考以下文章
#yyds干货盘点# Java | 关于synchronized相关理解
java多线程——锁机制synchronized(同步方法)