一个小总结

Synchronized与同步块的形象比喻:

我们以去商店买衣服为比喻:synchrnized锁方法就好比去一家商店买衣服,一次只能进一个人,买完出来才能进第二个人。而同步块则是在整个买衣服流程的关键之处:试衣间换衣服,结账(假设只有一个试衣间,只有一个收银台)时做了排队处理,排队使得数据不会错乱同步块锁的就是临界资源(试衣间、收银台)。

概念

关键字synchronized可以写在方法和代码块中

  • 写在普通方法中:锁住的对象是this,即类的实例。也就是说锁住的是类下面的类变量(成员变量),而不是方法中的变量。
  • 写在静态方法中:锁住的对象时class
  • 写在代码块中,只锁住代码块中的内容

关于这个synchronized关键字

  • 线程锁会造成性能下降
  • 线程锁用在大的方法中,很影响性能

关于线程锁

  • 除了使用synchronized关键字外,还可以使用另一种线程锁,本文没有收录方法

写在方法声明中:synchronized锁对象

案例1

下面来看一个没有加线程锁的案例:3个线程抢票

package _20191205;
/**
* 线程不安全:
* @author TEDU
*/
public class SynTest01 {
public static void main(String[] args) {
//一份资源
SafeWeb12306 web = new SafeWeb12306();
new Thread(web,"线程1").start();
new Thread(web,"线程2").start();
new Thread(web,"线程3").start();
}
} class SafeWeb12306 implements Runnable{
//票数
private int ticketNums = 100;
private boolean flag = true; @Override
public void run() {
while(flag) {
try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
buy();
}
} //买票:线程不安全
public void buy() {
if(ticketNums<=0) {
flag = false;
return;
}
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);
}
}

  

它的运行结果:

会出现多个线程抢了同一张票的情况,为了避免这种情况,我们需要给关键方法加锁,在本例中,我们只需要给buy()方法加锁即可,即:

public void synchronized buy(){...}

  

注意的地方

注意我们只new了一个资源的实例,当三个线程对它的成员变量进行操作时,才能使用synchronized对这个实例进行线程保护,锁住这个实例的成员变量。
     SafeWeb12306 web = new SafeWeb12306();
new Thread(web,"线程1").start();
new Thread(web,"线程2").start();
new Thread(web,"线程3").start();

  

案例2 这个例子就不要看了

两个人都持有同一个账户的银行卡。

现两人同时使用两台ATM机取款,要保证线程安全,就要在增加和减少账户余额的方法中加入sychronized关键字

package _20191205;

import java.util.Scanner;

/**
* synchronized案例
* @author TEDU
*模拟两个人从取款机取同一个账户
*人类,取款机类,银行账户类,账户数据库类
*线程锁锁的是账户数据库类,也就是锁在这个类的取款与存款方法
*/
public class synTest02 {//测试类
public static void main(String[] args) {
DataBase db = new DataBase();//生成一个账户的数据库(相当于一张卡)
Man m1 = new Man("小明",db);//两个人都持有这张卡
Man m2 = new Man("小李",db);//两个人都持有这张卡
new Thread(m1,"人1").start();;
new Thread(m1,"人2").start(); }
} class Man implements Runnable{
private String name;
private ATM atm;
private DataBase db;
public Man(String name,DataBase db) {
this.name = name;
this.db = db;
}
//与ATM交互
public void operateATM() {
System.out.println(name+"正在操作ATM机,掏出准备好的小纸条:账号111,密码222");
atm = new ATM(db);
atm.logIn();
System.out.println(name+"已经操作完ATM机");
}
@Override
public void run() {
// TODO Auto-generated method stub
operateATM();
} } class ATM {
private DataBase db;
public ATM(DataBase db) {
this.db = db;
}
Account account;
//查询余额方法
private void inquire() {
account.doMath("inquire");
}
//取款方法
private void withDraw() {//传入要取的数量
int num;
Scanner scan = new Scanner(System.in);
System.out.println("请输入取款金额:");
num = scan.nextInt();
account.doMath("sub",num); }
//存款方法
private void save() {
int num;
Scanner scan = new Scanner(System.in);
System.out.println("请假装整理好您的钞票,假装放进取款机!");
num = scan.nextInt();
System.out.println("存入中,请稍后......");
account.doMath("add",num);
System.out.println("存入成功!");
}
public void logIn() {//登入方法
int command;//用于接收用户命令
System.out.println("======欢迎使用口袋银行ATM系统======");
Scanner scan = new Scanner(System.in);
System.out.println("请输入账号:");
String acc = scan.nextLine();
System.out.println("请输入密码:");
String psd = scan.nextLine();
account = new Account(acc,psd,db);
//判断账户与密码是否存在
if(account.compare()) {
//账户密码均正确
System.out.println("登入成功!");
do {
System.out.println("请选择您的操作:1.查询余额 2.取款 3.存款 0.退出");
command = scan.nextInt();
switch(command) {
case 0:
break;
case 1:
//查询余额方法
inquire();
break;
case 2:
//调用取款方法
withDraw();
break;
case 3:
//调用存款方法
save();
break;
default:
System.out.println("输入错误!");
break;
}
}while(command!=0);
}else {
System.out.println("账户或密码错误");
}
}
} class Account{
private DataBase db;
private String accountName;
private String passwd;
public Account(String accountName,String psd,DataBase db) {
this.accountName = accountName;
this.passwd = psd;
this.db = db;
} //判断用户输入的账号密码是否正确
public boolean compare() {
if(db.getAccountName().contentEquals(accountName)&&db.getPasswd().contentEquals(passwd)){
return true;
}else {
return false;
} }
//账户余额做计算
public void doMath(String act,int num) {
if(act.equals("add")) {
//账户添加余额
db.addBalance(num);
}
if(act.equals("sub")) {
//账户减少余额
db.subBalance(num);
}
} public void doMath(String act) {
if(act.equals("inquire")) {
System.out.println("您的账户余额为:¥"+db.getBalance());
}
}
}
//本数据库只存了一个账户的信息
class DataBase{
private String accountName = "111";
private String passwd = "222";
private int balance = 10000;//账户余额1w
//增加余额方法
public synchronized void addBalance(int num) {
this.balance += num;
}
//减少余额
public synchronized void subBalance(int num) {
if(num>balance) {
System.out.println("余额不足");
}else {
this.balance -= num;
System.out.println("出钞中,请稍后......");
System.out.println("请尽快取走钞票!");
}
} public int getBalance() {
return balance;
} public String getAccountName() {
return accountName;
} public String getPasswd() {
return passwd;
}
}

  

运行结果

小明正在操作ATM机,掏出准备好的小纸条:账号111,密码222
小明正在操作ATM机,掏出准备好的小纸条:账号111,密码222
======欢迎使用口袋银行ATM系统======
======欢迎使用口袋银行ATM系统======
请输入账号:
请输入账号:
111
111
请输入密码:
请输入密码:
222
222
登入成功!
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出
登入成功!
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出
1
1
您的账户余额为:¥10000
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出
您的账户余额为:¥10000
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出
2
2
请输入取款金额:
请输入取款金额:
8000
8000
出钞中,请稍后......
请尽快取走钞票!
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出
余额不足
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出
1
1
您的账户余额为:¥2000
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出
您的账户余额为:¥2000
请选择您的操作:1.查询余额 2.取款 3.存款 0.退出

  

写在代码块中:synchronized锁(同步块)案例

synchronized块的优点为:更细致的对需要线程同步的部分进行加锁,优化代码,使性能提高。

格式:

synchronized (obj) {...} //obj为监视器,即要锁住的对象,注意是对象,不是属性

  

注意:

  • 无论是基础数据类型还是引用数据类型,如果它的改变会引起线程不安全,那它们都要加线程锁
  • 要锁住的是对象,注意是对象(引用类型),不是属性(基础类型)
  • 被锁的对象可以是this(如果有多个被修改的对象时)
  • synchronized关键字要与被锁的对象被修改的地方尽可能近
  • synchronized块只能锁一个对象,如果需要需要

案例1

1w个并发线程向一个ArrayList中添加自己的线程名,最后看看这个容器的大小是不是1w。

注意:

  • 重点在这里,synchronized代码要与被锁的对象尽可能近
  • 被锁的对象可以是this
package _20191205;
import java.util.List;
import java.util.ArrayList;
public class SynBlockTest02 {
public static void main(String[] args) {
List<String> list = new ArrayList<>(); for(int i = 0;i < 10000;i++ ) { new Thread(()->{
        
synchronized (list) {//重点在这里,synchronized代码要与被锁的对象使用的地方尽可能近
list.add(Thread.currentThread().getName().toString());
}
},"线程"+i).start();
} try {
Thread.sleep(8000);//这里休眠8s是因为我们for循环里的线程与main线程是并发的,如果不写休眠,可能main就很快把容器的容量输出来了,就得不到正确的结果
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(list.size());
// for(String str : list) {
// System.out.println(str);
// }
}
}

  

案例2 三个线程抢100张票

本例的重点在于,需要保证线程安全的对象时基础数据类型,而且不止一个基础数据类型被修改,则在这个线程安全中obj为this,代表这两个基础数据类型所在的类的对象。

package _20191205;
/**
* 线程安全:在并发时保证数据的正确性、效率尽可能的高
* synchronized锁方法案例
* @author TEDU
*
*/
public class SynTest01 {
public static void main(String[] args) {
//一份资源,注意我们只new了一个资源的实例,当三个线程对它进行操作时,才能使用synchronized对他锁住资源线程保护
SafeWeb12306 web = new SafeWeb12306();
new Thread(web,"线程1").start();
new Thread(web,"线程2").start();
new Thread(web,"线程3").start();
}
} class SafeWeb12306 implements Runnable{
//票数
private int ticketNums = 100;
private boolean flag = true; @Override
public void run() {
while(flag) {
try{
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
buy2();
}
}
//买票:线程安全的买 通过synchronized块
public void buy2() {

          //这里再写一遍是因为代码优化,没有票就没有必要等了,考虑的是没有票的情况
          if(ticketNums<=0) {
          flag = false;
          return;
          }

		//必须涵盖所有会被修改的地方,这里会被修改的地方即flag与ticketNums
synchronized(this) {//由于ticketNums是属性是基础数据类型,不是引用类型,所以直接用this,表示ticketNums所在的类
if(ticketNums<=0) { //这里写,是考虑只有一张票的情况(三个线程都读到了这张票)
flag = false;//被修改的地方1
return;
}
try {
Thread.sleep(100);
}catch(InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);//被修改的地方2
}
}
}

  

34 多线程(六)——线程安全 synchronized的更多相关文章

  1. Java多线程(六) —— 线程并发库之并发容器

    参考文献: http://www.blogjava.net/xylz/archive/2010/07/19/326527.html 一.ConcurrentMap API 从这一节开始正式进入并发容器 ...

  2. java多线程:线程同步synchronized(不同步的问题、队列与锁),死锁的产生和解决

    0.不同步的问题 并发的线程不安全问题: 多个线程同时操作同一个对象,如果控制不好,就会产生问题,叫做线程不安全. 我们来看三个比较经典的案例来说明线程不安全的问题. 0.1 订票问题 例如前面说过的 ...

  3. 【多线程】线程同步 synchronized

    由于同一进程的多个线程共享同一块存储空间 , 在带来方便的同时,也带来了访问 冲突问题 , 为了保证数据在方法中被访问时的正确性 , 在访问时加入 锁机制synchronized , 当一个线程获得对 ...

  4. Java多线程(六) 线程系列总结

    多线程系列终于终结得差不多,本人对该系列所做的总结大致如下: 线程锁模块耗费了大量的时间,底层的AQS实现比较复杂.仍然没有时间总结源码部分,能够坚持写下这么几个篇幅的内容真心佩服自己....希望继续 ...

  5. java 多线程:线程安全问题synchronized关键字解决

    背景: 多个线程同时修改一个变量时,有概率导致两次修改其中某些次被覆盖. 例如:如下案例一个变量值为3,三个线程同时对其-1,如果按顺序执行,每次减完的结果应该是2,1,0.但实际运行中有可能变为0, ...

  6. Java多线程父子线程关系 多线程中篇(六)

    有的时候对于Java多线程,我们会听到“父线程.子线程”的概念. 严格的说,Java中不存在实质上的父子关系 没有方法可以获取一个线程的父线程,也没有方法可以获取一个线程所有的子线程 子线程的消亡与父 ...

  7. 处理java多线程时线程安全问题 - ThreadLocal和Synchronized

    多线程在自动化测试中用的不多,也就是说我们用单线程可以完成大部分的自动化测试脚本. 主要有两个原因,首先是因为自动化测试首要考虑的是脚本的稳定性,所以一般会牺牲效率以保证脚本稳定,其次是由于局限于我们 ...

  8. C#多线程之线程同步篇2

    在上一篇C#多线程之线程同步篇1中,我们主要学习了执行基本的原子操作.使用Mutex构造以及SemaphoreSlim构造,在这一篇中我们主要学习如何使用AutoResetEvent构造.Manual ...

  9. JAVA多线程之线程间的通信方式

    (转发) 收藏 记 周日,北京的天阳光明媚,9月,北京的秋格外肃穆透彻,望望窗外的湛蓝的天,心似透过栏杆,沐浴在这透亮清澈的蓝天里,那朵朵白云如同一朵棉絮,心意畅想....思绪外扬, 鱼和熊掌不可兼得 ...

  10. Java多线程与线程同步

    六.多线程,线程,同步 ①概念: 并行:指两个或多个在时间同一时刻发生(同时发生) 并发:指两个或多个事件在同一时间段内发生 具体概念: 在操作系统中,安装了多个程序,并发指的是在一段时间内宏观上有多 ...

随机推荐

  1. Redis BGSAVE因为内存不足 fork 失败导致目标 Redis 无法访问的问题

    中秋的时候正在外面愉快的在外卖喝着咖啡玩电脑......突发 redis 报警从 sentry 应用端曝出的错误 MISCONF Redis is configured to save RDB sna ...

  2. Kafka 消费者到底是什么 以及消费者位移主题到底是什么(Python 客户端 1.01 broker)

    Kafka 中有这样一个概念消费者组,所有我们去订阅 topic 和 topic 交互的一些操作我们都是通过消费者组去交互的. 在 consumer 端设置了消费者的名字之后,该客户端可以对多个 to ...

  3. 关于 Mercury_Lc 说明

    现在还主要在用 csdn 写博客,博客地址:https://blog.csdn.net/Mercury_Lc 这个是因为好奇,点了一下 一键搬家 ,就酱紫了. 主要更新,前往这个网址 https:// ...

  4. go的接口内部实现

    1 前言 1.1 Go汇编 Go语言被定义为一门系统编程语言,与C语言一样通过编译器生成可直接运行的二进制文件.这一点与Java,PHP,Python等编程语言存在很大的不同,这些语言都是运行在基于C ...

  5. shell 杀死80端口的所有进程

    netstat -lnp|grep |grep -v grep |awk

  6. beforeDestroy的使用

    beforeDestroy ---实例销毁之前调用 需求是这样的: important:下面截图数据都是测试数据 日期在我点击查询的时候要存储,刷新就读内存,但是我点击其他页面再进来的时候,这个内存要 ...

  7. OpenFOAM——梯形腔双边驱流

    本算例来自<ANSYS Fluid Dynamics Verification Manual>中的VMFL054: Laminar flow in a Trapezoidal Cavity ...

  8. Edusoho之Basic Authentication

    通过如下代码,可以正常请求并获取对应的数据: curl -X POST -H "Accept:application/vnd.edusoho.v2+json" -H "A ...

  9. 浅谈SOA面向服务化编程架构(dubbo)

      dubbo 是阿里系的技术.并非淘宝系的技术啦,淘宝系的分布式服务治理框架式HSF啦 ,只闻其声,不能见其物.而dubbo是阿里开源的一个SOA服务治理解决方案,dubbo本身 集成了监控中心,注 ...

  10. SpringBoot使用jasypt加解密密码

    在我们的服务中不可避免的需要使用到一些秘钥(数据库.redis等) 开发和测试环境还好,但生产如果采用明文配置讲会有安全问题,jasypt是一个通用的加解密库,我们可以使用它. <depende ...