一个小总结

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. 实训作业6 (数据I/O)

    1. 文件输出流的应用. 定义如下字符串: String str = “12345abcdef@#%&*软件工程”; 编写程序将该字符串写入文件”data.txt”. import java. ...

  2. 微信小程序基本语法

    渲染 .js page ({ data :{ memo:'hello world' } }) .wxml <view>{{memo}}</view> 绑定id .js page ...

  3. Codeforces 526C.Om Nom and Candies

    题目描述 一个只有两个物品的背包问题,但是范围都是1e9,需要考虑根号或者log的复杂度. 如果这两个物品中的某一个花费超过了根号C,那么我们可以直接枚举这件物品的数量,另一件物品的数量可以计算得出. ...

  4. 洛谷 P5506 封锁

    目录 题目 思路 \(Code\) 题目 P5506 封锁 思路 模拟 \(\large\text{读题一定要细心}\) 解释都在代码里. \(Code\) #include<bits/stdc ...

  5. Automatic Annotation of Airborne Images by Label Propagation Based on a Bayesian-CRF Model

    贝叶斯+全连接条件场,无人机和航片数据,通过标注航片数据自动生成无人机标注数据,具体不懂

  6. Codeforces - 1264C - Beautiful Mirrors with queries - 概率期望dp

    一道挺难的概率期望dp,花了很长时间才学会div2的E怎么做,但这道题是另一种设法. https://codeforces.com/contest/1264/problem/C 要设为 \(dp_i\ ...

  7. ELK平台搭建及日志监控

    一.使用背景 当生产环境有很多服务器.很多业务模块的日志需要每时每刻查看时 二.环境 系统:centos 6.5 JDK:1.8 Elasticsearch-5.0.0 Logstash-5.0.0 ...

  8. Ajax:修改了项目的ajax相关代码,点击运行没有效果

    在运行ajax代码的时候发现这个问题,无论是重启浏览器还是IDE依旧不能解决. 原因: 浏览器调试的缓存 措施: 浏览器启用开发者模式,以Chrome.IDEA为例 1.浏览器设置disable ca ...

  9. 第06组 Alpha冲刺(1/4)

    队名:福大帮 组长博客链接:https://www.cnblogs.com/mhq-mhq/p/11863075.html 作业博客 :https://edu.cnblogs.com/campus/f ...

  10. semi-join子查询优化 -- FirstMatch策略

    FirstMatch执行semi-join子查询的一种策略. 类似于MySQL 5.x中如何执行in.exists子查询. 让我们以搜索拥有大城市的国家为例: select * from Countr ...