【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提交。)

八、参考(以下是当初学习整理的博客文章,我也没认真看完,都是慢慢理解一点)

Java线程:概念与原理

Java多线程释放锁

Java 线程讲解 - 系列

ps: 周末沉迷手游无法自拔,有几篇想总结的一直没写…而且,本来以为这篇会写很多,但回头一看,还是不知道自己写了些什么。哪来这么多时间年让我慢慢来了?

手头项目要炸了,事前没有需求调查分析、需求转开发设计,有3张核心表都是1对1对1的,现在需求改要改成1对多、1对多。而且公司领导来一句2-3天能改好不,我就呵呵了。

而且、而且、而且,这项目负责人居然被公司领导调到别的项目组了。我这项目加上我才3个人,还是3个新人,2个代码1个运维。

本汪的内心不是崩溃的,早就被这些公司折磨碎了,心好累…好想回到农村,当一只骄傲的中华田园犬!

时间: 2024-10-15 03:17:02

【Thread】java线程之对象锁、类锁、线程安全的相关文章

java对象锁&amp;类锁

首先,来看一段代码. 1 public class PersonSet { 2 3 private final Set<Person> mySet = new HashSet<Person>(); 4 5 public synchronized void add(Person p){ 6 mySet.add(p); 7 } 8 9 public synchronized boolean contains(Person p){ 10 return mySet.contains(p);

synchronized关键字以及实例锁 类锁

Java语言的关键字,当它用来修饰一个方法或者一个代码块的时候,能够保证在同一时刻最多只有一个线程执行该段代码. 一.当两个并发线程访问同一个对象object中的这个synchronized(this)同步代码块时,一个时间内只能有一个线程得到执行.另一个线程必须等待当前线程执行完这个代码块以后才能执行该代码块. 二.然而,当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块.

Java第二章----对象和类

从第一章到第二章整整隔了一个月的时间,这速度也是慢的无语了.因为这个月负责开发公司一个SaaS类型APP,忙的昏天暗地终于上线了,这才有时间写个博客.本章还是以概念为主,有点枯燥重在理解. 第一节:对象 名词解释 OOA-Object Oriented Analysis-面向对象分析 OOD-Object Oriented Design-面向对象设计 OOP-Object Oriented Programming-面向对象程序设计 面向对象基本特性 万物皆可为对象:任何一件事物都可以表示为程序中

java基础知识-对象和类

前言: 因为要准备Java面试,所有将java基础知识点重新复习一遍,主要笔记来源于菜鸟教程和java核心技术的书籍中,也有一些博客上的资料(这些只供我个人学习使用) Java 对象和类 对象:对象是类的一个实例,有状态和行为.例如,一条狗是一个对象,它的状态有:颜色.名字.品种:行为有:摇尾巴.叫.吃等. 类:类是一个模板,它描述一类对象的行为和状态. 下图中男孩女孩为类,而具体的每个人为该类的对象: 1.Java中的对象 现在让我们深入了解什么是对象.看看周围真实的世界,会发现身边有很多对象

java之常见对象-Object类

1.API概述 API(Application Programming Interface),应用程序编程接口. 编写一个机器人程序去控制机器人踢足球,程序就需要向机器人发出向前跑.向后跑.射门.抢球等各种命令,没有编过程序的人很难想象这样的程序如何编写.但是对于有经验的开发人员来说,知道机器人厂商一定会提供一些用于控制机器人的java类,这些类中定义好了操作机器人各种动作的方法.其实,这些java类就是机器人厂商提供给应用程序编程的接口,大家把这些类就称为应用程序编程接口. 2.java AP

java学习笔记(Core Java)4 对象与类

第四章 对象与类oop三个特征:对象的行为.状态.标识类之间关系:依赖,聚合,继承依赖:一个类的方法操纵另一个类的对象.(尽量减少这种耦合情况)聚合(has-a)一个类作为另一个类的变量而存在继承(is-a) 如果只声明一个类变量,而没有在堆空间中开辟没存,那么这个变量就不能调用类中的方法因为这个变量没有引用对象 !!一个对象的变量并没有实际包含一个对象,而仅仅引用了一个对象 Data data;等同于:Data *data; //它只代表一个指针,而不拥有对象数据 get/set 访问器 一个

浅谈java中的对象、类、与方法的重载

对象: 一切皆为对象. 对象包括两部分内容:属性(名词形容词),行为(动词). 对象和对象之间是有关系的: 派生,关联,依赖. 类: 对同一类别的众多对象的一种抽象. 类,还是用来生成对象的一种模板,对象是类的一种具体化的表现. 面向对象的三大特性:封装,继承,多态. ? 1 2 3 4 class 类名{ 访问修饰符 成员变量的定义; 访问修饰符 成员函数(方法)的定义; } 访问修改符:默认不写,private,public. private,私有.只能被当前class 类名{}中的代码访问

JAVA学习记录①——对象、类、属性、方法、构造方法的总结

对象——现实存在的手机(三星.苹果) 类——虚拟的手机(包含属性:5.0寸屏幕,2.1GHZCPU等,方法:能打电话,玩游戏) 属性:手机的配置(5.0寸屏幕,2.1GHZCPU) 方法:手机能做什么的(能打电话,玩游戏) 构造方法:用来给手机赋初值 具体展示在下面: /* * 文件一 * 这是类文件,用来保存手机的性质以及如何赋初值 * */ //这是类 public class Telphone { //这是属性,表示手机应该拥有什么 double screen; double cpu; /

Java对象锁和类锁全面解析(多线程synchronized关键字)

最近工作有用到一些多线程的东西,之前吧,有用到synchronized同步块,不过是别人怎么用就跟着用,并没有搞清楚锁的概念.最近也是遇到一些问题,不搞清楚锁的概念,很容易碰壁,甚至有些时候自己连用没用对都不知道. 今天把一些疑惑都解开了,写篇文章分享给大家,文章还算比较全面.当然可能有小宝鸽理解得不够深入透彻的地方,如果说得不正确还望指出. 看之前有必要跟某些猿友说一下,如果看一遍没有看明白呢,也没关系,当是了解一下,等真正使用到了,再回头看. 本文主要是将synchronized关键字用法作

synchronize——对象锁和类锁

最近在研究Java 多线程的只是,经常能看到synchronize关键字,以前只是一眼带过,没有细究,今天趁这个机会,整理下 synchronize作为多线程关键字,是一种同步锁,它可以修饰以下几种对象: 代码块:被修饰的代码块称为同步语句块,其作用的范围是大括号{ }里的代码,作用的对象是调用这个代码块的对象: 方法:被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象 静态方法:作用的范围是整个静态方法,作用的对象是这个类的所有对象 类:作用的范围是synchro