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. Linux下Oracle单实例配置多监听

    Oracle单实例配置多监听 一.前言 有时候我们项目中需要使用Oracle数据库,同时要需要不同的数据源,而Oracle不像Mysql那样直接建个库即可,Oracle是以账号为单位,可以理解为一个账 ...

  2. 【博客索引】Welcome!!

    欢迎来到 Daniel_yzy 的博客园 个人简介 初二,男,就读于长沙市一中双语实验学校. 爱好 OI,一生讨厌文化课. 当然,也是唯物主义无神论者. 已有 npy,要问是谁的话可以私下问. 博客索 ...

  3. VMware中的三种网络模式

    1.桥接模式网络 通过桥接模式网络连接,虚拟机中的虚拟网络适配器可连接到主机中的物理网络适配器.虚拟机可通过主机网络适配器连接到主机系统所用的 LAN.桥接模式网络连接支持有线和无线主机网络适配器. ...

  4. 表格JS实现在线Excel的附件上传与下载

    摘要:本文由葡萄城技术团队于博客园原创并首发.转载请注明出处:葡萄城官网,葡萄城为开发者提供专业的开发工具.解决方案和服务,赋能开发者. 前言 在本地使用Excel时,经常会有需要在Excel中添加一 ...

  5. 当 GPT-4 拥有了 Diff 视图,那真的是如虎添翼!

    目录 1. 当你要求 GPT-4 帮你写点代码时 2. 你需要的背景知识都在这里 2.1 关于 GoPool 和 DevChat 2.2 关于 GoPool 的工作原理 2.3 我想要让 taskQu ...

  6. 详情讲解canvas实现电子签名

    签名的实现功能 我们要实现签名: 1.我们首先要鼠标按下,移动,抬起.经过这三个步骤. 我们可以实现一笔或者连笔. 按下的时候我们需要移动画笔,可以使用 moveTo 来移动画笔. e.pageX,e ...

  7. cs50ai0----search

    cs50ai0-------Search cs50ai0-------Search 基础知识 课后题目 代码实践 学习链接 总结 基础知识 (1) search problem 上图是搜索问题的一般形 ...

  8. Linux学习疑惑总结

    重定向问题 Linux shell 中 2>&1 的含义 首先了解下1和2在Linux中代表什么,先整理一份在Linux系统中0 1 2是一个文件描述符: 名称 代码 操作符 Java中 ...

  9. ImGui界面优化:使用图标字体、隐藏主窗口标题栏

    目录 使用图标字体 扩展:内存加载字体 隐藏主窗口标题栏 增加程序退出 改进HideTabBar 窗口最大化 总结 本文主要介绍ImGui应用中的一些界面优化方法,如果是第一次使用ImGui推荐从上一 ...

  10. AI绘画StableDiffusion实操教程:冰霜旗袍美女

    飞书原文链接,获取更多资源:AI绘画StableDiffusion实操教程:冰霜旗袍美女 前几天分享了StableDiffusion的入门到精通教程:AI绘画:Stable Diffusion 终极炼 ...