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");}//静态方法
}
本文来自博客园,作者:如梦幻泡影,转载请注明原文链接:https://www.cnblogs.com/WangJiQing/p/16993933.html