java多线程下的对象及变量的并发访问

上一节讲到,并发访问的时候,因为是多线程,变量如果不加锁的话,会出现“脏读”的现象,这个时候需要“临界区”的出现去解决多线程的安全的并发访问。(这个“脏读”的现象不会出现在方法内部的私有变量中,因为其私有的特性,永远都是线程安全的)

目前锁有三种:synchronized / volatile / Lock

三类锁各有所长,本节先介绍关键字 :synchronized

synchronized关键字用来实现线程之间同步互斥。

public classTest{private num = 0;public voidaddId(String username){try{if(username.equals("a")){

num= 100;

System.out.println("a set over!");

Thread.sleep(2000);

}else{

num= 200;

System.out.println("b set over!");

}

System.out.println(username+ " num = " +num);

}catch(InterruptedException e){

e.printStackTrace();

}

}

}public class ThreadA extendsThread {privateTest test;publicThreadA(Test test){this.test =test;

}

@Overridepublic voidrun(){super.run(); //在笔记 (一) 里面提到过,其实觉得可以不加上

test.addId("a");

}

}public class ThreadB extendsThread {privateTest test;publicThreadB(Test test){this.test =test;

}

@Overridepublic voidrun(){super.run(); //在笔记 (一) 里面提到过,其实觉得可以不加上

test.addId("b");

}

}

同时运行时,Test类中的num变量会被两个线程不同步的修改,出现错误

public classRun{public static voidmain(String[] args){

Test test= newTess(); //!!!!!!!!!!这里很关键,这里是同一个实例对象test!下文会提到!

ThreadA athread= newThreadA(test);

athread.start();

ThreadB bthread= newThreadB(test);

bthread.start();

}

}

这时,想让他们同步的办法便是给他们的 addId() 方法,加上锁:synchronized 关键字。

synchronized public voidaddId(String username){//...中间部分全部相同的代码

}

结论:两个线程访问同一个对象中的同步方法时,一定是线程安全的。

既然有同一个对象中的同步方法,肯定就会有多个对象的情况,这个时候就会有多个对象多个锁的情况:

这里详细说一下synchronized关键字加锁的范围:(本部分加锁范围借鉴了宇学愈多的博文)

修饰普通方法(锁住的是当前实例对象)

同步代码块传参this(锁住的是当前实例对象)

同步代码块传参变量对象 (锁住的是变量对象)

同步代码块传参class对象(全局锁)

修饰静态方法(全局锁)

构造函数,原型对象,实例对象三者之间的关系

构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。

Example e =new Example(n); //构造函数。

通过调用构造函数产生的实例对象,都拥有一个内部属性,指向了原型对象。其实例对象能够访问原型对象上的所有属性和方法。//e 为实例对象

1 修饰普通方法:

public classSynchronizedTest {//锁住了本类的实例对象

public synchronized voidtest1() {try{

logger.info(Thread.currentThread().getName()+ " test1 进入了同步方法");

Thread.sleep(5000);

logger.info(Thread.currentThread().getName()+ " test1 休眠结束");

}catch(InterruptedException e) {

e.printStackTrace();

}

}public static voidmain(String[] args) {

SynchronizedTest st= newSynchronizedTest();

SynchronizedTest st2= newSynchronizedTest();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st.test1();

}).start();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st2.test1();

}).start();

}

}

本例的实例对象为 st st2 :

同一个实例调用会阻塞(开篇提到的例子中,两个线程访问同一个对象实例方法,所以会产生阻塞)

不同实例调用不会阻塞

上文的代码的运行结果是没有阻塞的,因为是不同的实例对象,调用了相同的方法 test1() .

2 同步代码块穿参this

同一个实例调用会阻塞

不同实例调用不会阻塞

public classSynchronizedTest {//锁住了本类的实例对象

public voidtest2() {synchronized (this) {try{

logger.info(Thread.currentThread().getName()+ " test2 进入了同步块");

Thread.sleep(5000);

logger.info(Thread.currentThread().getName()+ " test2 休眠结束");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}public static voidmain(String[] args) {

SynchronizedTest st= newSynchronizedTest();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st.test2();

}).start();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st.test2();

}).start();

}

}

和 1 一样,同样是锁住了当前的实例对象

3 同步代码块传参变量对象

同一个属性对象才会实现同步

public classSynchronizedTest {publicInteger lockObject;publicSynchronizedTest(Integer lockObject) {this.lockObject =lockObject;

}//锁住了实例中的成员变量

public voidtest3() {synchronized(lockObject) {try{

logger.info(Thread.currentThread().getName()+ " test3 进入了同步块");

Thread.sleep(5000);

logger.info(Thread.currentThread().getName()+ " test3 休眠结束");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}public static voidmain(String[] args) {

SynchronizedTest st= new SynchronizedTest(127);

SynchronizedTest st2= new SynchronizedTest(127);new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st.test3();

}).start();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st2.test3();

}).start();

}

}

同一个实例对象的成员属性肯定是同一个,此处列举的是不同实例的情况,但是 依旧实现了同步,原因如下:

Integer存在静态缓存,范围是-128 ~ 127,当使用Integer A = 127 或者 Integer A = Integer.valueOf(127) 这样的形式,都是从此缓存拿。如果使用 Integer A = new Integer(127),每次都是一个新的对象。此例中,两个对象实例的成员变量 lockObject 其实是同一个对象,因此实现了同步。还有字符串常量池也要注意。所以此处关注是,同步代码块传参的对象是否是同一个。这跟第二个方式其实是同一种。

4、同步代码块传参class对象(全局锁)

所有调用该方法的线程都会实现同步。

public classSynchronizedTest {//全局锁,类是全局唯一的

public voidtest4() {synchronized (SynchronizedTest.class) {try{

logger.info(Thread.currentThread().getName()+ " test4 进入了同步块");

Thread.sleep(5000);

logger.info(Thread.currentThread().getName()+ " test4 休眠结束");

}catch(InterruptedException e) {

e.printStackTrace();

}

}

}public static voidmain(String[] args) {

SynchronizedTest st= newSynchronizedTest();

SynchronizedTest st2= newSynchronizedTest();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st.test4();

}).start();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st2.test4();

}).start();

}

}

类锁,直接锁了全局了

5、修饰静态方法(全局锁)

所有调用该方法的线程都会实现同步

public classSynchronizedTest {//全局锁,静态方法全局唯一的

public synchronized static voidtest5() {try{

logger.info(Thread.currentThread().getName()+ " test5 进入同步方法");

Thread.sleep(5000);

logger.info(Thread.currentThread().getName()+ " test5 休眠结束");

}catch(InterruptedException e) {

e.printStackTrace();

}

}public static voidmain(String[] args) {

SynchronizedTest st= newSynchronizedTest();

SynchronizedTest st2= newSynchronizedTest();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st.test5();

}).start();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

st2.test5();

}).start();new Thread(() ->{

logger.info(Thread.currentThread().getName()+ " test 准备进入");

SynchronizedTest.test5();

}).start();

}

}

结论:synchronized在语法维度上主要分为三个用法

静态方法加上关键字

实例方法(也就是普通方法)加上关键字

方法中使用同步代码块

前两种方式最为偷懒,第三种方式比前两种性能要好。

本篇的最后加上一个多线程的题目:利用5个线程并发执行,num数字累计计数到10000,并打印。

/*** Description:

* 利用5个线程并发执行,num数字累加计数到10000,并打印。

* 2019-06-13

* Created with OKevin.*/

public classCount {private int num = 0;public static void main(String[] args) throwsInterruptedException {

Count count= newCount();

Thread thread1= new Thread(count.newMyThread());

Thread thread2= new Thread(count.newMyThread());

Thread thread3= new Thread(count.newMyThread());

Thread thread4= new Thread(count.newMyThread());

Thread thread5= new Thread(count.newMyThread());

thread1.start();

thread2.start();

thread3.start();

thread4.start();

thread5.start();

thread1.join();

thread2.join();

thread3.join();

thread4.join();

thread5.join();

System.out.println(count.num);

}private synchronized voidincrese() {for (int i = 0; i < 2000; i++) {

num++;

}

}class MyThread implementsRunnable {

@Overridepublic voidrun() {

increse();

}

}

}

java丐帮_java多线程学习笔记(三)相关推荐

  1. java丐帮_java多线程学习笔记(五)

    补充一个synchronized关键字的结论: 线程A先持有object对象的Lock锁,B线程可以以异步的方式调用object对象中的非synchronized类型的方法 A线程现持有object对 ...

  2. java丐帮_java多线程学习笔记(四)

    上一节讲到Synchronized关键字,synchronized上锁的区域:对象锁=方法锁/类锁 本节补充介绍一下synchronized锁重入: 关键字synchronized拥有锁重入的功能,也 ...

  3. java丐帮_Java多线程学习笔记(一)

    一.什么是多线程 首先是多线程的概念: 多线程是异步的,和单任务不同,并不一定按照代码的执行顺序(上图左)来运行,而是交错占用CPU运行(上图右): 二.如何使用多线程 JAVA多线程有两种实现方式: ...

  4. java丐帮_java多线程学习笔记(二)

    上一节讲到多线程的创建,两种创建方式一种继承Thread类,一种实现Runnable接口: 常用的多线程函数: currentThread()方法        返回代码段正在被哪个线程调用的信息 i ...

  5. java丐帮_java多线程学习笔记(八)

    本节开始线程间通信: 使用wait/notify实现线程间通信 生产者/消费者模式的实现 方法join的使用 ThreadLocal类的使用 可以通过使用 sleep() 结合 while(true) ...

  6. java丐帮_java多线程学习笔记(六)

    本节开始synchronized关键字的最后一部分内容: 静态同步方法synchronized方法与synchronized(class)代码块 关键字synchronized还可以用在static静 ...

  7. java线程集合点_Java多线程学习笔记(三) 甚欢篇

    使人有乍交之欢,不若使其无久处之厌 <小窗幽记>很多时候,我们需要的都不是再多一个线程,我们需要的线程是许多个,我们需要让他们配合.同时我们还有一个愿望就是复用线程,就是将线程当做一个工人 ...

  8. java 编程思想 多线程学习笔记

    java 编程思想 多线程学习笔记 一.如何创建多线程? 1.继承 java.lang.Thread 类 2.实现 java.lang.Runnable 接口 3.Callable接口 总之,在任何线 ...

  9. Java多线程学习笔记(三)休眠(sleep),让步(yield),插队(join)

    线程目录 线程休眠: 线程让步: 线程插队: 线程休眠: 使用Thread.sleep(long millis)可以使线程休眠,也就是将正在执行的线程暂停,将CPU让给其他线程去执行. 实例: pub ...

最新文章

  1. 为什么做了梦第二天想不起来_为什么做的梦总是想不起来?科学的解释是……...
  2. Qt学习笔记之SQLITE数据库
  3. Google Gson 使用简介
  4. java uuid_Java UUID
  5. oracle sequence last_number,关于oracle序列的LAST_NUMBER
  6. 实战:kafka实现日志收集系统
  7. 如何快速打好java基础_学习Java课程时如何才能打好基础呢?
  8. ERROR streaming.StreamExecution:createConsumer(ConsumerStrategy.scala:63)
  9. cbitmap 从内存中加载jpg_Pytorch数据加载的分析
  10. 可控硅型号怎样识别_可控硅是什么_可控硅型号_可控硅分类及判别_可控硅种类...
  11. Hello China操作系统的安装和使用
  12. js中 json对象与json字符串相互转换的几种方式
  13. 像素值/DN值/数字量化值
  14. GEF原理及实现系列(二、模型)
  15. 在c的基础上关于c#入门的一些个人理解
  16. 让人感到心灵平静,阳光温暖的图片
  17. 世界生产力科学院院士(中国籍)名单
  18. window10安装Mac虚拟机详细教程
  19. 零基础入门金融风控-贷款违约预测-机器学习-数据分析
  20. 创建PostgreSQL数据库

热门文章

  1. Python 实时盯盘并在股价突破时通过微信通知(tushare的使用)
  2. unity3D游戏开发实战原创视频讲座系列13之帽子戏法游戏开发(预告)
  3. 中国风味苹果酒市场现状研究分析与发展前景预测报告(2022)
  4. 欢迎订阅我的专栏,好文章一网打尽
  5. The Bucket List 题解
  6. 【转】我奋斗了18年不是为了和你一起喝咖啡
  7. dapi 基于Django的轻量级接口测试平台一
  8. 认识SD卡家族(SD miniSD microSD TF SDIO)
  9. 字节跳动小程序tt.pay支付流程和遇到的问题
  10. 关于无盘产品市场调查