java多线程学习笔记(三)
java多线程下的对象及变量的并发访问
上一节讲到,并发访问的时候,因为是多线程,变量如果不加锁的话,会出现“脏读”的现象,这个时候需要“临界区”的出现去解决多线程的安全的并发访问。(这个“脏读”的现象不会出现在方法内部的私有变量中,因为其私有的特性,永远都是线程安全的)
目前锁有三种:synchronized / volatile / Lock
三类锁各有所长,本节先介绍关键字 :synchronized
synchronized关键字用来实现线程之间同步互斥。
public class Test{
private num = 0;
public void addId(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 extends Thread {
private Test test;
public ThreadA(Test test){
this.test = test;
}
@Override
public void run(){
super.run(); //在笔记 (一) 里面提到过,其实觉得可以不加上
test.addId("a");
}
}
public class ThreadB extends Thread {
private Test test;
public ThreadB(Test test){
this.test = test;
}
@Override
public void run(){
super.run(); //在笔记 (一) 里面提到过,其实觉得可以不加上
test.addId("b");
}
}
同时运行时,Test类中的num变量会被两个线程不同步的修改,出现错误
public class Run{
public static void main(String[] args){
Test test = new Tess(); //!!!!!!!!!!这里很关键,这里是同一个实例对象test!下文会提到!
ThreadA athread = new ThreadA(test);
athread.start();
ThreadB bthread = new ThreadB(test);
bthread.start();
}
}
这时,想让他们同步的办法便是给他们的 addId() 方法,加上锁:synchronized 关键字。
synchronized public void addId(String username){
//...中间部分全部相同的代码
}
结论:两个线程访问同一个对象中的同步方法时,一定是线程安全的。
既然有同一个对象中的同步方法,肯定就会有多个对象的情况,这个时候就会有多个对象多个锁的情况:
这里详细说一下synchronized关键字加锁的范围:(本部分加锁范围借鉴了宇学愈多的博文)
- 修饰普通方法(锁住的是当前实例对象)
同步代码块传参this(锁住的是当前实例对象)
同步代码块传参变量对象 (锁住的是变量对象)
同步代码块传参class对象(全局锁)
修饰静态方法(全局锁)
构造函数,原型对象,实例对象三者之间的关系
构造函数 ,是一种特殊的方法。主要用来在创建对象时初始化对象, 即为对象成员变量赋初始值,总与new运算符一起使用在创建对象的语句中。特别的一个类可以有多个构造函数 ,可根据其参数个数的不同或参数类型的不同来区分它们 即构造函数的重载。
Example e = new Example(n); //构造函数。
通过调用构造函数产生的实例对象,都拥有一个内部属性,指向了原型对象。其实例对象能够访问原型对象上的所有属性和方法。//e 为实例对象
1 修饰普通方法:
public class SynchronizedTest {
//锁住了本类的实例对象
public synchronized void test1() {
try {
logger.info(Thread.currentThread().getName() + " test1 进入了同步方法");
Thread.sleep(5000);
logger.info(Thread.currentThread().getName() + " test1 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SynchronizedTest st = new SynchronizedTest();
SynchronizedTest st2 = new SynchronizedTest();
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 class SynchronizedTest {
//锁住了本类的实例对象
public void test2() {
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 void main(String[] args) {
SynchronizedTest st = new SynchronizedTest();
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 class SynchronizedTest {
public Integer lockObject;
public SynchronizedTest(Integer lockObject) {
this.lockObject = lockObject;
}
//锁住了实例中的成员变量
public void test3() {
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 void main(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 class SynchronizedTest {
//全局锁,类是全局唯一的
public void test4() {
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 void main(String[] args) {
SynchronizedTest st = new SynchronizedTest();
SynchronizedTest st2 = new SynchronizedTest();
new Thread(() -> {
logger.info(Thread.currentThread().getName() + " test 准备进入");
st.test4();
}).start();
new Thread(() -> {
logger.info(Thread.currentThread().getName() + " test 准备进入");
st2.test4();
}).start();
}
}
类锁,直接锁了全局了
5、修饰静态方法(全局锁)
- 所有调用该方法的线程都会实现同步
public class SynchronizedTest {
//全局锁,静态方法全局唯一的
public synchronized static void test5() {
try {
logger.info(Thread.currentThread().getName() + " test5 进入同步方法");
Thread.sleep(5000);
logger.info(Thread.currentThread().getName() + " test5 休眠结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
SynchronizedTest st = new SynchronizedTest();
SynchronizedTest st2 = new SynchronizedTest();
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 class Count {
private int num = 0; public static void main(String[] args) throws InterruptedException {
Count count = new Count(); Thread thread1 = new Thread(count.new MyThread());
Thread thread2 = new Thread(count.new MyThread());
Thread thread3 = new Thread(count.new MyThread());
Thread thread4 = new Thread(count.new MyThread());
Thread thread5 = new Thread(count.new MyThread());
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 void increse() {
for (int i = 0; i < 2000; i++) {
num++;
}
} class MyThread implements Runnable {
@Override
public void run() {
increse();
}
}
}
java多线程学习笔记(三)的更多相关文章
- java多线程学习笔记——详细
一.线程类 1.新建状态(New):新创建了一个线程对象. 2.就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()方法.该状态的线程位于可运行线程池中, ...
- JAVA多线程学习笔记(1)
JAVA多线程学习笔记(1) 由于笔者使用markdown格式书写,后续copy到blog可能存在格式不美观的问题,本文的.mk文件已经上传到个人的github,会进行同步更新.github传送门 一 ...
- Java多线程学习笔记(一)——多线程实现和安全问题
1. 线程.进程.多线程: 进程是正在执行的程序,线程是进程中的代码执行,多线程就是在一个进程中有多个线程同时执行不同的任务,就像QQ,既可以开视频,又可以同时打字聊天. 2.线程的特点: 1.运行任 ...
- Java IO学习笔记三
Java IO学习笔记三 在整个IO包中,实际上就是分为字节流和字符流,但是除了这两个流之外,还存在了一组字节流-字符流的转换类. OutputStreamWriter:是Writer的子类,将输出的 ...
- Java多线程学习(三)volatile关键字
转载请备注地址:https://blog.csdn.net/qq_34337272/article/details/79680693 系列文章传送门: Java多线程学习(一)Java多线程入门 Ja ...
- Java IO学习笔记三:MMAP与RandomAccessFile
作者:Grey 原文地址:Java IO学习笔记三:MMAP与RandomAccessFile 关于RandomAccessFile 相较于前面提到的BufferedReader/Writer和Fil ...
- Java多线程学习笔记
进程:正在执行中的程序,其实是应用程序在内存中运行的那片空间.(只负责空间分配) 线程:进程中的一个执行单元,负责进程汇总的程序的运行,一个进程当中至少要有一个线程. 多线程:一个进程中时可以有多个线 ...
- JAVA WEB学习笔记(三):简单的基于Tomcat的Web页面
注意:每次对Tomcat配置文件进行修改后,必须重启Tomcat 在E盘的DATA文件夹中创建TomcatDemo文件夹,并将Tomcat安装路径下的webapps/ROOT中的WEB-INF文件夹复 ...
- Java多线程学习笔记--生产消费者模式
实际开发中,我们经常会接触到生产消费者模型,如:Android的Looper相应handler处理UI操作,Socket通信的响应过程.数据缓冲区在文件读写应用等.强大的模型框架,鉴于本人水平有限目前 ...
随机推荐
- printf输出各种类型,cout控制输出各式
; char c = 'A'; int *p = &a; char *st = "ahj"; float x = 3.1415926; cout << & ...
- 《JavaScript DOM 编程艺术》学习成果
(在线演示地址)[http://thqy39.github.io/works/03.Js%20DOM%20website/index.html]
- 记录js中的兼容问题及解决办法
1.获取非行内样式的兼容问题: 2.获取事件对象的兼容问题: 3.事件冒泡的兼容: 4.keyCode的兼容问题: 5.处理默认事件的兼容问题: 6.事件的绑定兼容问题:
- Cocos2d 之FlyBird开发---GameData类
| 版权声明:本文为博主原创文章,未经博主允许不得转载. 现在是大数据的时代,绝大多数的游戏也都离不开游戏数据的控制,简单的就是一般记录游戏的得分情况,高端大气上档次一点的就是记录和保存各方面的游 ...
- Java开发用H2数据库
#JPA Configuration:#spring.jpa.database=MySQLspring.datasource.url=jdbc:h2:mem:jpaspring.datasource. ...
- C#编程—第五天--循环语句for
for穷举法.迭代法 穷举法练习: //穷举法: //1.找100以内的与7有关的数 //2.小明单位发了一百元的购物卡,他到超市买洗化用品,一是洗发水(15元),二是香皂(2元),三是牙刷(5元)怎 ...
- GeneXus笔记本—获取当月的最后一天
首先获取当前日期 然后赋值为当前年月的第一天 然后加一个月 减去一天 就是当月最后一天 多用于筛选数据时的条件或者区间 我们先随便拉个页面 简单点就好 放入两个textblock 然后点击Even ...
- CSS中表示颜色的4种方法
#1:直接用颜色名称 #2:十六进制数 #3:RGB整数设置颜色 0-255 #4:RGB百分数设置颜色0%-100%
- Centos yum的源 设置为阿里云源
在 阿里巴巴镜像站页面,在centos 操作的帮助,有介绍 wget和curl 2种方式来下载CentOS-Base.repo 备份 mv /etc/yum.repos.d/CentOS-Base.r ...
- Java集合类框架的最佳实践有哪些
根据应用的需要正确选择要使用的集合的类型对性能非常重要,比如:元素的大小是固定的,而且能事先知道,我们就应该用Array而不是ArrayList. 有些集合类允许指定初始容量.因此,如果我们能估计出存 ...