线程同步

一、线程安全问题

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

线程安全问题往往发生在多个线程调用同一方法或者操作同一变量,但是我们要知道其本质就是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. Flink实时计算pv、uv的几种方法

    本文首发于:Java大数据与数据仓库,Flink实时计算pv.uv的几种方法 实时统计pv.uv是再常见不过的大数据统计需求了,前面出过一篇SparkStreaming实时统计pv,uv的案例,这里用 ...

  2. 编译原理-文法(G)和语言(L)

    1.设文法G2(S): S->AB A->aA|a B->bB|b G2(S)产生的语言是什么? 解:L(G2)={ambn|m,n≥1} 2.请给出产生语言为{anbn|n≥1}的 ...

  3. 十二、iptables基本管理

    关闭firewalld,启动iptables服务 [root@proxy ~]# systemctl stop firewalld.service     //关闭firewalld服务器 [root ...

  4. NX二次开发-曲线或边分析函数

    UF_EVAL_is_arc   判断是圆形曲线或边UF_EVAL_ask_arc 圆形曲线或边分析,得到曲线或边的信息 类似的函数还有以下这些: UF_EVAL_is_ellipse // 椭圆UF ...

  5. Reactor3 中文文档(用户手册)

    文章很长,建议收藏起来,慢慢读! 疯狂创客圈为小伙伴奉上以下珍贵的学习资源: 疯狂创客圈 经典图书 : <Netty Zookeeper Redis 高并发实战> 面试必备 + 大厂必备 ...

  6. DG duplicate报错:RMAN-05001:auxiliary file name /u01/app/oracle/oradata/fratbs01.dbf conflicts with a file used by the target database

    问题:rman duplicate时报错: RMAN-05001:auxiliary file name /u01/app/oracle/oradata/fratbs01.dbf conflicts ...

  7. 为你的Go应用创建轻量级Docker镜像?

    缩小Go二进制文件大小 环境 youmen@youmendeMacBook-Pro % gcc -dumpversion 12.0.5 youmen@youmendeMacBook-Pro % go ...

  8. 微信订阅号中获取openid以及个人信息

    采用的方式是利用另一个服务号获取用户信息. 其中有一个问题就是不关注这个服务号获取不了用户头像等信息.

  9. 34.qt quick-Popup弹出窗口自定义

    1.Popup介绍 Popup是一个弹出窗口的控件它的常用属性如下所示: anchors.centerIn : Object,用来设置居中在谁窗口中. closePolicy : enumeratio ...

  10. external-attacher源码分析(1)-main方法与启动参数分析

    更多 ceph-csi 其他源码分析,请查看下面这篇博文:kubernetes ceph-csi分析目录导航 摘要 ceph-csi分析-external-attacher源码分析.external- ...