廖雪峰Java11多线程编程-2线程同步-1同步代码块
1.线程安全问题
多个线程同时运行,线程调度由操作系统决定,程序本身无法决定。
如果多个线程同时读写共享变量,就可能出现问题。
假设有AddThread和DecThread,它们分别对同一个共享变量做加和减运算LOOP次,最终结果应该是0。但某些时候比如LOOP为10000时,结果是错误的。
class AddThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
Main.count += 1;
}
}
}
class DecThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
Main.count -= 1;
}
}
}
public class Main {
final static int LOOP = 10000;
public static int count = 0;
public static void main(String[] args) throws InterruptedException{
Thread t1 = new AddThread();
Thread t2 = new DecThread();
t1.start();
t2.start();
//等待这两个线程执行结束
t1.join();
t2.join();
System.out.println(count);
}
}

## 2.原子操作
* 因此对共享变量进行写入时,必须保证是原子操作
* 原子操作是指不能被中断的一个或一系列操作
当执行 n = n +1时,编译器会把它编译为3条字节码指令,分别是ILOAD, IADD, ISTORE。所以对于这个简单的赋值语句,它并不是一个原子操作,这就可能导致两个线程在执行这条语句的时候,会出现问题。
假设1:n=100,Thread1执行语句n为101,Thread2再执行n为102。
假设2:Thread1刚执行完ILOAD指令,就被操作系统暂停了,然后Thread2调度执行,结果n变成了101,此后Thread1再度被操作系统调度执行,结果也是101。即n+1的指令被2个线程调用了2次,最终只加了1.

所以我们要保证当Thread1执行时,Thread2不能执行,直到Thread1执行完毕,Thread2才能开始执行。这样运行的结果就是正确的。
要实现这个效果,就要对ILOAD之前和ISTORE之后进行加锁和解锁。

3.同步代码块
Java使用synchronized对一个对象进行加锁:
- 为了保证一系列操作作为原子操作,必须保证一系列操作过程中不被其他线程执行
synchronized (lock){
n=n+1;
}
当一个线程想要执行synchronized语句块时,必须首先获得指定对象的锁,这个对象就是synchronized括号里的对象,然后线程再执行synchronized语句块,执行结束以后释放锁。
在执行synchronized语句块时,如果Thread1执行到任何语句时,被操作系统中断。其他线程如Thread2因为无法获取lock对象的锁,从而导致Thread2无法进入synchronized语句块,Thread2就必须等待,直到Thread1再次被调用,并执行完synchronized语句块释放了锁,Thread2才能获得lock对象锁,进入synchronized语句块。
synchronized保证了代码块和任意时刻最多只有一个线程能执行。
- 因为一个对象的锁只能被一个线程获得,其他线程必须等待。
synchronized的问题:
- 性能下降。因为synchronized代码块无法并发执行,所以性能会下降。此外加锁和解锁都会消耗一定的时间,所以synchronized会降低程序的执行效率。
如何使用synchronized:
- 1.找出修改共享变量的线程代码块
- 2.选择一个实例作为锁
- 3.使用synchronized(lock Object){...}
注意:
- 对于同一个变量的修改,必须要获取同一个锁,如果2个线程获取的是不同的锁,它们是没有办法进行同步的。
- 不用担心异常。无论有无异常,在synchronized结束时都会释放锁。
class AddThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
synchronized (Main.LOCK) {
Main.count += 1;
}
}
}
}
class DecThread extends Thread{
public void run(){
for(int i=0;i<Main.LOOP;i++){
synchronized (Main.LOCK) {//对于同一个变量的修改,要使用同一个锁
Main.count -= 1;
}//无论有无异常,都会在此释放锁
}
}
}
public class Main {
final static int LOOP = 10000;
public static int count = 0;
public static final Object LOCK = new Object();
public static void main(String[] args) throws InterruptedException{
Thread t1 = new AddThread();
Thread t2 = new DecThread();
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(count);
}
}

4.JVM的原子操作
JVM定义了几种原子操作:
- 基本类型(long和double除外)赋值
- 引用类型赋值
注意:
- 原子操作时不需要同步的。
- 可以把非原子操作变为原子操作
- 局部变量不需要同步
//原子操作不需要同步
public void set(int m){
synchronized (obj){
this.value = m;
}
}
//->
public void set(int m){
this.value = m;
}
//对2个int类型进行赋值,它不是一个原子操作。但可以先构造一个int数组,然后利用引用类型赋值,把它变成1个原子操作。
class Pair{
int first;
int last;
public void set(int first,int last){
synchronized (this){
this.first = first;
this.last = last;
}
}
}
//->
class Pair{
int[] pair;
public void set(int first,int last){
int[] ps = new int[]{first,last};
this.pair = ps;
}
}
//a,b,s1,s2,r都是局部变量,各个线程的局部变量是完全独立的,互不影响,所以这个方法不需要同步。
public int avg(int a, int b){
int s1 = a*a + b*b;
int s2 = a + b;
int r = s1/s2;
return r;
}
5.总结:
- 多线程同时修改变量,会造成逻辑错误
* 需要通过synchronized同步
* 同步的本质就是给指定对象加锁
* 注意加锁对象必须是同一个实例 - 对JVM定义的单个原子操作不需要同步
廖雪峰Java11多线程编程-2线程同步-1同步代码块的更多相关文章
- 廖雪峰Java11多线程编程-2线程同步-3死锁
1.线程锁可以嵌套 在多线程编程中,要执行synchronized块: 必须首先获得指定对象的锁 Java的线程锁是可重入的锁.对同一个对象,同一个线程,可以多次获取他的锁,即同一把锁可以嵌套.如以下 ...
- 廖雪峰Java11多线程编程-2线程同步-2synchronized方法
1.Java使用synchronized对一个方法进行加锁 class Counter{ int count = 0; public synchronized void add(int n){ cou ...
- 廖雪峰Java11多线程编程-2线程同步-4wait和notify
wait和notify synchronized解决了多线程竞争的问题 我们可以在synchronized块中安全的对一个变量进行修改,但是它没有解决多线程协调的问题. 例如设计一个TaskQueue ...
- 廖雪峰Java11多线程编程-1线程的概念-1多线程简介
多任务 现代操作系统(windows,MacOS,Linux)都可以执行多任务: 多任务就是同时运行多个任务,例如同时开启钉钉.百度网盘.火狐.谷歌.ps等 操作系统执行多任务就是让多个任务交替执行, ...
- 廖雪峰Java11多线程编程-4线程工具类-1ThreadLocal
多线程是Java实现多任务的基础: Thread ExecutorService ScheduledThreadPool Fork/Join Thread对象代表一个线程:调用Tread.curren ...
- 廖雪峰Java11多线程编程-1线程的概念-2创建新线程
Java语言内置多线程支持: 一个Java程序实际上是一个JVM进程 JVM用一个主线程来执行main()方法 在main()方法中又可以启动多个线程 1.创建新线程 1.1 方法一:使用Thread ...
- 廖雪峰Java11多线程编程-1线程的概念-5中断线程
1.中断线程: 如果线程需要执行一个长时间任务,就可能需要中断线程.场景:从网络上下载一个100M的文件,用户在下载过程中中断下载任务的执行. 中断线程就是其他线程给该线程发一个信号,该线程收到信号后 ...
- 廖雪峰Java11多线程编程-1线程的概念-3线程的状态
1线程的状态 线程终止的的原因: run()或call()方法执行完成,线程正常结束 线程抛出一个未捕获的Exception或Error 直接调用该线程的stop()方法来结束该线程--该方法容易导致 ...
- 廖雪峰Java11多线程编程-3高级concurrent包-2ReadWriteLock
ReentrantLock保证单一线程执行 ReentrantLock保证了只有一个线程可以执行临界区代码: 临界区代码:任何时候只有1个线程可以执行的代码块. 临界区指的是一个访问共用资源(例如:共 ...
随机推荐
- el-table单元格新增、编辑、删除功能
<template> <div class="box"> <el-button class="addBtn" type=" ...
- re.groups取出来的空元祖??
源自学习笔记: day23_1_re_ groups方法取出来的字符是空的元组??为啥? ''' # ------------------------------------------------- ...
- Assert(断言) 的用法
Assert Assert是断言的意思,头文件为assert.h, assert是一个宏 功 能: 测试一个条件并可能使程序终止 用 法: void assert(int test); 在单元测试中经 ...
- 标记excel中输入的重复数据
首先选中需要标记重复的数据列 开始 -> 条件格式 -> 突出显示单元格规则 -> 重复值 选择相应的颜色即可 效果如下:
- 国外主机如何ICP备案
想都不要想了,无法备案. 因为,备案是在主机服务器提供商处的备案平台提交申请,国外的主机服务商是没有这种平台服务的.(跟你域名在哪儿买的没关系) 下面,把昨天折腾到半夜的过程记录一下,希望可以帮到需要 ...
- error C2146: 语法错误: 缺少“;”(在标识符“CRC”的前面) ...\...\MyMethod.h
错误原因:头文件的顺序错误,这种情况一般是因为dxsdk的头文件放在其他头文件前面了. 问题复现: 这里如果将#include <ReadDataThreadClass.h>放到最末尾就不 ...
- JConsole&VisualVM监控总结
简介JConsole(以下写作jconsole),VisualVM(以下写作jvisualvm ) 都是比较好的JVM调优工具,且都为JDK自带,可在命令行直接启动. 监控示例Server端(需要监控 ...
- uoj279 题目交流通道
题目:告诉你每两个点之间的最短路距离.构造每条边边权<=m的无向完全图.求有多少种不同边权的图满足最短路限制?n<=400. 标程: #include<cstdio> #inc ...
- MAC地址获取,有线网卡与无线网卡、物理网卡与虚拟网卡的区分
获取当前活跃状态的网卡MAC地址.物理地址 Wmic命令:Win32_NetworkAdapter和Win32_NetworkAdapterConfiguration. 其中cmd命令行执行: 1. ...
- 【JZOJ3347】树的难题
description analysis 比较麻烦树形\(DP\) 不过这个我还是不算很懂-- 下次要注意思考,不要怕麻烦 code #pragma GCC optimize("O3&quo ...