文章目录

  • 前言
    • 1. 什么是块,分为几种
    • 2. 静态块与构造块的区别
  • 一、 举例说明:并发情况下,线程不安全
    • 1. 示例1:unsafe12306取票
    • 2. 示例2:unsafe银行取钱
  • 二、线程不安全解密:内存可见性
    • 1. java内存模型
    • 2. 解释线程不安全
    • 3. 线程安全与不安全
  • 三、线程同步
    • 1. 概念
    • 2. Synchronized 块和方法
      • 1) 同步方法:safe12306买票
      • 3) 同步块:safe银行取钱
  • 四、synchronized 的范围问题,优化效率
    • 1. 同步块、同步方法:12306 取票,double checking
    • 2. 同步块:快乐影院 ,买几张票
    • 3. 同步块:快乐影院:可选座位
    • 4. 同步方法:
  • 五、简单了解锁机制
    • 1. 死锁案例

前言

首先整理了一下在java中 的概念,然后整理了一下 线程安全不安全 的示例,为了理解线程不安全,进而学习了 java内存模型 引出的 内存可见性 问题。

1. 什么是块,分为几种

花括号写上代码,一般称为块
加上 synchronized 的就为同步块。

java中 块有四种:

  1. 方法里面的块:局部快
    作用: 解决变量作用域
  2. 类中块,和属性同级,方法外,:构造块
    作用:初始化对象信息的。
  3. 在类中块上加个static ,称为 静态块
  4. 同步块,称为同步监视器。
    在方法里面,解决我们线程安全的问题,加上 synchronized 。

2. 静态块与构造块的区别

  1. 静态块加载一次,初始化类的。
  2. 构造块是初始化对象的。
  3. 静态块先于构造块执行。

一、 举例说明:并发情况下,线程不安全

先不说线程同步,可能这个概念不太好整。先说一下并发,因为线程同步的前提是 先有并发。

  • 并发: 同一个对象或者资源 被多个线程同时操作。
  • 线程同步: 当多个线程同时操作一个对象 或者资源的时候,就会出现问题,这个问题是jvm调度无法避免的问题,这个问题就是线程不安全。要避免就要借助其他工具,而这个工具,再Java中叫做线程同步。用示例来表现一下线程不安全的故事:

1. 示例1:unsafe12306取票

需求:模仿12306取票,有10张票,3个人去取,判断 当票数小于0时打印出 票已卖完。

package com.feng.syn;/***  线程不安全: 数据有负数、相同 ,这两种情况*  原因:*      1、负数,处于临界值1时,进来到了test()方法中,都休息了,先后醒来之后,有人拿了1,有人拿了0,有人拿了 -1,*      2、相同,每个线程都有各自的工作空间,最开始时,每个工作空间都去 主存中复制数据,处理之后,在放到主存上去,但是处理的数据不快,*              当其中一个线程拿了10这个数据后,处理完后还未放到主存上时,第二个线程就拿到了这个10.所以会出现两个10的情况。*/
public class ch01_UnsafeTicketTest01 {public static void main(String[] args) {//一份资源UnsafeWeb12306 web =new UnsafeWeb12306();System.out.println(Thread.currentThread().getName());//多个代理new Thread(web,"码畜").start();new Thread(web,"码农").start();new Thread(web,"码蟥").start();}
}
class UnsafeWeb12306 implements Runnable{//票数private int ticketNums = 10;private boolean flag = true;@Overridepublic void run() {while(flag) {test();}}public void test(){if(ticketNums<0) {flag = false;System.out.println("票已卖完");return;}//模拟延时try {Thread.sleep(600);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}
}


结果却发现,有重复值,有负数,相当于判断没起作用。这就是线程不安全。原因就是:3个进程同时操作这个唯一的资源,就会出现线程不安全的情况。

2. 示例2:unsafe银行取钱

需求:银行里有100块钱结婚礼金,两个人去取,打印出账户的余额和取出放在口袋里的钱。

银行信息bean
Account.java 类

package com.feng.syn;public class Account {int money; //金额String name; //名称public Account(int money, String name) {this.money = money;this.name = name;}
}

ch02_UnsafeMoneyTest02.java类

package com.feng.syn;/*** 线程不安全: 取钱** 逻辑:* 连取两次  则为负数 -70;* 各自的口袋的钱是没问题的。*/
public class ch02_UnsafeMoneyTest02 {public static void main(String[] args) {//账户Account account =new Account(100,"结婚礼金");Drawing you = new Drawing(account,80,"可悲的你");Drawing wife = new Drawing(account,90,"happy的她");you.start();wife.start();}
}
//模拟  取款
class Drawing extends Thread{Account account ; //取钱的账户int drawingMoney ;//取的钱数int packetTotal ; //口袋的总数public Drawing(Account account, int drawingMoney,String name) {super(name);this.account = account;this.drawingMoney = drawingMoney;}@Overridepublic void run() {try {Thread.sleep(2000);         // 如果不堵塞的话,速度太快。看不到问题} catch (InterruptedException e) {e.printStackTrace();}if(account.money -drawingMoney<0) {return;}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}account.money -=drawingMoney;packetTotal +=drawingMoney;System.out.println(this.getName()+"-->账户余额为:"+account.money);System.out.println(this.getName()+"-->口袋的钱为:"+packetTotal);}
}

余额竟然会出现-70,的情况,原因是 100现金,她取90,剩10,我取80,剩-70。也就相当于那个判断没起作用。这就是线程不安全。

那为什么会有线程不安全的这一说呢。

二、线程不安全解密:内存可见性

讲解线程不安全之前,先理解下 java内存模型,由此产生的内存可见性问题。

1. java内存模型

  • Java的内存模型分为主内存和工作内存(线程的)。
  • Java内存模型规定了所有的变量都存储在主内存中,每个线程还有自己的工作内存,线程的工作内存中保存了被该线程使用到的变量的主内存副本拷贝(如果对象很大怎么办呢,比如10个MB?虚拟机肯定不会那么傻把对象直接拷贝进去啦,会拷贝对象的地址,以及需要用到的字段的值,哪怕值也很大,比如大的字符串,也会有相应的机制保证不会全拷贝,不然不是直接就爆掉了么)。
  • 线程对变量的所有操作(读取,赋值等)都必须在工作内存中进行,而不能直接读写主内存中的变量。有一个关键字是 volatile, volatile可以保证任何情况下变量的可见性,是不是就是直接读主内存呢,事实上,volatile变量依然有工作内存的拷贝,但是它的操作顺序比较特殊,会每次都从主内存重新加载,所以你会看到每次volatile读取到的都是最新的值。
  • 不同的线程也无法访问其他线程的工作内存中的变量,线程间变量值的传递需要通过主内存来完成。
    这里说的Java的主内存,工作内存与Java堆,栈,方法区等并不是同一个层次的划分,这两者基本是没有关系的,如果两者一定要勉强对应起来,那从变量,主内存,工作内存的定义上看,主内存只要对应于Java堆中的对象实例数据部分,工作内存对应于虚拟机栈中的部分区域。从更低层次上来说,主内存就是直接对应于物理硬件的内存,而为了获取更好的运行速度,虚拟机可能会让工作内存优先存储于寄存器和高速缓存区中,因为程序运行时主要访问读写的是工作内存。

2. 解释线程不安全

  • 当多个线程操作同一个对象或者同一个资源时(比如上面的12306买票和取钱),每个线程会把资源从java主内存中 读取一份到自己的工作内存中,进行操作,操作后,在将结果写到java主内存
    中。
  • 但是,因为有多个线程在同时操作,就要多个工作内存去读和写,假如有a,b两个进程,都读取了一份资源到自己的工作内存中, b内存可能没被更新到主内存去。导致a线程或者其他内存 从主内存拷贝数据到自己的工作区时,拷贝的不是最新的数据。这就是 内存可见性问题。从而导致线程不安全问题。

3. 线程安全与不安全

  1. 线程安全:指多个线程在执行同一段代码的时候采用加锁机制,使每次的执行结果和单线程执行的结果都是一样的,不存在执行程序时出现意外结果。

  2. 线程不安全:是指不提供加锁机制保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据

三、线程同步

1. 概念

然后在说解决线程不安全的那个工具,就是线程同步

线程同步:即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作, 其他线程才能对该内存地址进行操作,而其他线程又处于等待状态。

处理多线程问题时,多个线程访问同一个对象,并且某些线程还想修改 这个对象。 这时候,我们就需要用到**“线程同步”**。 线程同步其实就 是一种等待机制,多个需要同时访问此对象的线程进入这个对象的等待 池形成队列,等待前面的线程使用完毕后,下一个线程再使用。

2. Synchronized 块和方法

由于我们可以通过 private 关键字来保证数据对象只能被方法访问,所以我们只需针对方法提出一套机制,*这套机制就是synchronized关键字,它包括两种用法:synchronized 方法和 synchronized 块

1) 同步方法:safe12306买票

public synchronized void method(int args) {}

synchronized 方法控制对“成员变量|类变量”对象的访问:每个对象对应一把锁,每个 synchronized 方法都必须获得调用该方法 的对象的锁方能执行,否则所属线程阻塞,方法一旦执行,就独占该锁,直到从该方法返回时才将锁释放,此后被阻塞的线程方能获 得该锁,重新进入可执行状态。

同步方法解决12306买票的需求:

package com.feng.syn;/***  线程不安全: 数据有负数、相同 ,这两种情况*  原因:*      1、负数,处于临界值1时,进来到了test()方法中,都休息了,先后醒来之后,有人拿了1,有人拿了0,有人拿了 -1,*      2、相同,每个线程都有各自的工作空间,最开始时,每个工作空间都去 主存中复制数据,处理之后,在放到主存上去,但是处理的数据不快,*              当其中一个线程拿了10这个数据后,处理完后还未放到主存上时,第二个线程就拿到了这个10.所以会出现两个10的情况。**  解决上述问题*  线程安全:在并发时保证数据的正确性、效率尽可能高*  synchronized*  1、同步方法*  2、同步块*/
public class ch04_SafeSynMethodTicketTest01 {public static void main(String[] args) {//一份资源SafeWeb12306 web =new SafeWeb12306();System.out.println(Thread.currentThread().getName());//多个代理new Thread(web,"码畜").start();new Thread(web,"码农").start();new Thread(web,"码蟥").start();}
}
class SafeWeb12306 implements Runnable{//票数private int ticketNums = 10;private boolean flag = true;@Overridepublic void run() {while(flag) {//模拟延时try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}test();}}/*** 线程安全,同步     成员方法,锁的就是 this。     锁住的是对象 this** 锁了资源,资源是对象的资源。此时这个资源是 this*/public synchronized void test(){if(ticketNums<0) {flag = false;return;}//模拟延时try {Thread.sleep(20);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}
}


3) 同步块:safe银行取钱

synchronized (obj){    },  // obj称之为同步监视器
  • obj可以是任何对象,但是推荐使用共享资源作为同步监视 器
  • 同步方法中无需指定同步监视器,因为**同步方法的同步监 视器是this即该对象本身,或class即类的模子**
  • 同步监视器的执行过程 ::
  1. 第一个线程访问,锁定同步监视器,执行其中代码
  2. 第二个线程访问,发现同步监视器被锁定,无法访问
  3. 第一个线程访问完毕,解锁同步监视器
  4. 第二个线程访问,发现同步监视器未锁,锁定并访问
    缺陷:若将一个大的方法声明为synchronized 将会大大影响效率。

同步块解决银行取钱的需求:

package com.feng.syn;/*** 线程安全: 在并发时保证数据的正确性、效率尽可能高* synchronized* 1、同步方法* 2、同步块 ,目标更明确**/
public class ch06_SafeSynBlockMoneyTest02_2 {public static void main(String[] args) {//账户Account account =new Account(100,"结婚礼金");SafeSynDrawing you = new SafeSynDrawing(account,80,"可悲的你");SafeSynDrawing wife = new SafeSynDrawing(account,90,"happy的她");you.start();wife.start();}
}
//模拟取款 线程安全
class SafeSynDrawing extends Thread{Account account ; //取钱的账户int drawingMoney ;//取的钱数int packetTotal ; //口袋的总数public SafeSynDrawing(Account account, int drawingMoney,String name) {super(name);this.account = account;this.drawingMoney = drawingMoney;}@Overridepublic void run() {test() ;}//目标锁定accountpublic  void test() {/*** 提高性能*/if(account.money<=0) {return ;}//同步块synchronized(account) {if(account.money -drawingMoney<0) {return;}try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}account.money -=drawingMoney;packetTotal +=drawingMoney;System.out.println(this.getName()+"-->账户余额为:"+account.money);System.out.println(this.getName()+"-->口袋的钱为:"+packetTotal);}}
}

四、synchronized 的范围问题,优化效率

  • 有时候同步方法 所锁住的范围太大,线程安全,影响效率。
  • 有时候同步块 所锁住的范围太小,锁不住,线程不安全。

1. 同步块、同步方法:12306 取票,double checking

package com.feng.syn;/*** 线程安全: 在并发时保证数据的正确性、效率尽可能高* synchronized* 1、同步方法* 2、同步块** @author 裴新 QQ:3401997271**/
public class ch08_StudySynBlockTicketTest01 {public static void main(String[] args) {//一份资源SynWeb12306 web =new SynWeb12306();//多个代理new Thread(web,"码畜").start();new Thread(web,"码农").start();new Thread(web,"码蟥").start();;}
}class SynWeb12306 implements Runnable{//票数private int ticketNums =10;private boolean flag = true;@Overridepublic void run() {while(flag) {try {Thread.sleep(100);} catch (InterruptedException e) {e.printStackTrace();}test5();}}//线程安全:尽可能锁定合理的范围(不是指代码 指数据的完整性),,性能最好。//double checkingpublic  void test5() {if(ticketNums<=0) {//考虑的是没有票的情况flag = false;return ;}synchronized(this) {if(ticketNums<=0) {//考虑最后的1张票flag = false;return ;}//模拟延时try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}}//线程不安全  范围太小锁不住public  void test4() {synchronized(this) {if(ticketNums<=0) {flag = false;return ;}}//模拟延时try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}//线程不安全  ticketNums对象在变public  void test3() {synchronized((Integer)ticketNums) {if(ticketNums<=0) {flag = false;return ;}//模拟延时try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}}//线程安全 同步块 范围 太大 -->效率低下  ,,同步块,指定锁住的内容,肯定是不变的资源。public  void test2() {synchronized(this) {if(ticketNums<=0) {flag = false;return ;}//模拟延时try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}}//线程安全  同步方法   同步方法锁住的是对象资源,  对象资源看方法里的内容public synchronized void test1() {if(ticketNums<=0) {flag = false;return ;}//模拟延时try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"-->"+ticketNums--);}
}

2. 同步块:快乐影院 ,买几张票

package com.feng.syn;/*** 快乐影院** 多线程 和 进程同步*//*** 如果不锁住的话,会使得可用的位置一直不变*/
public class ch09_HappyCinema01 {public static void main(String[] args) {Cinema cinema = new Cinema(2, "shangxuetang");try {Thread.sleep(200);} catch (InterruptedException e) {e.printStackTrace();}new Thread(new Customer(cinema,2),"老毛").start();new Thread(new Customer(cinema,1),"老冯").start();}
}//顾客
class Customer implements Runnable{private Cinema cinema;private int seats;public Customer(Cinema cinema, int seats) {this.cinema = cinema;this.seats = seats;}@Overridepublic void run() {synchronized (cinema){boolean b = cinema.bookTickets(seats);if (b){System.out.println("出票成功"+Thread.currentThread().getName()+"-<位置为:"+seats);}else{System.out.println("出票失败"+Thread.currentThread().getName()+"-<位置不够");}}}
}// 影院
class Cinema{private int available; // 可用的位置private String name; // 名称public Cinema(int available, String name) {this.available = available;this.name = name;}// 购票public boolean bookTickets(int seats){System.out.println("可用的位置:" + available);if (seats>available){return false;}else {available -= seats;return true;}}
}

3. 同步块:快乐影院:可选座位

package com.feng.syn;/*** 快乐影院** 多线程 和 进程同步*/import java.util.ArrayList;
import java.util.List;/*** 如果不锁住的话,会使得可用的位置一直不变*/
public class ch10_HappyCinema02 {public static void main(String[] args) {//可用位置List available =new ArrayList<Integer>();available.add(1);available.add(2);available.add(3);available.add(6);available.add(7);//顾客需要的位置List<Integer> seats1 =new ArrayList<Integer>();seats1.add(1);seats1.add(2);List<Integer> seats2 =new ArrayList<Integer>();seats2.add(3);seats2.add(6);FengCinema fengCinema = new FengCinema(available, "shangxuetang");new Thread(new HappyCustomer(fengCinema,seats1),"老毛").start();new Thread(new HappyCustomer(fengCinema,seats2),"老冯").start();}
}//顾客
class HappyCustomer implements Runnable{private FengCinema fengCinema;private List<Integer> seats;public HappyCustomer(FengCinema fengCinema, List<Integer> seats) {this.fengCinema = fengCinema;this.seats = seats;}@Overridepublic void run() {synchronized (fengCinema){boolean b = fengCinema.bookTickets(seats);if (b){System.out.println("出票成功"+Thread.currentThread().getName()+"-<位置为:"+seats);}else{System.out.println("出票失败"+Thread.currentThread().getName()+"-<位置不够");}}}
}// 影院
class FengCinema{private List<Integer> available; // 可用的位置private String name; // 名称public FengCinema(List<Integer> available, String name) {this.available = available;this.name = name;}// 购票public boolean bookTickets(List<Integer> seats){System.out.println("欢迎光临"+this.name+",当前可用位置为:"+available);List<Integer> copy = new ArrayList<Integer>();copy.addAll(available);//相减copy.removeAll(seats);                              // 会将相同的值 给删掉   ,remove方法参数为集合//判断大小if(available.size()-copy.size() !=seats.size()) {return false;}else {//成功available = copy;return true;}}
}

4. 同步方法:

package com.feng.syn;public class ch11_Happy12306 {public static void main(String[] args) {Web12306 c = new Web12306(4,"happy sxt");new  Passenger(c,"老高",2).start();new  Passenger(c,"老裴",1).start();}}
//顾客
class Passenger extends  Thread{int seats;public Passenger(Runnable target,String name,int seats) {super(target,name);this.seats = seats;}}
//火车票网
class Web12306 implements Runnable{int available; //可用的位置String name; //名称public Web12306(int available, String name) {this.available = available;this.name = name;}public void run() {Passenger p = (Passenger)Thread.currentThread();boolean flag = this.bookTickets(p.seats);if(flag) {System.out.println("出票成功"+Thread.currentThread().getName()+"-<位置为:"+p.seats);}else {System.out.println("出票失败"+Thread.currentThread().getName()+"-<位置不够");}}//购票public synchronized boolean bookTickets(int seats) {System.out.println("可用位置为:"+available);if(seats>available) {return false;}available -=seats;return true;}
}

五、简单了解锁机制

由于同一进程的多个线程共享同一块存储空间,在带来方便的同时,也带 来了访问冲突的问题。为了保证数据在方法中被访问时的正确性,在访问 时加入**锁机制(synchronized),当一个线程获得对象的排它锁**,独占资源, 其他线程必须等待,使用后释放锁即可。存在以下问题:

  • 一个线程持有锁会导致其它所有需要此锁的线程挂起;
  • 在多线程竞争下,加锁、释放锁会导致比较多的上下文切换和调度延时, 引起性能问题;
  • 如果一个优先级高的线程等待一个优先级低的线程释放锁会导致优先级 倒置,引起性能问题。

死锁:多个线程各自占有一些共享资源,并且互相 等待其他线程占有的资源才能进行,而导致两个 或者多个线程都在等待对方释放资源,都停止执 行的情形。某一个同步块同时拥有“两个以上对 象的锁”时,就可能会发生“死锁”的问题

1. 死锁案例

package com.feng.syn;/*** 死锁: 过多的同步可能造成相互不释放资源* 从而相互等待,一般发生于同步中持有多个对象的锁** 避免: 不要在同一个代码块中,同时持有多个对象的锁**/
public class ch13_DeadLock {public static void main(String[] args) {Markup g1 = new Markup(1,"张柏芝");Markup g2 = new Markup(0,"王菲");g1.start();g2.start();}}
//口红
class Lipstick{}
//镜子
class Mirror{}
//化妆
class Markup extends Thread{static Lipstick lipstick = new Lipstick();static Mirror mirror = new Mirror();//选择int choice;//名字String girl;public Markup(int choice,String girl) {this.choice = choice;this.girl = girl;}@Overridepublic void run() {//化妆markup();}//相互持有对方的对象锁-->可能造成死锁private void markup() {if(choice==0) {synchronized(lipstick) { //获得口红的锁System.out.println(this.girl+"涂口红");//1秒后想拥有镜子的锁try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}/*synchronized(mirror) {System.out.println(this.girl+"照镜子");}*/}synchronized(mirror) {System.out.println(this.girl+"照镜子");}}else {synchronized(mirror) { //获得镜子的锁System.out.println(this.girl+"照镜子");//2秒后想拥有口红的锁try {Thread.sleep(2000);} catch (InterruptedException e) {e.printStackTrace();}/*synchronized(lipstick) {System.out.println(this.girl+"涂口红");}    */}synchronized(lipstick) {System.out.println(this.girl+"涂口红");}}}
}

java多线程学习二、安全与不安全示例:12306买票和银行取钱、java内存模型、内存可见性、线程同步块和方法相关推荐

  1. java多线程学习一、线程介绍、线程创建的3种方式、lambda创建方式、线程状态、线程示例:12306买票和银行取钱

    文章目录 前言 一.线程简介 1.概述 2.进程.线程 区别 在这里插入图片描述 3. 核心概念 二. 线程创建 1.概述 2. 第一种方式继承Thread 1) 继承Thread 2) 示例:下载图 ...

  2. Java多线程学习(二)---线程创建方式

    线程创建方式 摘要: 1. 通过继承Thread类来创建并启动多线程的方式 2. 通过实现Runnable接口来创建并启动线程的方式 3. 通过实现Callable接口来创建并启动线程的方式 4. 总 ...

  3. Java多线程学习二十:HashMap 为什么是线程不安全的

    为什么 HashMap 是线程不安全的?而对于 HashMap,相信你一定并不陌生,HashMap 是我们平时工作和学习中用得非常非常多的一个容器,也是 Map 最主要的实现类之一,但是它自身并不具备 ...

  4. Java多线程学习二十六:原子类是如何利用 CAS 保证线程安全的?

    什么是原子类,以及它有什么作用. 在编程领域里,原子性意味着"一组操作要么全都操作成功,要么全都失败,不能只操作成功其中的一部分".而 java.util.concurrent.a ...

  5. Java多线程学习二十三:什么是阻塞队列

    阻塞队列的作用 阻塞队列,也就是 BlockingQueue,它是一个接口,如代码所示: public interface BlockingQueue<E> extends Queue&l ...

  6. Java多线程学习二十一:ConcurrentHashMap 在 Java7 和 8 有何不同

    在 Java 8 中,对于 ConcurrentHashMap 这个常用的工具类进行了很大的升级,对比之前 Java 7 版本在诸多方面都进行了调整和变化.不过,在 Java 7 中的 Segment ...

  7. Java多线程学习二十八:原子类和 volatile 有什么异同?

    原子类和 volatile 有什么异同 案例说明 volatile 和原子类的异同 我们首先看一个案例.如图所示,我们有两个线程. 在图中左上角可以看出,有一个公共的 boolean flag 标记位 ...

  8. Java多线程学习二十四:阻塞队列包含哪些常用的方法?add、offer、put 等方法的区别?

    阻塞队列包含哪些常用的方法,以及 add,offer,put 等方法的区别. 在阻塞队列中有很多方法,而且它们都非常相似,所以非常有必要对这些类似的方法进行辨析,所以本课时会用分类的方式,和你一起,把 ...

  9. Java多线程学习二十九:AtomicInteger(原子类) 和 synchronized 的异同点?

    原子类和 synchronized 关键字都可以用来保证线程安全,在本课时中,我们首先分别用原子类和 synchronized 关键字来解决一个经典的线程安全问题,给出具体的代码对比,然后再分析它们背 ...

最新文章

  1. react部署之页面空白
  2. (进阶篇_01)Oracle数据同步3种场景
  3. 市监总局对橙心优选等五家社区团购不正当价格行为作出行政处罚
  4. Java成神之路——UML类关系图
  5. 《如何搭建小微企业风控模型》第十三节 额度公式 节选
  6. 通过Ajax的方式执行GP服务
  7. ajax 实时进度_【乐建工程宝】如何把控施工项目进度
  8. c#正则表达式应用实例
  9. [转] Java/JSP中使用JDBC连接SQL Server 2005
  10. Jenkins + Docker 简单部署 node.js 项目
  11. Ubuntu 11.10安装QQ2012
  12. 艺术摄影--曝光与测光(2学时)--SDUST
  13. 产品经理应该懂哪些术语?
  14. “防不胜防”的智能助理:Alexa秒变诈骗工具
  15. dbus系列教程(2)理解dbus核心概念
  16. securefx显示linux目录,使用secureFX连接到linux上需要在linux上配置什么
  17. 10018801骗子
  18. python计算正方体和长方体_定义一个接口,计算正方体和长方体的体积,并写一个测试类进行测试...
  19. Spring boot 集成 ureport (三) 报表存储至数据库
  20. NGINX 常见模块

热门文章

  1. 51nod1289 大鱼吃小鱼
  2. 线稿图视频制作--从此短视频平台不缺上传视频了
  3. CentOS6和CentOS7区别
  4. 计算机毕业设计SSM服装创意定制管理系统【附源码数据库】
  5. HTML基础-05-图片(引用图片src=“图片地址“、图片位置 align=“位置“、浮动图片 style=“float:位置“、图片链接 href=“目标url“、图形映射)
  6. 中移动本周开通7城市TD网 首期用户月补助80元
  7. Alpine Linux 时间同步
  8. php开源saas系统,ThinkSAAS
  9. Exynos4412搭建最小文件系统
  10. 玩qq游戏提示计算机内存不足,电脑玩游戏总提示内存不足怎么办