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. div中内容可左右上下滑动

    在<table>外套一层<div>,并且声明overflow:scroll属性,如: <div style="width:1620px;height:680px ...

  2. CSS W3C统一验证工具

    CssStats 是一个在线的 CSS 代码分析工具  网址是: http://www.cssstats.com/ 如果你想要更全面的,这个神奇,你值得拥有: W3C 统一验证工具: http://v ...

  3. 弹性网卡支持私网多IP

    摘要: 弹性网卡支持多IP功能可以最多在一块弹性网卡配置20个私网IP地址,特别适用于于以下场景. 1.单个服务器上托管多个应用,提升实例利用率,每个应用对外暴露一个独立的服务IP地址. 2.当实例发 ...

  4. thinkphp 默认值输出

    我们可以给变量输出提供默认值,例如: 大理石平台厂家 {$user.nickname|default="这家伙很懒,什么也没留下"} 对系统变量依然可以支持默认值输出,例如: {$ ...

  5. 8 包含min函数的栈

    0 引言 题目:定义栈的数据结构,请在该类型中实现一个能够得到栈的最小元素的min函数.在该栈中,调用min.push及pop的时间复杂度都是O(). 1 抽象问题具体化 2 具体问题抽象分析 需要解 ...

  6. .net Framework 中的四种计时器

    在Framework中存在着4种定时器:其中分为两类, 多线程计时器 1:System.Threading.Timer 2:System.Timers.Timer 特殊目的的单线程计时器: 1:Sys ...

  7. Docker系列(六):Docker网络机制(下)

    Linux Namespace详解 namespace:是一个空间,空间里可以放进程,文件系统,账号,网络等,某个资源被放到namespace之后 别人就看不到他了. 可以看到有两个namespace ...

  8. 纯CSS3实现图片展示特效

    本文中要实现的一个纯CSS3的图片展示特效,以前只能用JavaScript实现,可想而知会受到多方面的限制,特别是性能.而今天我们将用简单的CSS3代码实现,你会发现它的动画效果在现代浏览器的帮助下无 ...

  9. <Python基础>python是如何进行内存管理的

    .Python 是如何进行内存管理的?答:从三个方面来说,一对象的引用计数机制,二垃圾回收机制,三内存池机制⒈对象的引用计数机制Python 内部使用引用计数,来保持追踪内存中的对象,所有对象都有引用 ...

  10. MySQL5.1的安装过程

    开始配置文件