线程同步

一、线程安全问题

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

线程安全问题往往发生在多个线程调用同一方法或者操作同一变量,但是我们要知道其本质就是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. NVIDIA GPU的神经网络自动调度

    NVIDIA GPU的神经网络自动调度 针对特定设备和工作负载的自动调整对于获得最佳性能至关重要.这是一个关于如何使用自动调度器为NVIDIA GPU调整整个神经网络的资料. 为了自动调整一个神经网络 ...

  2. AMD–7nm “Rome”芯片SOC体系结构,支持64核

    AMD–7nm "Rome"芯片SOC体系结构,支持64核 AMD Fully Discloses Zeppelin SOC Architecture Details at ISS ...

  3. dataguard日志损坏处理

    ===== 问题 ===== 日志损坏无法应用日志(开启MRP应用系统会因无法应用日志而关闭) Completed: ALTER DATABASE RECOVER MANAGED STANDBY DA ...

  4. 【NX二次开发】获取对象边界包容盒的三个函数UF_MODL_ask_bounding_box

    今天看到胡工对bounding_box的分享,我也来测试一番(原帖地址:https://www.ugapi.com/thread-10287.html) 获取对象的边界盒子的三个函数: 1 UF_MO ...

  5. 详解详解Java中static关键字和final关键字的功能

    摘要:static关键字和final关键字是Java语言的核心,深入理解他们的功能非常重要. 本文分享自华为云社区<Java: static关键字与final关键字>,原文作者:唐里 . ...

  6. ORACLE中的PL/SQL

    一. 1.过程,函数,触发器是pl/sql编写.                2. 过程函数触发器是在Oracle中.                      3.pl/sql是非常强大的数据库过 ...

  7. WIN10无法进行Android应用开发真机调试解决方案

    在WIN10操作系统进行ANDROID开发真机调试时,遇到的问题主要归纳一下有以下几点: 一.没有打开"USB调试"项.这点不再赘述: 二.没有安装ADB Interface驱动: ...

  8. MATLAB导入txt和excel文件技巧汇总:批量导入、单个导入

    在使用MATLAB的时候,想必各位一定会遇到导入数据的问题.如果需要导入的数据其数据量巨大的话,那么在MATLAB编辑器中将这些数据复制粘贴进来,显然会在编辑器中占据巨大的篇幅,这是不明智的. 一般来 ...

  9. 三、JavaSE语言基础之数据类型

    数据类型的分类   按照数据的复杂程度可分为引用数据类型与基本数据类型   引用数据类型的数据是对象(多值数据/复杂数据),引用数据类型的数据的名字叫做引用/引用名:   基本数据类型的数据是常量值( ...

  10. 10.8、mysql日志

    mysql生成或相关联的日志文件种类繁多,这里重点关注与mysql数据库服务相关 的几类日志文件: 1.错误日志: 记录mysql服务进程mysql的在启动/关闭/运行过程中遇到的错误信息: [mys ...