线程同步

一、线程安全问题

如果你的代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

线程安全问题往往发生在多个线程调用同一方法或者操作同一变量,但是我们要知道其本质就是CPU对线程的随机调度,CPU无法保证一个线程执行完其逻辑才去调用另一个线程执行。

package threadtest;

public class ThreadTest  implements Runnable{

    static int i = 0;
public void incre() {
i++;
} @Override
public void run() {
for(int j=0;j<1000000;j++) {
incre();
}
} public static void main(String[] args) throws InterruptedException {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }

由于 i++不是原子操作,先读取值,再加1 赋值,所以当在读取i值的时候线程切换了,导致两个线程读取的i相同,导致线程安全问题。

1363390  //结果小于2000000

二、线程同步

java多线程支持引入了同步监视器来解决线程同步问题,通过synchronized关键字,主要有同步方法和同步代码块

执行同步代码前必须先获得对同步监视器的锁定(任何时刻都只有一个线程可以获得同步监视器的锁定)

java5开始提供了更强大的同步机制,同步锁Lock

1、同步方法: synchronized修饰方法

package threadtest;

public class ThreadTest  implements Runnable{

    static int i = 0;
public synchronized void incre() {
i++;
} @Override
public void run() {
for(int j=0;j<1000000;j++) {
incre();
}
} public static void main(String[] args) throws InterruptedException {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }

同步方法的同步监视器就是方法所属的对象本身

2000000 //结果正常

synchronized修饰静态方法

package threadtest;

public class ThreadTest  implements Runnable{

    static int i = 0;
/**
* 同步静态方法的同步监视器是该类对应的class对象
*/
public static synchronized void incre() {
i++;
} @Override
public void run() {
for(int j=0;j<1000000;j++) {
incre();
}
} public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new ThreadTest());
Thread t2 = new Thread(new ThreadTest());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }

上面类中synchronized修饰的静态方法,同步监视器是该类对应的class对象,i是类属性,多个线程调用不同实例,i也是线程安全的。

2000000

2、同步代码块

除了使用关键字修饰实例方法和静态方法外,还可以使用同步代码块,在某些情况下,我们编写的方法体可能比较大,同时存在一些比较耗时的操作,而需要同步的代码又只有一小部分,如果直接对整个方法进行同步操作,可能会得不偿失,此时我们可以使用同步代码块的方式对需要同步的代码进行包裹,这样就无需对整个方法进行同步操作了

package threadtest;

public class ThreadTest  implements Runnable{

    static int i = 0;
/**
* 同步代码块,synchronized(obj),obj就是同步监视器
*/
public void incre() {
synchronized(this) {
i++;
}
} @Override
public void run() {
for(int j=0;j<1000000;j++) {
incre();
}
} public static void main(String[] args) throws InterruptedException {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }

同步代码块修饰静态方法

package threadtest;

public class ThreadTest  implements Runnable{

    static int i = 0;
/**
* 同步代码块,synchronized(obj),obj就是同步监视器
*/
public static void incre() {
synchronized(ThreadTest.class) {
i++;
}
} @Override
public void run() {
for(int j=0;j<1000000;j++) {
incre();
}
} public static void main(String[] args) throws InterruptedException {
//ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(new ThreadTest());
Thread t2 = new Thread(new ThreadTest());
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }

3、同步锁Lock

java5开始提供了通过显示定义同步锁对象来实现同步。

在实现线程安全中,比较常用的是ReentrantLock(可重入锁),它是Lock接口的实现类。

package threadtest;

import java.util.concurrent.locks.ReentrantLock;

public class ThreadTest  implements Runnable{

    private final ReentrantLock lock = new ReentrantLock();
static int i = 0;
public void incre() {
lock.lock();//加锁
try {
i++;
} finally {
lock.unlock();
} } @Override
public void run() {
for(int j=0;j<1000000;j++) {
incre();
}
} public static void main(String[] args) throws InterruptedException {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
} }
2000000//线程安全

4、死锁

当两个线程同时等待对方释放同步监视器就会发生死锁,java虚拟机没有检测,也没有采取措施来处理死锁情况,所以多线程编程时应该采取措施避免死锁出现。

一旦出现死锁,程序不会发生任何异常情况,也没有任何提示,只是所有线程处于阻塞状态,无法继续

下面程序就是发生死锁

package threadtest;
/**
* 一个简单的死锁例子,大概的思路:两个线程A和B,两把锁X和Y,现在A先拿到锁X,然后sleep()一段时间,我们知道sleep()是不会释放锁资源的。然后如果这段时间线程B拿到锁Y,也sleep()一段时间的话,那么等到两个线程都醒过来的话,那么将互相等待对方释放锁资源而僵持下去,陷入死锁。flag的作用就是让A和B获得不同的锁。
* @author rdb
*
*/
public class ThreadTest implements Runnable{ Object o1 = new Object();
Object o2 = new Object();
private boolean flag = true ; @Override
public void run() {
if(flag) {
flag = false;
synchronized (o1) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o2) {
System.out.println("**************");
}
}
}else {
flag = true;
synchronized (o2) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (o1) {
System.out.println("**************");
}
}
}
} public static void main(String[] args) throws InterruptedException {
ThreadTest tt = new ThreadTest();
Thread t1 = new Thread(tt);
Thread t2 = new Thread(tt);
t1.start();
t2.start(); } }

java并发编程基础——线程同步的更多相关文章

  1. Java并发编程基础-线程安全问题及JMM(volatile)

    什么情况下应该使用多线程 : 线程出现的目的是什么?解决进程中多任务的实时性问题?其实简单来说,也就是解决“阻塞”的问题,阻塞的意思就是程序运行到某个函数或过程后等待某些事件发生而暂时停止 CPU 占 ...

  2. java并发编程:线程同步和锁

    一.锁的原理 java中每个对象都有一个内置锁.当程序运行到非静态的synchronized同步方法上时,自动获得与正在执行代码类的当前实例(this)有关的锁.获得一个对象的锁也称为获取锁,当程序运 ...

  3. java并发编程基础——线程通信

    线程通信 当线程在系统内运行时,程序通常无法准确的控制线程的轮换执行,但我们可以通过一些机制来保障线程的协调运行 一.传统的线程通信 传统的线程通信主要是通过Object类提供的wait(),noti ...

  4. java并发编程基础——线程的创建

    一.基础概念 1.进程和线程 进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程.(进程是资源分配的最小单位) 线程:同一类线程共享代码和数据 ...

  5. java并发编程基础——线程相关的类

    线程相关类 java还为线程安全提供了一些工具类. 一.ThreadLocal类(Thread Local Variable) ThreadLocal类,是线程局部变量的意思.功用非常简单,就是为每一 ...

  6. java并发编程基础——线程池

    线程池 由于启动一个线程要与操作系统交互,所以系统启动一个新的线程的成本是比较高的.在这种情况下,使用线程池可以很好的提升性能,特别是程序中涉及创建大量生命周期很短暂的线程时. 与数据库连接池类似,线 ...

  7. Java并发编程:线程的同步

    Java并发编程:线程的同步 */--> code {color: #FF0000} pre.src {background-color: #002b36; color: #839496;} J ...

  8. Java并发编程基础

    Java并发编程基础 1. 并发 1.1. 什么是并发? 并发是一种能并行运行多个程序或并行运行一个程序中多个部分的能力.如果程序中一个耗时的任务能以异步或并行的方式运行,那么整个程序的吞吐量和可交互 ...

  9. 并发-Java并发编程基础

    Java并发编程基础 并发 在计算机科学中,并发是指将一个程序,算法划分为若干个逻辑组成部分,这些部分可以以任何顺序进行执行,但与最终顺序执行的结果一致.并发可以在多核操作系统上显著的提高程序运行速度 ...

随机推荐

  1. MindInsight计算图可视设计

    MindInsight计算图可视设计 特性背景 计算图可视的功能,主要协助开发者在下面这些场景中使用. 开发者在编写深度学习神经网络的代码时,可以使用计算图的功能查看神经网络中算子的数据流走向,以及模 ...

  2. 稀疏性如何为AI推理增加难度

    稀疏性如何为AI推理增加难度 NVIDIA Ampere架构使数学运算加倍,以加速对各种神经网络的处理. 如果曾经玩过游戏Jenga,那么将有一些AI稀疏感. 玩家将木制积木交叉成一列.然后,每个玩家 ...

  3. 使用Auto TensorCore CodeGen优化Matmul

    使用Auto TensorCore CodeGen优化Matmul 本文将演示如何使用TVM Auto TensorCore CodeGen在Volta / Turing GPU上编写高性能matmu ...

  4. 【NX二次开发】Block UI 超级点

    属性说明 属性   类型   描述   常规           BlockID    String    控件ID    Enable    Logical    是否可操作    Group    ...

  5. 把axios获取到的数据渲染到列表上,使用better-scroll实现列表左右滑动

    问题:axios数据请求完后,页面是有数据的,即页面看到有数据,但是better-scroll却无法滚动 原因:这是因为在数据更新前,better-scroll已经渲染完成了 解决方法:这是个异步问题 ...

  6. 为什么Mongodb索引用B树,而Mysql用B+树?

    引言 好久没写文章了,今天回来重操旧业. 今天讲的这个主题,是<面试官:谈谈你对mysql索引的认识>,里头提到的一个坑. 也就是说,如果面试官问的是,为什么Mysql中Innodb的索引 ...

  7. noip2010 总结

    机器翻译 题目背景 小晨的电脑上安装了一个机器翻译软件,他经常用这个软件来翻译英语文章. 题目描述 这个翻译软件的原理很简单,它只是从头到尾,依次将每个英文单词用对应的中文含义来替换.对于每个英文单词 ...

  8. Vue实现点击复制文本内容(原生JS实现)

    需求: 实现点击订单编号复制内容 实现步骤: 这里我是在element 的table组件里实现的步骤,仅供参考,实际上实现思路都大同小异 首先在需要点击的地方,添加点击事件 <div class ...

  9. 16、编译安装ansible

    16.1.python版本说明: Ansible是一种批量部署工具,现在运维人员用的最多的三种开源集中化管理工具有:puppet,saltstack,ansible,各有各的优缺点, 其中saltst ...

  10. 21、部署heartbeat

    21.1.heartbeat部署规划: 本文的实验环境是虚拟机设备: 名称 接口 ip 用途 master-db(主) eth0 10.0.0.16/24 用于服务器之间的心跳连接(直连) eth1 ...