Java线程:同步

简介: 一 同步的概念   线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。   例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。   MyRunnable.

一 同步的概念

  线程的同步是为了防止多个线程访问一个数据对象时,对数据造成的破坏。

  例如:两个线程ThreadA、ThreadB都操作同一个对象Foo对象,并修改Foo对象上的数据。

  MyRunnable.java

 1 package Thread;
 2 public class MyRunnable implements Runnable{
 3     private Foo foo=new Foo();
 4     public static void main(String[] args){
 5         MyRunnable r=new MyRunnable();
 6         Thread ta=new Thread(r,"Thread-A");
 7         Thread tb=new Thread(r,"Thread-B");
 8         ta.start();
 9         tb.start();
10     }
11     public void run(){
12         for(int i=0;i<3;i++){
13             this.fix(30);
14             try{
15                 Thread.sleep(1);
16             }
17             catch(InterruptedException e){
18                 e.printStackTrace();
19             }
20             System.out.println(Thread.currentThread().getName()+":当前foo对象的值="+foo.getX());
21         }
22     }
23     public int fix(int y){
24         return foo.fix(y);
25     }
26 }
View Code

  Foo.java

 1 package Thread;
 2 public class Foo {
 3     private int x=100;
 4     public int getX(){
 5         return x;
 6     }
 7     public int fix(int y){
 8         x=x-y;
 9         return x;
10     }
11 }
View Code

  运行结果:

1 Thread-A:当前foo对象的值=40
2 Thread-B:当前foo对象的值=40
3 Thread-A:当前foo对象的值=-20
4 Thread-B:当前foo对象的值=-50
5 Thread-A:当前foo对象的值=-80
6 Thread-B:当前foo对象的值=-80
View Code

从结果看出,这样的输出值明显不合理。原因是两个线程不加控制的访问Foo对象并修改器数据所致。因此,应该对Foo的访问加以限制,每次只能有一个线程在访问。

 1 、同步方法

  线程的同步是保证多线程安全访问竞争资源的一种手段。 对于同步具体的Java代码中需要完成两个操作:

  1、把竞争访问的资源标识为private;

  2、同步哪些修改变量的代码,使用synchronized关键字同步方法或代码。(对于synchronized而言,只能标记非抽象方法,不能标识成员变量。)

  为了演示同步方法的使用,构建了一个信用卡账户,起初信用额度为100w,然后模拟透支、存款等操作。显然银行账户User对象是个竞争资源,而多个并发操作的是账户方法oper(int x),当然应该在此方法上加上同步,并将账户余额设为私有变量,禁止直接访问。

  BankTest.java

 1 package Thread;
 2 
 3 public class BankTest {
 4     public static void main(String[] args){
 5         User u=new User("小二",100);
 6         MyThread t1=new MyThread("线程1",u,20);
 7         MyThread t2=new MyThread("线程2",u,-60);
 8         MyThread t3=new MyThread("线程3",u,-80);
 9         MyThread t4=new MyThread("线程4",u,-30);
10         MyThread t5=new MyThread("线程5",u,32);
11         MyThread t6=new MyThread("线程6",u,21);
12         t1.start();
13         t2.start();
14         t3.start();
15         t4.start();
16         t5.start();
17         t6.start();
18     }
19 }
20 class MyThread extends Thread{
21     private User u;
22     private int y=0;
23     MyThread(String name,User u,int y){
24         super(name);
25         this.u=u;
26         this.y=y;
27     }
28     public void run(){
29         u.oper(y);
30     }
31 }
32 class User{
33     private String code;
34     private int cash;
35     User(String code,int cash){
36         this.code=code;
37         this.cash=cash;
38     }
39     public String getCode(){
40         return code;
41     }
42     /**
43      * 业务方法
44      * @param x 添加x万元
45      * */
46      public synchronized void oper(int x){
47          try{
48              Thread.sleep(10L);
49              this.cash+=x;
50              System.out.println(Thread.currentThread().getName()+"运行结果,增加"+x+",当前余额为:"+cash);
51              Thread.sleep(10L);
52          }
53          catch(InterruptedException e){
54              e.printStackTrace();
55          }
56      }
57      public String toString(){
58          return "User{" + "code=" + code + ",cash=" + cash +'}';
59      }
60 }
View Code

结果为:

1 线程1运行结果,增加20,当前余额为:120
2 线程2运行结果,增加-60,当前余额为:60
3 线程6运行结果,增加21,当前余额为:81
4 线程5运行结果,增加32,当前余额为:113
5 线程4运行结果,增加-30,当前余额为:83
6 线程3运行结果,增加-80,当前余额为:3

但是如果把同步关键字synchronized去掉,结果为:

1 线程2运行结果,增加-60,当前余额为:60
2 线程6运行结果,增加21,当前余额为:1
3 线程4运行结果,增加-30,当前余额为:3
4 线程5运行结果,增加32,当前余额为:3
5 线程1运行结果,增加20,当前余额为:60
6 线程3运行结果,增加-80,当前余额为:-20

显然是错误的,多个线程并发访问了竞争资源u,并对u的属性做了修改。可见同步的重要性。

2、同步块

  有时候,同步块比同步方法有更好的效果。在上个例子的基础上对oper方法做了改变,由同步方法改为同步块模式。

   BankTest.java

 1 public void oper(int x){
 2          try{
 3              Thread.sleep(10L);
 4              synchronized(this){
 5                  this.cash+=x;
 6                  System.out.println(Thread.currentThread().getName()+"运行结果,增加"+x+",当前余额为:"+cash);             
 7              }
 8              Thread.sleep(10L);
 9          }
10          catch(InterruptedException e){
11              e.printStackTrace();
12          }
13      }

结果和上例是一样的。具体的变化思想为:

1 public syncronized int getX(){
2    return x++;  //同步方法
3 }
45 public int getX(){
6   synchronized(this){     
7         return x;//非同步方法
8     }
9 }

注意:

  在使用synchronized时,应该避免在synchronized方法或synchronized块中使用sleep或yield方法。因为synchronized程序块占用着对象锁,你休息那么其他线程只能等待。这样效率不高。同样,在同步程序块内调用yield方法让出CPU资源也没有意义,因为你占用着锁,其他资源无法访问。

二、静态方法同步

  要同步静态方法,需要一个用整个类对象的锁,这个对象就是这个类(xxx.class),如:

1 public static synchronized int setName(String name){
2   xxx.name=name;  
3 }
4 等价于
5 public static int setName(){
6   synchronized(xxx.class){ 7 xxx.name=name; 8  } 9 }

三、何时需要同步

  1、多个线程同时访问互斥(可交换)数据时,应该同步保护数据,确保两个线程不会同时更改它。

  2、非静态字段中可更改的数据,通常用非静态方法访问。

  3、静态字段中可更改的数据,用静态方法访问。

 四、总结

  1、线程同步的目的是为了保护多个线程反问一个资源时对资源的破坏。

  2、线程同步方法是通过锁来实现,每个对象都仅有一个锁,这个锁与一个特定的对象关联,线程一旦获取了对象锁,其他访问该对象的线程无法再访问该对象的其他同步方法。

  3、对于静态同步方法,锁是针对这个类的,锁对象是该类的Class对象。静态和非静态方法的锁不干预。一个线程获得锁,当在一个同步方法中访问另外对象的同步方法时,会获取两个对象的锁。

  4、对于同步,要时刻清醒哪个对象上同步,这是关键。

当神已无能为力,那便是魔渡众生
目录
相关文章
|
1天前
|
存储 监控 Java
【Java并发】【线程池】带你从0-1入门线程池
欢迎来到我的技术博客!我是一名热爱编程的开发者,梦想是编写高端CRUD应用。2025年我正在沉淀中,博客更新速度加快,期待与你一起成长。 线程池是一种复用线程资源的机制,通过预先创建一定数量的线程并管理其生命周期,避免频繁创建/销毁线程带来的性能开销。它解决了线程创建成本高、资源耗尽风险、响应速度慢和任务执行缺乏管理等问题。
88 60
【Java并发】【线程池】带你从0-1入门线程池
|
4天前
|
安全 Java 开发者
Java并发迷宫:同步的魔法与死锁的诅咒
在Java并发编程中,合理使用同步机制可以确保线程安全,避免数据不一致的问题。然而,必须警惕死锁的出现,采取适当的预防措施。通过理解同步的原理和死锁的成因,并应用有效的设计和编码实践,可以构建出高效、健壮的多线程应用程序。
36 21
|
13天前
|
Java 程序员 开发者
Java社招面试题:一个线程运行时发生异常会怎样?
大家好,我是小米。今天分享一个经典的 Java 面试题:线程运行时发生异常,程序会怎样处理?此问题考察 Java 线程和异常处理机制的理解。线程发生异常,默认会导致线程终止,但可以通过 try-catch 捕获并处理,避免影响其他线程。未捕获的异常可通过 Thread.UncaughtExceptionHandler 处理。线程池中的异常会被自动处理,不影响任务执行。希望这篇文章能帮助你深入理解 Java 线程异常处理机制,为面试做好准备。如果你觉得有帮助,欢迎收藏、转发!
72 14
|
16天前
|
安全 Java 程序员
Java 面试必问!线程构造方法和静态块的执行线程到底是谁?
大家好,我是小米。今天聊聊Java多线程面试题:线程类的构造方法和静态块是由哪个线程调用的?构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节有助于掌握Java多线程机制。下期再见! 简介: 本文通过一个常见的Java多线程面试题,详细讲解了线程类的构造方法和静态块是由哪个线程调用的。构造方法由创建线程实例的主线程调用,静态块在类加载时由主线程调用。理解这些细节对掌握Java多线程编程至关重要。
48 13
|
17天前
|
安全 Java 开发者
【JAVA】封装多线程原理
Java 中的多线程封装旨在简化使用、提高安全性和增强可维护性。通过抽象和隐藏底层细节,提供简洁接口。常见封装方式包括基于 Runnable 和 Callable 接口的任务封装,以及线程池的封装。Runnable 适用于无返回值任务,Callable 支持有返回值任务。线程池(如 ExecutorService)则用于管理和复用线程,减少性能开销。示例代码展示了如何实现这些封装,使多线程编程更加高效和安全。
|
7天前
|
Java Shell 数据库
【YashanDB 知识库】kettle 同步大表提示 java 内存溢出
【问题分类】数据导入导出 【关键字】数据同步,kettle,数据迁移,java 内存溢出 【问题描述】kettle 同步大表提示 ERROR:could not create the java virtual machine! 【问题原因分析】java 内存溢出 【解决/规避方法】 ①增加 JVM 的堆内存大小。编辑 Spoon.bat,增加堆大小到 2GB,如: if "%PENTAHO_DI_JAVA_OPTIONS%"=="" set PENTAHO_DI_JAVA_OPTIONS="-Xms512m" "-Xmx512m" "-XX:MaxPermSize=256m" "-
|
1月前
|
监控 Java
java异步判断线程池所有任务是否执行完
通过上述步骤,您可以在Java中实现异步判断线程池所有任务是否执行完毕。这种方法使用了 `CompletionService`来监控任务的完成情况,并通过一个独立线程异步检查所有任务的执行状态。这种设计不仅简洁高效,还能确保在大量任务处理时程序的稳定性和可维护性。希望本文能为您的开发工作提供实用的指导和帮助。
116 17
|
2月前
|
Java
Java—多线程实现生产消费者
本文介绍了多线程实现生产消费者模式的三个版本。Version1包含四个类:`Producer`(生产者)、`Consumer`(消费者)、`Resource`(公共资源)和`TestMain`(测试类)。通过`synchronized`和`wait/notify`机制控制线程同步,但存在多个生产者或消费者时可能出现多次生产和消费的问题。 Version2将`if`改为`while`,解决了多次生产和消费的问题,但仍可能因`notify()`随机唤醒线程而导致死锁。因此,引入了`notifyAll()`来唤醒所有等待线程,但这会带来性能问题。
Java—多线程实现生产消费者
|
1月前
|
缓存 安全 算法
Java 多线程 面试题
Java 多线程 相关基础面试题
|
2月前
|
安全 Java Kotlin
Java多线程——synchronized、volatile 保障可见性
Java多线程中,`synchronized` 和 `volatile` 关键字用于保障可见性。`synchronized` 保证原子性、可见性和有序性,通过锁机制确保线程安全;`volatile` 仅保证可见性和有序性,不保证原子性。代码示例展示了如何使用 `synchronized` 和 `volatile` 解决主线程无法感知子线程修改共享变量的问题。总结:`volatile` 确保不同线程对共享变量操作的可见性,使一个线程修改后,其他线程能立即看到最新值。

热门文章

最新文章