1、如下代码

 1 public class TestSync1 implements Runnable {
2 Timer1 timer = new Timer1();
3 public static void main(String[] args) {
4 TestSync1 test = new TestSync1();
5 Thread t1 = new Thread(test);
6 Thread t2 = new Thread(test);
7 t1.setName("t1");
8 t2.setName("t2");
9 t1.start();
10 t2.start();
11 }
12
13 public void run() {
14 timer.add(Thread.currentThread().getName());
15 }
16
17 }
18
19 class Timer1 {
20 private static int num = 0;
21 //int num = 0;
22 public void add(String name) {
23 //synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了
24 num++; //这一段的执行过程被打断了
25 try {
26 Thread.sleep(1000);
27 } catch (InterruptedException e) {
28 e.printStackTrace();
29 }
30 System.out.println(name + ", 你是第" + num + "个使用timer的线程");
31 //}
32 }

结果:

D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSync1
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程

2、解决:加锁

 1 public class TestSync1 implements Runnable {
2 Timer1 timer = new Timer1();
3 public static void main(String[] args) {
4 TestSync1 test = new TestSync1();
5 Thread t1 = new Thread(test);
6 Thread t2 = new Thread(test);
7 t1.setName("t1");
8 t2.setName("t2");
9 t1.start();
10 t2.start();
11 }
12
13 public void run() {
14 timer.add(Thread.currentThread().getName());
15 }
16
17 }
18
19 class Timer1 {
20 private static int num = 0;
21 //int num = 0;
22 public void add(String name) {
23 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了
24 num++;
25 try {
26 Thread.sleep(1000);
27 } catch (InterruptedException e) {
28 e.printStackTrace();
29 }
30 System.out.println(name + ", 你是第" + num + "个使用timer的线程");
31 }
32 }

结果:

D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSync1
t2, 你是第1个使用timer的线程
t1, 你是第2个使用timer的线程

分析一下1、2中的内存情况:只有一个test实例,该实例包含一个timer实例变量,因此代码中的private static int num = 0;换成int num = 0;对结果没有任何影响

接着,我们考虑如下这段代码,即在上述例子基础上新增了一个test实例,即共有test1、test2两个实例:

 1 public class TestSync1 implements Runnable {
2 Timer1 timer = new Timer1();
3 public static void main(String[] args) {
4 TestSync1 test1 = new TestSync1();
5 TestSync1 test2 = new TestSync1();
6 Thread t1 = new Thread(test1);
7 Thread t2 = new Thread(test2);
8
9 t1.setName("t1");
10 t2.setName("t2");
11 t1.start();
12 t2.start();
13 }
14
15 public void run() {
16 timer.add(Thread.currentThread().getName());
17 }
18
19 }
20
21 class Timer1 {
22 //private static int num = 0;
23 int num = 0;
24 public void add(String name) {
25 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了
26 num++; //这一段的执行过程被打断了
27 try {
28 Thread.sleep(3000);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 System.out.println(name + ", 你是第" + num + "个使用timer的线程");
33 }
34 }

结果:

D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSync1
t1, 你是第1个使用timer的线程
t2, 你是第1个使用timer的线程

分析一下内存使用情况:有两个test实例:test1、test2,它们分别各自指向自己的实例变量timer

如果第23行改为,private static int num = 0;,则执行结果为

D:\聚划算\技术部\编程练习\TestThread\Sync>java Test
t1, 你是第2个使用timer的线程
t2, 你是第2个使用timer的线程

这是因为此时num为Timer的类变量,为所有实例所共有

简单改造一下,把Timer1 这个类相关的方法执行放到run方法里面,如下:

 1 public class TestSyncNew implements Runnable {
2 int num = 0;
3
4 public static void main(String[] args) {
5 TestSyncNew test1 = new TestSyncNew();
6 //TestSyncNew test2 = new TestSyncNew();
7 Thread t1 = new Thread(test1);
8 Thread t2 = new Thread(test1);
9 //Thread t2 = new Thread(test2);
10
11 t1.setName("t1");
12 t2.setName("t2");
13 t1.start();
14 t2.start();
15 }
16
17 public void run() {
18 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了
19 num++; //这一段的执行过程被打断了
20 try {
21 Thread.sleep(1000);
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25 System.out.println(Thread.currentThread().getName() + ", 你是第" + num + "个使用timer的线程");
26 }
27 }
28 }

结果为:

D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSyncNew
t1, 你是第1个使用timer的线程
t2, 你是第2个使用timer的线程

 1 public class TestSyncNew implements Runnable {
2 int num = 0;
3
4 public static void main(String[] args) {
5 TestSyncNew test1 = new TestSyncNew();
6 TestSyncNew test2 = new TestSyncNew();
7 Thread t1 = new Thread(test1);
8 //Thread t2 = new Thread(test1);
9 Thread t2 = new Thread(test2);
10
11 t1.setName("t1");
12 t2.setName("t2");
13 t1.start();
14 t2.start();
15 }
16
17 public void run() {
18 synchronized(this) { //锁定当前对象:执行大括号之间语句的过程中,一个线程执行的过程中,不会被另一个线程打断;锁定当前对象,当然成员变量也被锁定了
19 num++; //这一段的执行过程被打断了
20 try {
21 Thread.sleep(1000);
22 } catch (InterruptedException e) {
23 e.printStackTrace();
24 }
25 System.out.println(Thread.currentThread().getName() + ", 你是第" + num + "个使用timer的线程");
26 }
27 }
28 }

结果为:

D:\聚划算\技术部\编程练习\TestThread\Sync>java TestSyncNew
t1, 你是第1个使用timer的线程
t2, 你是第1个使用timer的线程

如上的两个例子内存空间分别如下的左右两边所示,例子1

解释结果:参考了http://jasshine.iteye.com/blog/1617813
例子1:
因为加了synchronzied,实现了同步,并且该对象锁对应的对象只有一个,那就是test1,所以当第一个线程锁住了test1,而第二个线程里面也是通过test1去访问run()方法,所以必须等第一个线程执行完对象的方法时才能获得对象锁。因此必须隔1秒钟才能执行当前线程

例子2:

因为此时每个线程都是通过不同的对象去访问run()方法,一个为test1,另外一个为test2,所以有2把对象锁,这2个对象锁毫不干,第一个线程锁住了test1,而第二个线程都是通过 
test2对象去访问的,所以仍然能访问该方法。

类锁:

 1 class TestSynchronized extends Thread {
2 public TestSynchronized(String name) {
3 super(name);
4 }
5
6 public synchronized static void prt() {
7 for (int i = 10; i < 20; i++) {
8 System.out.println(Thread.currentThread().getName() + " : " + i);
9 try {
10 Thread.sleep(100);
11 } catch (InterruptedException e) {
12 System.out.println("Interrupted");
13 }
14 }
15 }
16
17 public synchronized void run() {
18 System.out.println(Thread.currentThread().getName() + " in here");
19 /*
20 for (int i = 0; i < 10; i++) {
21 System.out.println(Thread.currentThread().getName() + " : " + i);
22 try {
23 Thread.sleep(100);
24 } catch (InterruptedException e) {
25 System.out.println("Interrupted");
26 }
27 } */
28 }
29 }
30
31 public class TestThread {
32 public static void main(String[] args) {
33 TestSynchronized t1 = new TestSynchronized("t1");
34 TestSynchronized t2 = new TestSynchronized("t2");
35 t1.start();
36
37 t1.prt();// (1)
38 t2.prt();// (2)
39
40 }
41 }

在代码(1)中,虽然是通过对象t1来调用prt()函数的,但由于prt()是静态的,所以调用它时不用经过任何对象,它所属的线程为main线程。

由于调用run()函数取走的是对象锁,而调用prt()函数取走的是class锁,所以同一个线程t1(由上面可知实际上是不同线程)调用run()函数且还没完成run()函数时,它就能调用prt()函数。但prt()函数只能被一个线程调用,如代码(1)和代码(2),即使是两个不同的对象也不能同时调用prt()。

结果:

D:\聚划算\技术部\编程练习\TestThread\Sync>java TestThread
main : 10
t1 in here  //如果注释掉35行 t1.start();,则该行不打印
main : 11
main : 12
main : 13
main : 14
main : 15
main : 16
main : 17
main : 18
main : 19
main : 10
main : 11
main : 12
main : 13
main : 14
main : 15
main : 16
main : 17
main : 18
main : 19

 上面的一个程序足以说明同步方法、对象锁和类锁的概念了。总结一下: 

1. java中的每个对象都有一个锁,当访问某个对象的synchronized方法时,表示将该对象上锁,此时其他任何线程都无法在去访问该syncronized 方法了,直到之前的那个线程执行方法完毕后,其他线程才有可能去访问该synchronized方法。 

2.如果一个对象有多个synchronized方法,某一时刻某个线程已经进入到某个synchronzed方法,那么在该方法没有执行完毕前,其他线程无法访问该对象的任何synchronzied 方法的,但可以访问非synchronzied方法。 

3.如果synchronized方法是static的,那么当线程访问该方法时,它锁的并不是synchronized方法所在的对象,而是synchuronized方法所在对象的对应的Class对象(类锁), 
因为java中无论一个类有多少个对象,这些对象会对应唯一一个Class 对象,因此当线程分别访问同一个类的两个对象的static,synchronized方法时,他们的执行也是按顺序来的,也就是说一个线程先执行,一个线程后执行。

JAVA多线程(2)——锁(对象锁和类锁)的更多相关文章

  1. 编程开发之--java多线程学习总结(3)类锁

    2.使用方法同步 package com.lfy.ThreadsSynchronize; /** * 1.使用同步方法 * 语法:即用 synchronized 关键字修饰方法(注意是在1个对象中用锁 ...

  2. “全栈2019”Java多线程第三十章:尝试获取锁tryLock()方法详解

    难度 初级 学习时间 10分钟 适合人群 零基础 开发语言 Java 开发环境 JDK v11 IntelliJ IDEA v2018.3 文章原文链接 "全栈2019"Java多 ...

  3. 从火箭发场景来学习Java多线程并发闭锁对象

    从火箭发场景来学习Java多线程并发闭锁对象 倒计时器场景 在我们开发过程中,有时候会使用到倒计时计数器.最简单的是:int size = 5; 执行后,size—这种方式来实现.但是在多线程并发的情 ...

  4. Java基础-IO流对象之字符类(FileWrite与FileReader)

    Java基础-IO流对象之字符类(FileWrite与FileReader) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.常见编码简介 1>ASCII 我们知道计算机是 ...

  5. Java基础-IO流对象之File类

    Java基础-IO流对象之File类 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.IO技术概述 回想之前写过的程序,数据都是在内存中,一旦程序运行结束,这些数据都没有了,等下 ...

  6. Java多线程操作同一个对象,线程不安全

    Java多线程操作同一个对象 发现问题:多个线程操作同一资源的情况下,线程不安全,数据紊乱 代码: package multithreading; // Java多线程操作同一个对象 // 买火车票的 ...

  7. Java多线程之释放对象的锁

          由于等待一个锁定线程只有在获得这把锁之后,才能恢复运行,所以让持有锁的线程在不需要锁的时候及时释放锁是很重要的.在以下情况下,持有锁的线程会释放锁: 1. 执行完同步代码块. 2. 在执行 ...

  8. Java 多线程之哪个对象才是锁?

    问题背景 在感觉正常的使用ArrayList的迭代删除的操作的时候,发现了如下的崩溃日志: Caused by: java.util.ConcurrentModificationException a ...

  9. java的synchronized有没有同步的类锁?

    转自:http://langgufu.iteye.com/blog/2152608 http://www.cnblogs.com/beiyetengqing/p/6213437.html 没有... ...

  10. java 多线程:Callable接口;FutureTask类实现对象【Thread、Runnable、Callable三种方式实现多线程的区别】

    Callable接口介绍: Java5开始,Java提供了Callable接口,像是Runnable接口的增强版,Callable接口提供了一个 call()方法可以作为线执行体. call()方法比 ...

随机推荐

  1. 带你掌握利用Terraform不同数据源扩展应用场景

    本文分享自华为云社区<利用Terraform不同数据源扩展应用场景>,作者: kaliarch . 一 背景 在生产环境中使用Terraform进行基础设施编排,通常又一些信息是通过其他外 ...

  2. PostgreSQL 10 文档: PostgreSQL 客户端工具

    PostgreSQL 客户端应用   这部份包含PostgreSQL客户端应用和工具的参考信息.不是所有这些命令都是通用工具,某些需要特殊权限.这些应用的共同特征是它们可以被运行在任何主机上,而不管数 ...

  3. XCTF-favorite_number

    题目 (xctf.org.cn) 判断,既要数组强等于,又要首元素不等:php有下标越界溢出漏洞,因此0可以通过整型溢出得到 绕过if判断 playload:stuff[4294967296]=adm ...

  4. python=2.7-not available from current channels

    现象 使用miniconda3创建python2的环境 Collecting package metadata (current_repodata.json): done Solving enviro ...

  5. SQL-去除最大值与最小值求均值的问题

    背景 今天有同事问我一道关于数据库SQL的面试题,我刚开始随便给了一个思路,后来思索发现这个思路有漏洞,于是总结下来,仅供参考. 问题: 薪水表中是员工薪水的基本信息,包括雇员编号,和薪水,查询除去最 ...

  6. php批量同步数据

    php批量同步流程 首先分页获取数据 创建临时表 批量添加数据 备份原表 删除原表 修改临时表表名改为原表 代码 1 <?php 2 3 class Stock{ 4 5 private $da ...

  7. 应用性能监控工具(pinpoint)部署

    Pinpoint是一款全链路分析工具,提供了无侵入式的调用链监控.方法执行详情查看.应用状态信息监控等功能.pinpoint使用HBASE储存数据. 下面介绍pinpoint部署及应用. 1.  安装 ...

  8. win10右键添加打开cmd窗口的命令

    创建文本文档,复制如下内容: Windows Registry Editor Version 5.00 [HKEY_LOCAL_MACHINE\SOFTWARE\Classes\Directory\b ...

  9. 一个可将执行文件打包成Windows服务的.Net开源工具

    Windows服务一种在后台持续运行的程序,它可以在系统启动时自动启动,并在后台执行特定的任务,例如监视文件系统.管理硬件设备.执行定时任务等. 今天推荐一个可将执行文件打包成Windows 服务的工 ...

  10. 6、Spring之基于xml的自动装配

    6.1.场景模拟 6.1.1.创建UserDao接口及实现类 package org.rain.spring.dao; /** * @author liaojy * @date 2023/8/5 - ...