Java拾贝第九天——synchronized关键字
Java拾贝不建议作为0基础学习,都是本人想到什么写什么
当多个线程同时读取某一变量时候,容易出现和预期不符的结果
public class Test9 {
static int i = 0;
public static void main(String[] args) {
Thread m1 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {//给静态属性i加10000
Test9.i++;
}
System.out.println(i);//循环结束打印i
}
};
Thread m2 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {//给静态属性i减10000
Test9.i--;
}
System.out.println(i);//循环结束打印i
}
};
m1.start();
m2.start();
try {
m1.join();//理想状态先加后减
Thread.sleep(5000);
System.out.println(Test9.i);//main线程睡眠5秒最后打印i值
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序运行结果:
2168
-80
-80
是的,不知道什么原因最后i值输出居然是-80。
这是因为线程的调度是操作系统决定的。因此,任何一个线程都有可能意外暂停,然后在某个时间段后继续执行。
想要使得上述问题解决须保证原子性。即一个或一系列操作无法被中断
synchronized
解决上述难题可以使用同步(synchronized)代码块和同步(synchronized)方法两种方式完成
同步代码块
代码块就是指用{}包括的一段代码,根据位置和声明的不同可以分为普通代码块,局部代码块,静态代码块和同步代码块。
同步代码块就是指在代码块前加上synchronized关键字:
synchronized(同步对象){
}
与其他代码块不同的是,同步代码块需要传入一个同步对象。同时也保证了代码块在任意时刻最多只有一个线程能执行。
同步对象也叫锁。而且也可以理解为锁,即谁拿到了锁谁就可以进门操作。
至此,修改上述的栗子代码:
public class Test9 {
static int i = 0;
static Object o=new Object();//新建一个成员变量o作为同步对象
public static void main(String[] args) {
Thread m1 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronized (Test9.o){//放到循环外线程不是并发
Test9.i++;
}
}
System.out.println(i);
}
};
Thread m2 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronized (Test9.o){//放到循环外等于线程顺序执行
Test9.i--;
}
}
System.out.println(i);
}
};
m1.start();
m2.start();
try {
Thread.sleep(5000);
System.out.println("main"+Test9.i);//main线程睡眠5秒最后打印i值
} catch (Exception e) {
e.printStackTrace();
}
}
}
程序运行结果:
243
0
main0
成功了!预期的结果达到了!变量i在经过m1和m2两个线程一阵倒腾,最终为0;
m1和m2在执行各自的同步代码块时,必须先获取同步对象才能进入代码块运行。执行结束后会自动释放同步对象。这样一来,对变量i的读写就不能同时进行。
即便在同步代码块中抛出异常,同步对象也会在同步代码块结束处正确的释放
注意
对于上述栗子同步对象一定要相同!!否则预期结果还是会错误
public class Test9 {
static int i = 0;
static Object o=new Object();
static Object o2=new Object();
public static void main(String[] args) {
Thread m1 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronized (Test9.o){//同步对象不一致
Test9.i++;
}
}
System.out.println(i);
}
};
Thread m2 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
synchronized (Test9.o2){//同步对象不一致
Test9.i--;
}
}
System.out.println(i);
}
};
//省略线程启动和主线程休眠打印i值
}
程序运行结果:
1815
1241
main1241
同步方法
同步方法就是用synchronized关键字声明的方法
synchronized 返回类型 方法名(传参){
}
修改栗子:
public class Test9 {
static int i = 0;
public static void main(String[] args) {
Test9 t9 = new Test9();
Thread m1 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
t9.addi();
}
System.out.println(i);
}
};
Thread m2 = new Thread() {
@Override
public void run() {
for (int j = 0; j < 10000; j++) {
t9.subi();
}
System.out.println(i);
}
};
m1.start();
m2.start();
try {
Thread.sleep(5000);
System.out.println("main" + Test9.i);
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized int addi() {
return ++i;
}
public synchronized int subi() {
return --i;
}
}
程序运行结果:
1134
0
main0
注意
那么同步方法的同步对象是谁呢?
Test9 t9 = new Test9();
t9.addi();
t9.subi();
同步方法的同步对象,就是调用同步方法的对象。即变量t9
对于使用了sitatic关键字的同步方法,其同步对象就是静态方法所在的类。也称静态同步方法
public class Test {
public static synchronized void test() {
System.out.println("对于这个栗子,同步对象就是Test类");
}
}
因为静态属性和方法是属于类的
死锁
在开发中要避免死锁的出现,死锁发生后程序没有任何办法能够解除死锁机制。只能强制结束JVM进程才能解决。
死锁:
A拿到了书👉想要画
B拿到了画👉想要书
然后AB两个线程都在对待对方释放资源,造成了程序的停滞。这就是死锁
举一个简单的栗子:
public class Test9 {
Object o1 = new Object();//创建两个锁
Object o2 = new Object();
public static void main(String[] args) {
Test9 t9 = new Test9();
Thread m1 = new Thread() {
@Override
public void run() {
System.out.println("m1开始获取o1锁");
synchronized (t9.o1) {
try {
Thread.sleep(3000);//休眠三秒确保死锁发生
System.out.println("m1休眠结束,开始获取o2锁");
synchronized (t9.o2) {
System.out.println("m1成功获取o2锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("m1结束");
}
};
Thread m2 = new Thread() {
@Override
public void run() {
System.out.println("m2开始获取o2锁");
synchronized (t9.o2) {
try {
Thread.sleep(3000);//休眠三秒确保死锁发生
System.out.println("m2休眠结束,开始获取o1锁锁");
synchronized (t9.o1) {
System.out.println("m2成功获取o1锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("m2结束");
}
};
m1.start();
m2.start();
}
}
程序运行结果:
m1开始获取o1锁
m2开始获取o2锁
m1休眠结束,开始获取o2锁
m2休眠结束,开始获取o1锁锁
[....发生死锁,后面的代码不再执行]
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?