一、线程安全

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

  下面通过一个案例来演示线程的安全问题。

  模拟电影票买票的过程,其中,一共有100张票。下面来模拟电影票的售票窗口,实现多个窗口同时卖票,采用线程对象来模拟,通过实现 Runnable 接口子类来模拟。

  Demo:

 // 模拟票
public class Ticket implements Runnable {
private int ticket = 100;
/*
* 执行卖票操作
*/
@Override
public void run() {
//每个窗口卖票的操作
//窗口 永远开启
while (true) {
if (ticket > 0) {//有票 可以卖
//出票操作
//使用sleep模拟一下出票时间
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto‐generated catch block
e.printStackTrace();
}
//获取当前线程对象的名字
String name = Thread.currentThread().getName();
System.out.println(name + "正在卖:" + ticket+"张票");
            ticket--;
}
}
}
}
// 测试类
public class Demo {
public static void main(String[] args) {
//创建线程任务对象
Ticket ticket = new Ticket();
//创建三个窗口对象
Thread t1 = new Thread(ticket, "窗口1");
Thread t2 = new Thread(ticket, "窗口2");
Thread t3 = new Thread(ticket, "窗口3");
//同时卖票
t1.start();
t2.start();
t3.start();
}
}

  结果中有一部分这样现象:

  发现程序出现了两个问题

    ① 相同的票数,比如5这张票被卖了两次。

    ② 不存在的票,比如 0 和 -1票,是不存在的。

   为什么出现这样的情况呢?

   当只有一个窗口售票或多个窗口分别出售自己的票是没有问题的。但是当三个窗口,同时访问共享的资源,就会导致线程不同步,这种问题称为线程不安全。

  线程安全问题产生的原理:

  

  注意:线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量,静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

二、线程同步

   当使用过个线程访问统一资源的时候,且多个线程中对资源有些的操作,就容易出现线程安全问题。

   要解决上述多线程并发访问一个资源的安全性问题:也就是解决重复票与不存在票的问题。Java 中提供了同步机制(synchronized)来解决。

   以上面的售票案例来简述一下同步机制

 当窗口1线程进入操作的时候,窗口2和窗口3线程只能在外面等着,窗口1操作结束,窗口1、窗口2和窗口3才有机会进入代码去执行。
也就是说某个线程修改共享资源的时候,其他线程不能修改该资源,等待修改完毕之后,才能去抢夺CPU资源,完成对应的操作,保证了数据的同步性,解决了线程不安全的现象。

     同步原理图解

  为了保证每个线程都能正常执行原子操作,Java 引入线程同步机制,下面学习三种同步机制。

  1、同步代码块方法

    同步代码块:synchronized 关键字可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。

    语法格式

synchronized(同步锁){
需要同步操作的代码 / 可能会出现线程安全问题的代码(访问共享数据的代码)
}

    同步锁:对象的同步锁只是一个概念,可以想象为在对象上标记了一个锁。

   锁对象:又称为监视器对象,同一时刻,某一段代码,只允许一个线程允许,其他线程进不来。

   注意

     ① 代码块中的锁对象,可以使用任意的对象

     ② 必须保证多个线程使用的锁对象是同一个

     ③ 锁对象作用:把同步代码块锁住,只让一个线程在同步代码块中执行

     ④ 在任何时候,最多允许一个线程拥有同步锁,谁拿到锁就进入代码块,其他的线程只能在外等着 (Blocked)

   Demo : 使用同步代码块解决上面售票问题

   (1)使用 Thread 类实现

 class Ticket extends Thread{
private static int total = 100;
private static Object lock = new Object();//锁的选择之一,单独造一个锁对象 public Ticket(String name) {
super(name);
} public void run(){
// synchronized (this) {//这里使用this不行,因为这个this,对于三个线程来说不是同一个
while(true){
synchronized (lock) {
if(total > 0){
System.out.println(getName() + "卖出一张票");
total--;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}else{
break;
}
}
}
}
}

  (2)使用 Runnable接口 实现:方式一

 public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100; //创建一个锁对象
Object obj = new Object(); //设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//同步代码块
synchronized (obj){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} //票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
}
}
}
}

    方式二:使用 this 锁

 class Ticket implements Runnable{
private int total = 10; @Override
public void run() {
while(true){
synchronized (this) {//选择this当锁,可以,因为只有一个Ticket的对象
if(total>0){
System.out.println(Thread.currentThread().getName() +"卖出一张票");
total--;
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}else{
break;
}
}
}
} }

  2、同步方法

    同步方法:使用 synchronized 修饰的方法,就叫做 同步方法,保证一个线程执行该方法的时候,其他线程只能在方法外等着。

    语法格式

【修饰符】 synchronized 返回值类型 方法名(【参数列表】){
可能会产生线程安全问题的代码 / 可能会出现线程安全问题的代码(访问了共享数据的代码)
}

     注意:同步方法的锁对象,程序员无法选择:

      (1)对于非静态的方法,同步锁就是实现类对象,也就是 this。

      (2)对于静态方法,同步锁对象就是当前类的 Class 对象。

    Demo:使用同步方法解决售票问题。

    (1)使用 Thread 类实现

 class Ticket extends Thread{
private static int total = 100; public Ticket(String name) {
super(name);
} public void run(){
while(total>0){//程序停止的条件
saleOneTicket();
}
} //非静态方法的锁对象是this,这里使用this,不是合格的锁对象
//使用非静态方法,当前锁对象是 当前的 Class 对象
public synchronized static void saleOneTicket(){
if(total > 0){//线程安全问题的条件
System.out.println(Thread.currentThread().getName() + "卖出一张票");
total--;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩余:" + total);
}
}
}

    (2)使用 Runnable 接口实现

 public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100; //设置线程任务:卖票
@Override
public void run() {
System.out.println("this:"+this); //使用死循环,让卖票操作重复执行
while(true){
payTicketStatic();
}
} /*
定义一个同步方法
同步方法也会把方法内部的代码锁住
只让一个线程执行
同步方法的锁对象是谁?
就是实现类对象 new RunnableImpl()
也是就是this
*/
public /*synchronized*/ void payTicket(){
synchronized (this){ //这里可以使用 this 锁对象
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} //票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
} }
}

    还可以把同步方法声明为一个静态的方法:

    语法格式

public static synchronized void method(){
可能会产生线程安全问题的代码 / 可能会出现线程安全问题的代码(访问了共享数据的代码)
}

     注意:对于 static方法,锁对象就是使用当前方法所在类的字节码对象(类名.class)。

     Demo:使用 static 同步方法解决售票问题。

 public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private static int ticket = 100; //设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
payTicketStatic();
}
} /*
静态的同步方法
静态方法的锁对象是本类的class属性-->class文件对象(反射)
*/
public static /*synchronized*/ void payTicketStatic(){
synchronized (RunnableImpl.class){
//先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
} //票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
}
} } }

  3、Lock 锁

    java.util.concurrent.locks.Lock 机制提供了比 synchronized 代码块和 synchronized 方法更广泛的锁定操作,同步代码块/同步方法具有的功能Lock都有,除此之外更强大,更体现面向对象。

    Lock 锁也称同步锁,方法如下:

public void lock() :加同步锁。
public void unlock() :释放同步锁。

     使用步骤

      ① 在成员位置创建一个ReentrantLock对象(Lock接口的一个实现类)

      ② 在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁

      ③ 在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁

    Demo:

 public class RunnableImpl implements Runnable{
//定义一个多个线程共享的票源
private int ticket = 100; //1.在成员位置创建一个ReentrantLock对象
Lock l = new ReentrantLock(); //设置线程任务:卖票
@Override
public void run() {
//使用死循环,让卖票操作重复执行
while(true){
//2.在可能会出现安全问题的代码前调用Lock接口中的方法lock获取锁
l.lock(); //先判断票是否存在
if(ticket>0){
//提高安全问题出现的概率,让程序睡眠
try {
Thread.sleep(10);
//票存在,卖票 ticket--
System.out.println(Thread.currentThread().getName()+"-->正在卖第"+ticket+"张票");
ticket--;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//3.在可能会出现安全问题的代码后调用Lock接口中的方法unlock释放锁
l.unlock();//无论程序是否异常,都会把锁释放
}
}
}
}
}

三、死锁

  1、死锁概念

    死锁:死锁是指两个或两个以上的线程在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去。

   2、死锁的必要条件

    死锁的发生也必须具备一定的条件,死锁的发生必须具备以下四个必要条件

    1)互斥条件:指进程对所分配到的资源进行排它性使用,即在一段时间内某资源只由一个进程占用。如果此时还有其它进程请求资源,则请求者只能等待,直至占有资源的进程用毕释放。
    2)请求和保持条件:指进程已经保持至少一个资源,但又提出了新的资源请求,而该资源已被其它进程占有,此时请求进程阻塞,但又对自己已获得的其它资源保持不放。
    3)不剥夺条件:指进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放。
    4)环路等待条件:指在发生死锁时,必然存在一个进程——资源的环形链,即进程集合{P0,P1,P2,···,Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。

Java 之 线程安全(线程同步)的更多相关文章

  1. 第22章 java线程(2)-线程同步

    java线程(2)-线程同步 本节主要是在前面吃苹果的基础上发现问题,然后提出三种解决方式 1.线程不安全问题 什么叫线程不安全呢 即当多线程并发访问同一个资源对象的时候,可能出现不安全的问题 对于前 ...

  2. Java线程:线程的同步-同步方法

    Java线程:线程的同步-同步方法   线程的同步是保证多线程安全访问竞争资源的一种手段. 线程的同步是Java多线程编程的难点,往往开发者搞不清楚什么是竞争资源.什么时候需要考虑同步,怎么同步等等问 ...

  3. (转)Java线程:线程的同步与锁

      Java线程:线程的同步与锁       一.同步问题提出   线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏. 例如:两个线程ThreadA.ThreadB都操作同一个对象Fo ...

  4. -1-5 java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁 sleep()和wait()方法的区别 为什么wait(),notify(),notifyAll()等方法都定义在Object类中

     本文关键词: java 多线程 概念 进程 线程区别联系 java创建线程方式 线程组 线程池概念 线程安全 同步 同步代码块 Lock锁  sleep()和wait()方法的区别 为什么wait( ...

  5. Java:多线程,线程同步,同步锁(Lock)的使用(ReentrantLock、ReentrantReadWriteLock)

    关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨Lock对象. synchronize ...

  6. Java:多线程,线程同步,synchronized关键字的用法(同步代码块、非静态同步方法、静态同步方法)

    关于线程的同步,可以使用synchronized关键字,或者是使用JDK 5中提供的java.util.concurrent.lock包中的Lock对象.本文探讨synchronized关键字. sy ...

  7. Java并发编程,互斥同步和线程之间的协作

    互斥同步和线程之间的协作 互斥同步 Java 提供了两种锁机制来控制多个线程对共享资源的互斥访问,第一个是 JVM 实现的 synchronized,而另一个是 JDK 实现的 ReentrantLo ...

  8. Java线程和多线程(三)——线程安全和同步

    线程安全在Java中是一个很重要的课题.Java提供的多线程环境支持使用Java线程.我们都知道多线程共享一些对象实例的话,可能会在读取和更新共享数据的事后产生数据不一致问题. 线程安全 之所以会产生 ...

  9. Java多线程——线程之间的同步

    Java多线程——线程之间的同步 摘要:本文主要学习多线程之间是如何同步的,如何使用volatile关键字,如何使用synchronized修饰的同步代码块和同步方法解决线程安全问题. 部分内容来自以 ...

  10. Java基础学习笔记: 多线程,线程池,同步锁(Lock,synchronized )(Thread类,ExecutorService ,Future类)(卖火车票案例)

    多线程介绍 学习多线程之前,我们先要了解几个关于多线程有关的概念.进程:进程指正在运行的程序.确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能. 线 ...

随机推荐

  1. excel隔行选中内容如何操作

    查看log日志是站长经常要做的事,从日志中可以发现很多问题,spider最近有没来爬,爬了哪些url,哪些页面不存在了等等,这些都可以看得到.然后你要根据不同的情况采取相应的措施.ytkah喜欢把这些 ...

  2. 2014-2015 ACM-ICPC, Asia Tokyo Regional Contest

    2014-2015 ACM-ICPC, Asia Tokyo Regional Contest A B C D E F G H I J K O O O O   O O         A - Bit ...

  3. 学习-guava

    Guava Guava工程包含了若干被Google的 Java项目广泛依赖 的核心库 例如:集合 [collections] .缓存 [caching] .原生类型支持 [primitives sup ...

  4. Linux提高工作效率的命令

    find ./ -name 'laun*'|xargs grep 8881 在laun开头的文件内查找8881 find ./ -name 'laun*' find . -type f -mtime ...

  5. token的验证过程

    1.用户向服务器发送用户名和密码. 2.服务端收到请求,验证用户名和密码. 3.验证成功后,服务端会签发一个token,并将这个token发送到客户端. 4.客户端收到token后将token存储起来 ...

  6. 动态内存管理:malloc/free/new/delete/brk/mmap

    这是我去腾讯面试的时候遇到的一个问题——malloc()是如何申请内存的? c++ 内存获取和释放 new/delete,new[]/delete[] c 内存获取和释放 malloc/free, c ...

  7. 使用 udev 进行动态内核设备管理(转自suse文档)

    第 12 章使用 udev 进行动态内核设备管理¶ 目录 12.1. /dev 目录 12.2. 内核 uevents 和 udev 12.3. 驱动程序.内核模块和设备 12.4. 引导和启动设备设 ...

  8. 记C# 调用虹软人脸识别 那些坑

    上一个东家是从事安防行业的,致力于人工智能领域,有自主人脸识别.步态识别的算法.C++同事比较称职有什么问题都可以第一时间反馈,并得到合理的处理,封装的DLL 是基于更高性能的GPU算法,可支持更多线 ...

  9. 自动签发https证书工具 cert manager

    最近cert manager进行升级,不再支持0.11以下的版本了,所以进行升级.但是发现不能直接通过更改镜像版本来升级,在Apps里的版本也是旧版本,部署后发现不支持,于是自已动手,根据文档整理了一 ...

  10. 【技术博客】利用Python将markdown文档转为html文档

    利用Python将markdown文档转为html文档 v1.0 作者:FZK 元素简单的md文件 Python中自带有一个markdown库,你可以直接这样使用 md_file = open(&qu ...