【Thread】java线程之对象锁、类锁、线程安全
说明:
1、个人技术也不咋滴、也没在项目中写过线程,以下全是根据自己的理解写的。所以,仅供参考及希望指出不同的观点。
2、其实想把代码的github贴出来,但还是推荐在初学的您多亲自写一下,就没贴出来了。
一、基本说明
类、对象:。。。(不知道怎么说,只可意会不可言传>.<!);要明白哪些方法、变量是对象的,哪些是类的。
类锁、对象锁:对应类和对象。每个类有且仅有一个类锁,每个对象有且仅有一个对象锁。
ex: Person p1 = new Person(); Person p2 = new Person();
Person类只有一个类锁,p1对象有自己的对象锁,p2也有自己的对象锁。所以,demo中一共有3把锁:1把类锁、2把对象锁。
进程、线程:一个进程可以有多个线程。
并发:最大化利用资源,轮流执行。ex: 只有一本《Think In Java》,A看一会B看一会。
并行:真正的同时进行,ex:有多本《Think In Java》,A看一本,B看一本。
摘自baike:
并发的实质是一个物理CPU(也可以多个物理CPU)在若干道程序之间多路复用,并发性是对有限物理资源强制行驶多用户共享以提高效率。
并行指的是两个或两个以上的事件或活动在同一时刻发生。在多道程序环境下,并行性使多个程序同一时刻可在不同CPU上同时执行。
我所知道的,java中的线程、或者平常说的线程基本都是并发的(我也不能确定,因为不清楚;前面baidu了下,基本博客说的java实现并行其实都是并发,但也看见有说JDK8能并行编程的,待了解)。
二、场景构想
场景:(不考虑线程安全,已知约束每次输出0~4)
要输出0、1、2、3、4共3次,即总共输出15次。
实现:
1、(类锁)3个对象各自执行1次。
2、(对象锁)一个对象执行3次。
三、实现1:3个对象各执行1次
1、现在不考虑线程安全、不考虑同步异步,只简单的满足:3个对象各自执行1次。
public class ClassLock {
public static void main(String[] args) {
Runnable async = new AsyncClass();
Thread t1 = new Thread(async,"Thread-A");
Thread t2 = new Thread(async,"Thread-B");
Thread t3 = new Thread(async,"Thread-C");
t1.start(); // code-1
t2.start(); // code-2
t3.start(); // code-3
}
}
/** 相对的异步 */
class AsyncClass implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
// Thread.sleep(1*1000);
System.out.println(Thread.currentThread().getName() + " : " + i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
}
特别重要说明:
1、java的线程调度是随机的,什么意思?
在上面代码中,只能保证t1、t2、t3都执行,但并不能确定输出的顺序。以下例举几个:
(个人理解,可能理解/用词错误)java代码还是按顺序执行,所以可以保证t1、t2、t3进入线程池(用词不好,或者说类似是进入候车厅,等待获取锁)的顺序是按代码顺序。但是,获取锁是随机(JVM决定)。
参考代码来说,进程Process-A开启线程Thread-main来执行main方法。此时只有一个线程:Thread-main。
当执行到code-1时开启了线程Thread-A,此时Process-A就有2个线程:Thread-main、Thread-A。
因为存在多个线程,所以现在Process-A就存在线程调度的问题。即,现在可能是继续执行main后面的代码,也可能去执行Thread-A的代码。或者轮换执行,即并发。
后面对应的,执行到code-2就有3个线程:Thread-main、Thread-A、Thread-B。
public static void main(String[] args) {
Runnable async = new AsyncClass();
Thread t1 = new Thread(async,"Thread-A");
Thread t2 = new Thread(async,"Thread-B");
Thread t3 = new Thread(async,"Thread-C");
System.out.println("vergilyn");
t1.start();
System.out.println("dante");
t2.start();
System.out.println("vergil");
t3.start();
System.out.println("end");
}
上面代码的结果: 只能保证“vergilyn”是最先输出的,后面的输出顺序都不确定(包括end)。
2、(同步类锁)现在保证某个对象输出完了,另外一个对象才接着输出
因为是3个对象各自执行,所以要用类锁去控制,而不是对象锁。(同步静态方法加即等同于类锁)
/** 同步 */
class SyncClass implements Runnable{
@Override
public void run() {
synchronized (SyncClass.class){ //sync-1
for (int i = 0; i < 5; i++) {
//synchronized (SyncClass.class) { //sync-2
try {
// Thread.sleep(1*1000);
System.out.println(Thread.currentThread().getName() + " : " + i);
} catch (Exception e) {
e.printStackTrace();
}
// }
}
}
}
}
说明:因为是类锁,所以保证了每个对象进来,必须执行完for循环才释放类锁(当然也可以手动释放wait():释放了锁,其它线程可以去竞争获取该锁;sleep():未释放锁,锁的所有权还是在当前线程)
1、sync-1才能达到需求。保证了执行完for循环才释放锁。
2、sync-2当执行完try-catch就释放了锁,线程间又竞争获取该锁,并不能确定下一个获得锁的是哪个线程。
在多线程中,重要的一点就是:同步块的抉择(哪里才是最小同步块,或者这同步块对不对)、同步影响并发性。
在上面demo中其实举例不好,要达到需求只能写在sync-1,而不能写在sync-2。但这想说明的是,你要明确要锁的是什么,是对象、还是类。最小同步代码块在哪?
四、实现2:一个对象执行3次
public class ObjectLock {
public static void main(String[] args) {
Runnable p1 = new SyncObject();
// Runnable p1 = new AsyncThread();
Thread t1 = new Thread(p1,"A");
Thread t2 = new Thread(p1,"B");
Thread t3 = new Thread(p1,"C");
t1.start();
t2.start();
t3.start();
}
}
class AsyncObject implements Runnable{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
// Thread.sleep(1*1000);
System.out.println(Thread.currentThread().getName() + " : " + i);
} catch (Exception e) {
e.printStackTrace();
}
}
}
} class SyncObject implements Runnable{
@Override
public void run() {
synchronized (this){
for (int i = 0; i < 5; i++) {
try {
// Thread.sleep(1*1000); System.out.println(Thread.currentThread().getName() + " : " + i); } catch (Exception e) {
e.printStackTrace();
}
}
}
}
}
在demo中,其实和类锁的代码区别很少。无非是三个对象各自绑定一个线程执行,还是一个对象绑定三个线程执行(用词不好)
五、对象锁、类锁
不知道怎么用语言表达;希望通过上面代码能悟出什么是锁,什么是对象锁、类锁。
锁的作用,就是持有锁的线程才可以执行,别的线程只能等待获取锁。(再次说明:jvm随机决定谁能获取到锁。)
扩展:
持有锁的线程会释放锁:
1. 执行完同步代码块。
2. 在执行同步代码块的过程中,遇到异常而导致线程终止。
3. 在执行同步代码块的过程中,执行了锁所属对象的wait()方法,这个线程会释放锁,进入对象的等待池。
除了以上情况外,只要持有锁的线程还没有执行完同步代码块,就不会释放锁。
线程不会释放锁:
1.在执行同步代码块的过程中,执行了Thread.sleep()方法,当前线程放弃CPU,开始睡眠,在睡眠中不会释放锁。
2. 在执行同步代码块的过程中,执行了Thread.yield()方法,当前线程放弃CPU,但不会释放锁。
3.在执行同步代码块的过程中,其他线程执行了当前对象的suspend()方法,当前线程被暂停,但不会释放锁。但Thread类的suspend()方法已经被废弃。
六、线程安全
摘自baike:
线程安全就是多线程访问时,采用了加锁机制,当一个线程访问该类的某个数据时,进行保护,其他线程不能进行访问直到该线程读取完,其他线程才可使用。不会出现数据不一致或者数据污染。 线程不安全就是不提供数据访问保护,有可能出现多个线程先后更改数据造成所得到的数据是脏数据。
比如一个 ArrayList 类,在添加一个元素的时候,它可能会有两步来完成:1. 在 Items[Size] 的位置存放此元素;2. 增大 Size 的值。
在单线程运行的情况下,如果 Size = 0,添加一个元素后,此元素在位置 0,而且 Size=1;
而如果是在多线程情况下,比如有两个线程,线程 A 先将元素1存放在位置 0。但是此时 CPU 调度线程A暂停,线程 B 得到运行的机会。线程B向此 ArrayList 添加元素2,因为此时 Size 仍然等于 0 (注意,我们假设的是添加一个元素是要两个步骤,而线程A仅仅完成了步骤1),所以线程B也将元素存放在位置0。然后线程A和线程B都继续运行,都增加 Size 的值,结果Size等于2。
那好,我们来看看 ArrayList 的情况,期望的元素应该有2个,而实际只有一个元素,造成丢失元素,而且Size 等于 2。这就是“线程不安全”了。
总的来说,要先明白变量、方法是属于类的还是对象的,或者还是局部变量。然后那些在线程间允许共享、哪些不允许。
(我所知道的)java的线程安全基本都是靠同步代码块来实现的。
1、线程不安全
线程间的数据不可预测,例如线程A把成员变量从1修改成2,但线程2读取到的可能是1。(并发性)
public class ThreadSecurity {
public static void main(String[] args) {
Runnable r1 = new Insecurity();
Thread t1 = new Thread(r1,"Thread-A");
Thread t2 = new Thread(r1,"Thread-B");
Thread t3 = new Thread(r1,"Thread-C");
t1.start();
t2.start();
t3.start();
}
}
/** 线程不安全 */
class Insecurity implements Runnable{
private int i = 5;
@Override
public void run() {
try {
if(i == 5){
Thread.sleep(2);// 设置小点,才能看出来
i -- ;
System.out.println(Thread.currentThread().getName() + ":i=5");
}
} catch (InterruptedException e) {
e.printStackTrace();
} }
}
以上的输出结果:
因为不是线程安全的,所以在线程Thread-A、Thread-B、Thread-C执行的时候都可能:i==5。即可能Thread-A在判断完if后,就把锁交给Thread-B了,此时Thread-B的if还是true。(这正好说明了线程间的并发,轮换执行。宏观上看着是一起执行的,因为轮换调度时间很短)
2、线程安全
/** 线程安全 */
class Security implements Runnable{
private int i = 5;
@Override
public void run() {
try {
synchronized (this){
if(i == 5){
Thread.sleep(2);
i -- ;
System.out.println(Thread.currentThread().getName() + ":i=5");
}
} } catch (InterruptedException e) {
e.printStackTrace();
} }
}
以上demo,必定只会输出1次。
七、阻塞、死锁
阻塞:线程A得到锁,线程B、C在等待。则对线程B、C是阻塞的。
死锁:所有线程都在等待其它线程释放锁。(我在写oracle触发器的时候遇到过,事务A在等待事务B提交,事务B也在等待事务A提交。)
八、参考(以下是当初学习整理的博客文章,我也没认真看完,都是慢慢理解一点)
ps: 周末沉迷手游无法自拔,有几篇想总结的一直没写…而且,本来以为这篇会写很多,但回头一看,还是不知道自己写了些什么。哪来这么多时间年让我慢慢来了?
手头项目要炸了,事前没有需求调查分析、需求转开发设计,有3张核心表都是1对1对1的,现在需求改要改成1对多、1对多。而且公司领导来一句2-3天能改好不,我就呵呵了。
而且、而且、而且,这项目负责人居然被公司领导调到别的项目组了。我这项目加上我才3个人,还是3个新人,2个代码1个运维。
本汪的内心不是崩溃的,早就被这些公司折磨碎了,心好累…好想回到农村,当一只骄傲的中华田园犬!
【Thread】java线程之对象锁、类锁、线程安全的更多相关文章
- (转)java 多线程 对象锁&类锁
转自:http://blog.csdn.net/u013142781/article/details/51697672 最近工作有用到一些多线程的东西,之前吧,有用到synchronized同步块,不 ...
- 关于Java中的对象、类、抽象类、接口、继承之间的联系
关于Java中的对象.类.抽象类.接口.继承之间的联系: 导读: 寒假学习JavaSE基础,其中的概念属实比较多,关联性也比较大,再次将相关的知识点复习一些,并理顺其中的关系. 正文: 举个例子:如果 ...
- 线程安全-002-多个线程多把锁&类锁
一.多个对象多把锁 例子代码: package com.lhy.thread01; public class MultiThread { //static private int num = 0; / ...
- synchronized关键字以及实例锁 类锁
Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this ...
- java双重检测或枚举类实现线程安全单例(懒汉模式)
双重检测实现 /** * 懒汉模式->双重同步锁单例模式 */ public class SingletonExample5 { private SingletonExample5() { } ...
- Java并发编程-并发工具类及线程池
JUC中提供了几个比较常用的并发工具类,比如CountDownLatch.CyclicBarrier.Semaphore. CountDownLatch: countdownlatch是一个同步工具类 ...
- 详解Java多线程编程中LockSupport类的线程阻塞用法
LockSupport类是Java6(JSR166-JUC)引入的一个类,提供了基本的线程同步原语.LockSupport实际上是调用了Unsafe类里的函数,归结到Unsafe里,只有两个函数: p ...
- Java第二章----对象和类
从第一章到第二章整整隔了一个月的时间,这速度也是慢的无语了.因为这个月负责开发公司一个SaaS类型APP,忙的昏天暗地终于上线了,这才有时间写个博客.本章还是以概念为主,有点枯燥重在理解. 第一节:对 ...
- java基础(二) -对象和类
Java 对象和类 Java作为一种面向对象语言.支持以下基本概念: 多态 继承 封装 抽象 类 对象 实例 方法 重载 对象:对象是类的一个实例(对象不是找个女朋友),有状态和行为.例如,一条狗是一 ...
- java中的对象、类、包、模块、组件、容器、框架、架构的概念入门
在Java中有那么一些概念:对象.类.包.模块.组件.容器.框架.这些概念都有一个共同的特点,就是[容纳]. 对象(Object) 在Java的世界里,对象是通过属性和方法来分别对应事务所具有的静态属 ...
随机推荐
- 《Python学习手册 第五版》 -第12章 if测试和语法规则
本章节的内容,主要讲解if语句,if语句是三大复合语句之一(其他两个是while和for),能处理编程中大多数逻辑运算 本章的重点内容如下: 1.if语句的基本形式(多路分支) 2.布尔表达式 3.i ...
- Kittenblock画笔基础,移动留下痕迹的蝴蝶,图形化编程经验分享
Kittenblock画笔基础,移动留下痕迹的蝴蝶,图形化编程经验分享 跟很多学生聊过,很多学生不是不努力,只是找不到感觉.有一点不可否认,同样在一个教室上课,同样是一个老师讲授,学习效果迥然不同.关 ...
- 大数据篇:Zookeeper
Zookeeper 1 Zookeeper概念 Zookeeper是什么 是一个基于观察者设计模式的分布式服务管理框架,它负责和管理需要关心的数据,然后接受观察者的注册,一旦这些数据的状态发生变化,Z ...
- 浅谈JSONP 的工作原理
小编最近在工作中经常用到 jsonp 这个东西, 表示之前从来没用过 最近稍微研究了下 当然很多内容来源于网上 收集整理 你懂的 ~~~ 话说我们访问一个页面的时候 需要像另一个网站获取部分信息, ...
- ASP.NET Core MVC 中实现中英文切换
哈喽..大家好 很久没有更新了,今天就来一篇最近开发用到的功能,那就是中英文切换,这个实际上也不是高大上,先说一下原理,在.NET Core框架中给我们提供了全球化的类,叫做Localization, ...
- Hapi+MySql项目实战路由初始化(二)
配置路由规则 将路由文件放在routes文件夹里,修改‘Server.js’文件,增加如下代码: 我们这里指明了require('./routes') routes文件夹,require可以文件但是不 ...
- filebeat+kafka
kafka出现接收不到filebeat数据,最后发现版本兼容问题 filebeat换成 filebeat-7.4.2-linux-x86_64 kafka是docker-compose启动的,版本是 ...
- k8s系列---EFK日志系统
文章拷于:http://blog.itpub.net/28916011/viewspace-2216748/ 用于自己备份记录错误 一个完整的k8s集群,应该包含如下六大部分:kube-dns.i ...
- Tomcat 配置2 tomcat-users.xml
Tomcat的配置 Tomcat的主要配置文件有3个,分别是: Tomcat-users.xml. web.xml server.xml. 配置Tomcat-users.xml 该文 ...
- git rm -r --cached解决已提交的文件在.gitignore中加入忽略后无效的问题。
有时候,发现有不该提交的文件已经提交后,仅仅在.gitignore中加入忽略是不行的.这个时候需要执行: git rm -r --cached 文件/文件夹名字 去掉已经托管的文件,然后重新提交: g ...