java线程同步以及对象锁和类锁解析(多线程synchronized关键字)
一、关于线程安全
1.是什么决定的线程安全问题?
线程安全问题基本是由全局变量及静态变量引起的。
若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。
2.可以解决多线程并发访问资源的方法有哪些?
主要有三种方式:分别是同步代码块 、同步方法和锁机制(Lock)
其中同步代码块和同步方法是通过关键字synchronized实现线程同步
本文主要是将synchronized关键字用法作为例子来去解释Java中的对象锁和类锁
二、synchronized关键字各种用法与实例
事实上,synchronized修饰非静态方法、同步代码块的synchronized (this)用法和synchronized (非this对象)的用法锁的是对象,线程想要执行对应同步代码,需要获得对象锁。
synchronized修饰静态方法以及同步代码块的synchronized (类.class)用法锁的是类,线程想要执行对应同步代码,需要获得类锁。
因此,事实上synchronized关键字可以细分为上面描述的五种用法。
1.同步块synchronized (this)
public class Ticket1 extends Thread{ private int nums = 0; //出票数 private int count =20; //剩余 @Override public void run() { while (true) { synchronized (this) { if(count <= 0) { break; } nums++; count--; try { Thread.sleep(550); } catch (Exception e) { e.printStackTrace(); } System.out.println("显示出票信息:"+Thread.currentThread().getName()+ "抢到第"+nums+"张票,剩余"+count+"张"); } } } public static void main(String[] args) { Ticket1 ticket1 = new Ticket1(); Thread anni = new Thread(ticket1,"安妮"); Thread jack = new Thread(ticket1,"jack"); anni.start(); jack.start(); } }
这样是同步的,线程获取的是同步块synchronized (this)括号()里面的对象实例的对象锁,这里就是Ticket1 实例对象的对象锁了。
需要注意的是synchronized (){}的{}前后的代码依旧是异步的
2.synchronized (非this对象)的用法锁的是对象
例1:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | public class Ticket2 implements Runnable{ private int nums = 0 ; //出票数 private int count = 25 ; //剩余 //实现线程安全三种方法 private final Object lock = new Object(); //1.使用私有不变对象锁,使得攻击者无法获取到锁对象(推荐使用) @Override public void run() { while ( true ) { //2.this 使用对象自身的锁(隐式锁)--对象锁 //3.Ticket2.class 给Ticket2加锁 --类锁->使用说明:静态方法则一定会同步,非静态方 //法需在单例模式才生效(本例为单例),但是也不能都用静态同步方法,总之用得不好可能会给性能带来极大的影响。 synchronized (lock) { if (count <= 0 ) { break ; } nums++; count--; try { Thread.sleep( 750 ); } catch (Exception e) { e.printStackTrace(); } System.out.println( "显示出票信息:" +Thread.currentThread().getName()+ "抢到第" +nums+ "张票,剩余" +count+ "张" ); } } } public static void main(String[] args) { Ticket2 ticket2 = new Ticket2(); Thread zhangsan = new Thread(ticket2, "张三" ); Thread zhaoyun = new Thread(ticket2, "赵云" ); zhangsan.start(); zhaoyun.start(); } } |
例2:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | public class Run2 { public static void main(String[] args) { Service service = new Service( "xiaobaoge" ); ThreadA2 a = new ThreadA2(service); a.setName( "A" ); a.start(); ThreadB2 b = new ThreadB2(service); b.setName( "B" ); b.start(); } } class Service { String anyString = new String(); public Service(String anyString){ this .anyString = anyString; } public void setUsernamePassword(String username, String password) { try { synchronized (anyString) { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入同步块" ); Thread.sleep( 3000 ); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开同步块" ); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA2 extends Thread { private Service service; public ThreadA2(Service service) { super (); this .service = service; } @Override public void run() { service.setUsernamePassword( "a" , "aa" ); } } class ThreadB2 extends Thread { private Service service; public ThreadB2(Service service) { super (); this .service = service; } @Override public void run() { service.setUsernamePassword( "b" , "bb" ); } } |
3.synchronized (class)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class Run { public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName( "A" ); a.start(); ThreadB b = new ThreadB(); b.setName( "B" ); b.start(); } } class Service { public static void printA() { synchronized (Service. class ) { try { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA" ); Thread.sleep( 3000 ); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA" ); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void printB() { synchronized (Service. class ) { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB" ); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB" ); } } } |
4.静态synchronized同步方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 | public class Run { public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName( "A" ); a.start(); ThreadB b = new ThreadB(); b.setName( "B" ); b.start(); } } class Service { synchronized public static void printA() { try { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printA" ); Thread.sleep( 3000 ); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printA" ); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "进入printB" ); System.out.println( "线程名称为:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "离开printB" ); } } class ThreadA extends Thread { @Override public void run() { Service.printA(); } } class ThreadB extends Thread { @Override public void run() { Service.printB(); } } |
5.synchronized修饰非静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef); athread.start(); ThreadB bthread = new ThreadB(numRef); bthread.start(); } } class HasSelfPrivateNum { private int num = 0 ; synchronized public void addI(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) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef) { super (); this .numRef = numRef; } @Override public void run() { super .run(); numRef.addI( "a" ); } } class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { super (); this .numRef = numRef; } @Override public void run() { super .run(); numRef.addI( "b" ); } } |
实验结论:两个线程访问同一个对象中的同步方法是一定是线程安全的。本实现由于是同步访问,所以先打印出a,然后打印出b
这里线程获取的是HasSelfPrivateNum的对象实例的锁——对象锁。
作者:鲁班快跑
出处:https://www.cnblogs.com/zhusf/p/10566293.html
版权:本文版权归作者和博客园共有
转载:您可以随意转载、摘录,但请在文章内注明作者和原文链接。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了