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同步代码块的更多相关文章

  1. 廖雪峰Java11多线程编程-2线程同步-3死锁

    1.线程锁可以嵌套 在多线程编程中,要执行synchronized块: 必须首先获得指定对象的锁 Java的线程锁是可重入的锁.对同一个对象,同一个线程,可以多次获取他的锁,即同一把锁可以嵌套.如以下 ...

  2. 廖雪峰Java11多线程编程-2线程同步-2synchronized方法

    1.Java使用synchronized对一个方法进行加锁 class Counter{ int count = 0; public synchronized void add(int n){ cou ...

  3. 廖雪峰Java11多线程编程-2线程同步-4wait和notify

    wait和notify synchronized解决了多线程竞争的问题 我们可以在synchronized块中安全的对一个变量进行修改,但是它没有解决多线程协调的问题. 例如设计一个TaskQueue ...

  4. 廖雪峰Java11多线程编程-1线程的概念-1多线程简介

    多任务 现代操作系统(windows,MacOS,Linux)都可以执行多任务: 多任务就是同时运行多个任务,例如同时开启钉钉.百度网盘.火狐.谷歌.ps等 操作系统执行多任务就是让多个任务交替执行, ...

  5. 廖雪峰Java11多线程编程-4线程工具类-1ThreadLocal

    多线程是Java实现多任务的基础: Thread ExecutorService ScheduledThreadPool Fork/Join Thread对象代表一个线程:调用Tread.curren ...

  6. 廖雪峰Java11多线程编程-1线程的概念-2创建新线程

    Java语言内置多线程支持: 一个Java程序实际上是一个JVM进程 JVM用一个主线程来执行main()方法 在main()方法中又可以启动多个线程 1.创建新线程 1.1 方法一:使用Thread ...

  7. 廖雪峰Java11多线程编程-1线程的概念-5中断线程

    1.中断线程: 如果线程需要执行一个长时间任务,就可能需要中断线程.场景:从网络上下载一个100M的文件,用户在下载过程中中断下载任务的执行. 中断线程就是其他线程给该线程发一个信号,该线程收到信号后 ...

  8. 廖雪峰Java11多线程编程-1线程的概念-3线程的状态

    1线程的状态 线程终止的的原因: run()或call()方法执行完成,线程正常结束 线程抛出一个未捕获的Exception或Error 直接调用该线程的stop()方法来结束该线程--该方法容易导致 ...

  9. 廖雪峰Java11多线程编程-3高级concurrent包-2ReadWriteLock

    ReentrantLock保证单一线程执行 ReentrantLock保证了只有一个线程可以执行临界区代码: 临界区代码:任何时候只有1个线程可以执行的代码块. 临界区指的是一个访问共用资源(例如:共 ...

随机推荐

  1. 导出Excel格式数据

    /** * 导出规则统计数据 excel格式 * @param bill * @return */ public String exportExamsignupExamnoExcel ( String ...

  2. springboot启动报 A child container failed during start 错误解决过程

    启动结果如下: "C:\Program Files\Java\jdk1.8.0_201\bin\java.exe" -agentlib:jdwp=transport=dt_sock ...

  3. Ubuntu 更新错误修复大全

    合并列表问题 当你在终端中运行更新命令时,你可能会碰到这个错误“合并列表错误”,就像下面这样: E:Encountered a section with no Package: header, E:P ...

  4. LUOGU P1039 侦探推理 (字符串+模拟)

    传送门 解题思路 一道%你神题,\(string\)好强大啊..首先枚举一个周几,再枚举一个罪犯是谁,然后判断的时候就是枚举所有人说的话.定义\(fAKe[i]\)表示第\(i\)个人说的是真话还是假 ...

  5. 关于css布局的定位问题

    虽然职位说是PHP程序,但实际上什么都要做些,排版当然也免不了了,以前自己做网站时就能排出网页了,但是很多东西不系统,有点走马观花,例如关于这个css布局定位的问题就是,今天特意总结了一下,知识这东西 ...

  6. day18_文件处理_迭代器_生成器

    #!/usr/bin/env python # -*- coding:utf-8 -*- # ********************day18_文件处理_迭代器_生成器 ************** ...

  7. Python中%r和%s的详解及区别_python_脚本之家

    Python中%r和%s的详解及区别_python_脚本之家 https://www.jb51.net/article/108589.htm

  8. Axios的get和post请求写法

    执行get请求 // 为给定 ID 的 user 创建请求 axios.get('/user?ID=12345') .then(function (response) { console.log(re ...

  9. java系统监控分析Jprofile下载及安装配置【转】

    JProfiler是一个全功能的Java剖析工具(profiler),专用於分析J2SE和J2EE应用程式.它把CPU.线程和记忆体的剖析组合在一个强大的应用中.JProfiler可提供许多IDE整合 ...

  10. jmeter做bbs作业时提示404错误

    在用jemter做bbs作业时候,注册成功后会跳转到主页,在写主页的脚本的时候,将fiddler抓到的url复制到路径下方“/bbs/forum.php”,但是第一次复制的时候在/bbs/forum. ...