一个程序在运行起来的时候会转换成进程,通常含有多个线程。

  通常情况下,一个进程中的比较耗时的操作(如长循环、文件上传下载、网络资源获取等),往往会采用多线程来解决。

比如显示生活中,银行取钱问题、火车票多个售票窗口的问题,通常会涉及到并发的问题,从而需要多线程的技术。

  当进程中有多个并发线程进入一个重要数据的代码块时,在修改数据的过程中,很有可能引发线程安全问题,从而造成数据异常。例如,正常逻辑下,同一个编号的火车票只能售出一次,却由于线程安全问题而被多次售出,从而引起实际业务异常。

现在我们就以售票问题来演示线程安全的问题

1, 在不对多线程数据进行保护的情况下会引发的状况

public class ThreadUnSecurity {

    static int tickets = 10;

    class SellTickets implements Runnable{

        @Override
public void run() {
// 未加同步时产生脏数据
while(tickets > 0) { System.out.println(Thread.currentThread().getName()+"--->售出第: "+tickets+" 票");
tickets--; try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} } if (tickets <= 0) { System.out.println(Thread.currentThread().getName()+"--->售票结束!");
}
}
} public static void main(String[] args) { SellTickets sell = new ThreadUnSecurity().new SellTickets(); Thread thread1 = new Thread(sell, "1号窗口");
Thread thread2 = new Thread(sell, "2号窗口");
Thread thread3 = new Thread(sell, "3号窗口");
Thread thread4 = new Thread(sell, "4号窗口"); thread1.start();
thread2.start();
thread3.start();
thread4.start(); } }

上述代码运行的结果:

1号窗口--->售出第:  10 票
3号窗口--->售出第: 10 票
2号窗口--->售出第: 10 票
4号窗口--->售出第: 10 票
2号窗口--->售出第: 6 票
1号窗口--->售出第: 5 票
3号窗口--->售出第: 4 票
4号窗口--->售出第: 3 票
2号窗口--->售出第: 2 票
4号窗口--->售出第: 1 票
1号窗口--->售出第: 1 票
3号窗口--->售票结束!
2号窗口--->售票结束!
1号窗口--->售票结束!
4号窗口--->售票结束!

我们可以看出同一张票在不对票数进行保护时会出现同一张票会被出售多次!由于线程调度中的不确定性,读者在演示上述代码时,出现的运行结果会有不同。

第一种实现线程安全的方式

  同步代码块

package com.bpan.spring.beans.thread;

import com.sun.org.apache.regexp.internal.recompile;

public class ThreadSynchronizedSecurity {

    static int tickets = 10;

    class SellTickets implements Runnable{

        @Override
public void run() {
// 同步代码块
while(tickets > 0) { synchronized (this) { // System.out.println(this.getClass().getName().toString()); if (tickets <= 0) { return;
} System.out.println(Thread.currentThread().getName()+"--->售出第: "+tickets+" 票");
tickets--; try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
} if (tickets <= 0) { System.out.println(Thread.currentThread().getName()+"--->售票结束!");
}
}
}
} public static void main(String[] args) { SellTickets sell = new ThreadSynchronizedSecurity().new SellTickets(); Thread thread1 = new Thread(sell, "1号窗口");
Thread thread2 = new Thread(sell, "2号窗口");
Thread thread3 = new Thread(sell, "3号窗口");
Thread thread4 = new Thread(sell, "4号窗口"); thread1.start();
thread2.start();
thread3.start();
thread4.start(); } }

输出结果读者可自行调试,不会出现同一张票被出售多次的情况。

第二种 方式

  同步方法

package com.bpan.spring.beans.thread;

public class ThreadSynchroniazedMethodSecurity {

    static int tickets = 10;

    class SellTickets implements Runnable{

        @Override
public void run() {
//同步方法
while (tickets > 0) { synMethod(); try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} if (tickets<=0) { System.out.println(Thread.currentThread().getName()+"--->售票结束");
} } } synchronized void synMethod() { synchronized (this) {
if (tickets <=0) { return;
} System.out.println(Thread.currentThread().getName()+"---->售出第 "+tickets+" 票 ");
tickets-- ;
} } }
public static void main(String[] args) { SellTickets sell = new ThreadSynchroniazedMethodSecurity().new SellTickets(); Thread thread1 = new Thread(sell, "1号窗口");
Thread thread2 = new Thread(sell, "2号窗口");
Thread thread3 = new Thread(sell, "3号窗口");
Thread thread4 = new Thread(sell, "4号窗口"); thread1.start();
thread2.start();
thread3.start();
thread4.start(); } }

读者可自行调试上述代码的运行结果

第三种 方式

  Lock锁机制, 通过创建Lock对象,采用lock()加锁,unlock()解锁,来保护指定的代码块

package com.bpan.spring.beans.thread;

import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock; public class ThreadLockSecurity { static int tickets = 10; class SellTickets implements Runnable{ Lock lock = new ReentrantLock(); @Override
public void run() {
// Lock锁机制
while(tickets > 0) { try {
lock.lock(); if (tickets <= 0) { return;
} System.out.println(Thread.currentThread().getName()+"--->售出第: "+tickets+" 票");
tickets--;
} catch (Exception e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}finally { lock.unlock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} if (tickets <= 0) { System.out.println(Thread.currentThread().getName()+"--->售票结束!");
} }
} public static void main(String[] args) { SellTickets sell = new ThreadLockSecurity().new SellTickets(); Thread thread1 = new Thread(sell, "1号窗口");
Thread thread2 = new Thread(sell, "2号窗口");
Thread thread3 = new Thread(sell, "3号窗口");
Thread thread4 = new Thread(sell, "4号窗口"); thread1.start();
thread2.start();
thread3.start();
thread4.start(); } }

 最后总结:

  由于synchronized是在JVM层面实现的,因此系统可以监控锁的释放与否;而ReentrantLock是使用代码实现的,系统无法自动释放锁,需要在代码中的finally子句中显式释放锁lock.unlock()。

  另外,在并发量比较小的情况下,使用synchronized是个不错的选择;但是在并发量比较高的情况下,其性能下降会很严重,此时ReentrantLock是个不错的方案。

 补充:  

  在使用synchronized 代码块时,可以与wait()、notify()、nitifyAll()一起使用,从而进一步实现线程的通信。
其中,wait()方法会释放占有的对象锁,当前线程进入等待池,释放cpu,而其他正在等待的线程即可抢占此锁,获得锁的线程即可运行程序;线程的sleep()方法则表示,当前线程会休眠一段时间,休眠期间,会暂时释放cpu,但并不释放对象锁,也就是说,在休眠期间,其他线程依然无法进入被同步保护的代码内部,当前线程休眠结束时,会重新获得cpu执行权,从而执行被同步保护的代码。
wait()和sleep()最大的不同在于wait()会释放对象锁,而sleep()不会释放对象锁。

  notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,才会释放对象锁。JVM会在等待的线程中调度一个线程去获得对象锁,执行代码。

  需要注意的是,wait()和notify()必须在synchronized代码块中调用。

  notifyAll()是唤醒所有等待的线程。

下面是示例代码,

package com.bpan.spring.beans.thread;

public class ThreadDemo {

    static final Object obj = new Object();

    //第一个子线程
static class ThreadA implements Runnable{ @Override
public void run() { int count = 10;
while(count > 0) { synchronized (ThreadDemo.obj) { System.out.println("A-----"+count);
count--; synchronized (ThreadDemo.obj) { //notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。
//调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,
ThreadDemo.obj.notify(); try {
ThreadDemo.obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
} } } static class ThreadB implements Runnable{ @Override
public void run() { int count = 10; while(count > 0) { synchronized (ThreadDemo.obj) {
System.out.println("B-----"+count);
count--; synchronized (ThreadDemo.obj) { //notify()方法会唤醒因为调用对象的wait()而处于等待状态的线程,从而使得该线程有机会获取对象锁。
//调用notify()后,当前线程并不会立即释放锁,而是继续执行当前代码,直到synchronized中的代码全部执行完毕,
ThreadDemo.obj.notify(); try {
ThreadDemo.obj.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} } } } } public static void main(String[] args) { new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start(); } }

参考地址:https://www.cnblogs.com/lizhangyong/p/8029287.html

Java 实现线程安全的三种方式的更多相关文章

  1. java核心知识点学习----创建线程的第三种方式Callable和Future CompletionService

    前面已经指出通过实现Runnable时,Thread类的作用就是将run()方法包装成线程执行体,那么是否可以直接把任意方法都包装成线程执行体呢?Java目前不行,但其模仿者C#中是可以的. Call ...

  2. java核心知识点----创建线程的第三种方式 Callable 和 Future CompletionService

    前面已经指出通过实现Runnable时,Thread类的作用就是将run()方法包装成线程执行体,那么是否可以直接把任意方法都包装成线程执行体呢?Java目前不行,但其模仿者C#中是可以的. Call ...

  3. JAVA - 启动线程有哪几种方式

    JAVA - 启动线程有哪几种方式 一.继承Thread类创建线程类 (1)定义Thread类的子类,并重写该类的run方法,该run方法的方法体就代表了线程要完成的任务.因此把run()方法称为执行 ...

  4. IOS 多线程,线程同步的三种方式

    本文主要是讲述 IOS 多线程,线程同步的三种方式,更多IOS技术知识,请登陆疯狂软件教育官网. 一般情况下我们使用线程,在多个线程共同访问同一块资源.为保护线程资源的安全和线程访问的正确性. 在IO ...

  5. java 实现md5加密的三种方式与解密

      java 实现md5加密的三种方式 CreateTime--2018年5月31日15点04分 Author:Marydon 一.解密 说明:截止文章发布,Java没有实现解密,但是已有网站可以免费 ...

  6. java中遍历集合的三种方式

    第一种遍历集合的方式:将集合变为数组 package com.lw.List; import java.util.ArrayList; import java.util.List; import ja ...

  7. java加载配置文件的三种方式

    比如我们要加载db.properties文件 如图: 比如我们要加载source目录下的db.properties文件.就有以下几种方式 第一种是文件io流: public static void l ...

  8. JAVA实现Base64编码的三种方式

    摘要: Javabase64编码的三种方式   有如下三种方式: 方式一:commons-codec.jar Java代码  1. String base64String="whuang12 ...

  9. Java通过JDBC连接数据库的三种方式!!!并对数据库实现增删改查

    前言 java连接数据库完整流程为: 1,获得驱动(driver),数据库连接(url),用户名(username),密码(password)基本信息的三种方式. 2,通过获得的信息完成JDBC实现连 ...

随机推荐

  1. Python内置的字符串处理函数

    生成字符串变量 str='python String function'   字符串长度获取:len(str) 例:print '%s length=%d' % (str,len(str)) 连接字符 ...

  2. CSS3:box-sizing:不再为盒子模型而烦恼

    题外话: W3C奉行的标准,就是content-box,就是须要计算边框,填充还有内容的;可是就我个人而言, 比較喜欢的是传统IE6时候的怪异模式,不用考虑容器是否会被撑开(打乱布局); 盒子模型差异 ...

  3. GDI+学习之------色彩与图像

    色彩 在GDI+中.色彩是通过Color类来描写叙述的.不是用RGB类.用RGB构造会出错.GDI+中的色彩信息值是由一个32位的数据来表示的,它包含8位alpha值和各8位的R.G.B值,对于alp ...

  4. Ubuntu下终端Vim编写C语言程序 AAAAA

    我是开虚拟机下的Ubuntu,装双系统又卸了,Ubuntu默认是不包含编辑器vim和编译器gcc.如果你是刚安装好的Ubuntu电脑,下面我们将来实现自己的第一个程序. 1.准备工作 首先进入root ...

  5. P1155 双栈排序(二分图染色)

    P1155 双栈排序(二分图染色) 题目描述 Tom最近在研究一个有趣的排序问题.如图所示,通过2个栈S1和S2,Tom希望借助以下4种操作实现将输入序列升序排序. 操作a 如果输入序列不为空,将第一 ...

  6. hdu 2222(AC自动机模版题)

    Keywords Search Time Limit: 2000/1000 MS (Java/Others)    Memory Limit: 131072/131072 K (Java/Others ...

  7. 线性回归(最小二乘法、批量梯度下降法、随机梯度下降法、局部加权线性回归) C++

    We turn next to the task of finding a weight vector w which minimizes the chosen function E(w). Beca ...

  8. (Go)11.九九乘法表示例

    //九九乘法表 package main import ( "fmt" ) func chengfa() { ; m < ; m ++ { ; n <= m; n++ ...

  9. Labeling Balls(拓扑)

    http://poj.org/problem?id=3687 看题意看了半天没看懂怎么回事,看完Discuss彻底凌乱了..后来看了题解才懂,就是逆向建图+拓扑排序,建图时要判重边. #include ...

  10. 原生JS---5

    原生js学习笔记5——BOM操作 什么是BOM BOM:Browser Object Model 是浏览器对象模型,浏览器对象模型提供了独立与内容的.可以与浏览器窗口进行互动的对象结构,BOM由多个对 ...