JAVA中线程同步的方法(7种)汇总
JAVA中线程同步的方法(7种)汇总
同步的方法:
一、同步方法
二、同步代码块
synchronized(object){ }
package com.xhj.thread;/*** 线程同步的运用* * @author XIEHEJUN* */public class SynchronizedThread {class Bank {private int account = 100;public int getAccount() {return account;}/*** 用同步方法实现* * @param money*/public synchronized void save(int money) {account += money;}/*** 用同步代码块实现* * @param money*/public void save1(int money) {synchronized (this) {account += money;}}}
class NewThread implements Runnable {private Bank bank;public NewThread(Bank bank) {this.bank = bank;}@Overridepublic void run() {for (int i = 0; i < 10; i++) {// bank.save1(10);bank.save(10);System.out.println(i + "账户余额为:" + bank.getAccount());}}}/*** 建立线程,调用内部类*/public void useThread() {Bank bank = new Bank();NewThread new_thread = new NewThread(bank);System.out.println("线程1");Thread thread1 = new Thread(new_thread);thread1.start();System.out.println("线程2");Thread thread2 = new Thread(new_thread);thread2.start();}public static void main(String[] args) {SynchronizedThread st = new SynchronizedThread();st.useThread();}}
示例加讲解
同步是多线程中的重要概念。同步的使用可以保证在多线程运行的环境中,程序不会产生设计之外的错误结果。同步的实现方式有两种,同步方法和同步块,这两种方式都要用到synchronized关键字。
同步方法:给一个方法增加synchronized修饰符之后就可以使它成为同步方法,这个方法可以是静态方法和非静态方法,但是不能是抽象类的抽象方法,也不能是接口中的接口方法。下面代码是一个同步方法的示例:
public synchronized void aMethod() { // do something } public static synchronized void anotherMethod() { // do something }
线程在执行同步方法时是具有排它性的。当任意一个线程进入到一个对象的任意一个同步方法时,这个对象的所有同步方法都被锁定了,在此期间,其他任何线程都不能访问这个对象的任意一个同步方法,直到这个线程执行完它所调用的同步方法并从中退出,从而导致它释放了该对象的同步锁之后。在一个对象被某个线程锁定之后,其他线程是可以访问这个对象的所有非同步方法的。
同步块:同步块是通过锁定一个指定的对象,来对同步块中包含的代码进行同步;而同步方法是对这个方法块里的代码进行同步,而这种情况下锁定的对象就是同步方法所属的主体对象自身。如果这个方法是静态同步方法呢?那么线程锁定的就不是这个类的对象了,也不是这个类自身,而是这个类对应的java.lang.Class类型的对象。同步方法和同步块之间的相互制约只限于同一个对象之间,所以静态同步方法只受它所属类的其它静态同步方法的制约,而跟这个类的实例(对象)没有关系。
如果一个对象既有同步方法,又有同步块,那么当其中任意一个同步方法或者同步块被某个线程执行时,这个对象就被锁定了,其他线程无法在此时访问这个对象的同步方法,也不能执行同步块。
synchronized 关键字用于保护共享数据。请大家注意“共享数据”,你一定要分清哪些数据是共享数据,请看下面的例子:
public class ThreadTest implements Runnable{public synchronized void run(){for(int i=0;i<10;i++) {System.out.print(" " + i);} }public static void main(String[] args) {Runnable r1 = new ThreadTest(); //也可写成ThreadTest r1 = new ThreadTest();Runnable r2 = new ThreadTest();Thread t1 = new Thread(r1);Thread t2 = new Thread(r2);t1.start();t2.start(); }}
在这个程序中,run()虽然被加上了synchronized 关键字,但保护的不是共享数据。因为这个程序中的t1,t2 是两个对象(r1,r2)的线程。而不同的对象的数据是不同的,r1,r2 有各自的run()方法,所以输出结果无法预知。
synchronized的目的是使同一个对象的多个线程,在某个时刻只有其中的一个线程可以访问这个对象的synchronized 数据。每个对象都有一个“锁标志”,当这个对象的一个线程访问这个对象的某个synchronized 数据时,这个对象的所有被synchronized 修饰的数据将被上锁(因为“锁标志”被当前线程拿走了),只有当前线程访问完它要访问的synchronized 数据时,当前线程才会释放“锁标志”,这样同一个对象的其它线程才有机会访问synchronized 数据。
示例3:
public class ThreadTest implements Runnable{public synchronized void run(){for(int i=0;i<10;i++){System.out.print(" " + i);} }public static void main(String[] args){Runnable r = new ThreadTest();Thread t1 = new Thread(r);Thread t2 = new Thread(r);t1.start();t2.start(); }}
如果你运行1000 次这个程序,它的输出结果也一定每次都是:0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9。因为这里的synchronized 保护的是共享数据。t1,t2 是同一个对象(r)的两个线程,当其中的一个线程(例如:t1)开始执行run()方法时,由于run()受synchronized保护,所以同一个对象的其他线程(t2)无法访问synchronized 方法(run 方法)。只有当t1执行完后t2 才有机会执行。
示例4:
public class ThreadTest implements Runnable{public void run(){synchronized(this){for(int i=0;i<10;i++){System.out.print(" " + i);} } }public static void main(String[] args){Runnable r = new ThreadTest();Thread t1 = new Thread(r);Thread t2 = new Thread(r);t1.start();t2.start(); } }
这个程序与示例3 的运行结果一样。在可能的情况下,应该把保护范围缩到最小,可以用示例4 的形式,this 代表“这个对象”。没有必要把整个run()保护起来,run()中的代码只有一个for循环,所以只要保护for 循环就可以了。
示例5:
public class ThreadTest implements Runnable{public void run(){for(int k=0;k<5;k++){System.out.println(Thread.currentThread().getName()+ " : for loop : " + k);}synchronized(this){for(int k=0;k<5;k++) {System.out.println(Thread.currentThread().getName()+ " : synchronized for loop : " + k);}} }public static void main(String[] args){Runnable r = new ThreadTest();Thread t1 = new Thread(r,"t1_name");Thread t2 = new Thread(r,"t2_name");t1.start();t2.start(); } }
运行结果:
t1_name : for loop : 0
t1_name : for loop : 1
t1_name : for loop : 2
t2_name : for loop : 0
t1_name : for loop : 3
t2_name : for loop : 1
t1_name : for loop : 4
t2_name : for loop : 2
t1_name : synchronized for loop : 0
t2_name : for loop : 3
t1_name : synchronized for loop : 1
t2_name : for loop : 4
t1_name : synchronized for loop : 2
t1_name : synchronized for loop : 3
t1_name : synchronized for loop : 4
t2_name : synchronized for loop : 0
t2_name : synchronized for loop : 1
t2_name : synchronized for loop : 2
t2_name : synchronized for loop : 3
t2_name : synchronized for loop : 4
第一个for 循环没有受synchronized 保护。对于第一个for 循环,t1,t2 可以同时访问。运行结果表明t1 执行到了k=2 时,t2 开始执行了。t1 首先执行完了第一个for 循环,此时t2还没有执行完第一个for 循环(t2 刚执行到k=2)。t1 开始执行第二个for 循环,当t1的第二个for 循环执行到k=1 时,t2 的第一个for 循环执行完了。t2 想开始执行第二个for 循环,但由于t1 首先执行了第二个for 循环,这个对象的锁标志自然在t1 手中(synchronized 方法的执行权也就落到了t1 手中),在t1 没执行完第二个for 循环的时候,它是不会释放锁标志的。所以t2 必须等到t1 执行完第二个for 循环后,它才可以执行第二个for 循环。
=====================================
三、wait与notify
wait():使一个线程处于等待状态,并且释放所持有的对象的lock。
sleep():使一个正在运行的线程处于睡眠状态,是一个静态方法,调用此方法要捕捉InterruptedException异常。
notify():唤醒一个处于等待状态的线程,注意的是在调用此方法的时候,并不能确切的唤醒某一个等待状态的线程,而是由JVM确定唤醒哪个线程,而且不是按优先级。
Allnotity():唤醒所有处入等待状态的线程,注意并不是给所有唤醒线程一个对象的锁,而是让它们竞争。
详细见:wait、notify、notifyAll的使用方法
四、使用特殊域变量(volatile)实现线程同步
//只给出要修改的代码,其余代码与上同class Bank {//需要同步的变量加上volatileprivate volatile int account = 100;public int getAccount() {return account;}//这里不再需要synchronized public void save(int money) {account += money;}}
五、使用重入锁实现线程同步
ReentrantLock() : 创建一个ReentrantLock实例 lock() : 获得锁 unlock() : 释放锁
//只给出要修改的代码,其余代码与上同class Bank {private int account = 100;//需要声明这个锁private Lock lock = new ReentrantLock();public int getAccount() {return account;}//这里不再需要synchronized public void save(int money) {lock.lock();try{account += money;}finally{lock.unlock();}}}
六、使用局部变量实现线程同步
ThreadLocal() : 创建一个线程本地变量 get() : 返回此线程局部变量的当前线程副本中的值 initialValue() : 返回此线程局部变量的当前线程的"初始值" set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
//只改Bank类,其余代码与上同public class Bank{//使用ThreadLocal类管理共享变量accountprivate static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){@Overrideprotected Integer initialValue(){return 100;}};public void save(int money){account.set(account.get()+money);}public int getAccount(){return account.get();}}
七、使用阻塞队列实现线程同步
前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。 使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。 本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步 LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。 队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~LinkedBlockingQueue 类常用方法 LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue put(E e) : 在队尾添加一个元素,如果队列满则阻塞 size() : 返回队列中的元素个数 take() : 移除并返回队头元素,如果队列空则阻塞代码实例: 实现商家生产商品和买卖商品的同步
注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:
add()方法会抛出异常
offer()方法返回false
put()方法会阻塞
7.使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
那么什么是原子操作呢?原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作即-这几种行为要么同时完成,要么都不完成。在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,使用该类可以简化线程同步。其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的
AtomicIntegeraddAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
代码实例:
只改Bank类,其余代码与上面第一个例子同
class Bank {private AtomicInteger account = new AtomicInteger(100);public AtomicInteger getAccount() {return account; } public void save(int money) {account.addAndGet(money);} }
补充--原子操作主要有:
对于引用变量和大多数原始变量(long和double除外)的读写操作;
对于所有使用volatile修饰的变量(包括long和double)的读写操作。
JAVA中线程同步的方法(7种)汇总相关推荐
- JAVA中线程同步的方法
1.wait方法. 2.notify方法和notifyAll方法. 3.synchronized关键字.
- JAVA中线程同步的几种实现方法
JAVA中线程同步的几种实现方法 一.synchronized同步的方法: 1.synchronized同步方法 即有synchronized关键字修饰的方法. 由于java的每个对象都有一个内置锁, ...
- Java中线程的创建有两种方式
Java中继承thread类与实现Runnable接口的区别 Java中线程的创建有两种方式: 1. 通过继承Thread类,重写Thread的run()方法,将线程运行的逻辑放在其中 2. 通过 ...
- Java 中线程同步锁和互斥锁
一 概述 1.1 互斥 所谓互斥,就是不同线程,通过竞争进入临界区(共享的数据和硬件资源),为了防止访问冲突,在有限的时间内只允许其中之一独占性的使用共享资源.如不允许同时写. 1.2 同步 同步关系 ...
- java实现线程同步的方法_Java实现线程同步方法及原理详解
一.概述 无论是什么语言,在多线程编程中,常常会遇到多个线同时操作程某个变量(读/写),如果读/写不同步,则会造成不符合预期的结果. 例如:线程A和线程B并发运行,都操作变量X,若线程A对变量X进行赋 ...
- java中实现同步的方法
java中同步有几种实现方法? 一般来说有六种实现方法: 使用synchronize关键字,可以是同步代码块,同步方法 使用wait和notify 使用关键字volatile 使用重入锁(Reentr ...
- Java 产生随机数:Java 中产生随机数的方法及应用汇总
文章目录 前言 一.System.currentTimeMillis() 方法 1.1.System.currentTimeMillis() 方法原理剖析 1.2.实现随机数加法程序 二.Math.r ...
- Java中线程同步锁和互斥锁有啥区别?看完你还是一脸懵逼?
首先不要钻概念牛角尖,这样没意义. 也许java语法层面包装成了sycnchronized或者明确的XXXLock,但是底层都是一样的.无非就是哪种写起来方便而已. 锁就是锁而已,避免多个线程对同一个 ...
- 同步方法 sleep和wait 线程同步的方法
当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法? 分两种情况 1):进入此对象的非同步方法 答案:可以 2):进入此对象的同步方法 答案:不可以 sle ...
最新文章
- 【牛腩新闻发布系统】开发前的准备01
- 区别:电感、磁珠和零欧电阻的作用
- 招聘|腾讯机器人实验室语义视觉方向(实习+社招)
- java swing 树_Java Swing JTree 树形
- spark.mllib:GradientBoostedTrees
- pandas.read_csv参数详解
- 游戏服务器架构:网络服务器端程序线程划分
- 漫步凸分析八——回收锥与无界
- Linux 下搭建jsp服务器(配置jsp开发环境)
- php 获取父类名称,[typecho]获取 父级分类 名称?
- 前端每日实战:151# 视频演示如何用纯 CSS 创作超能陆战队的大白
- 全网最快安装JDK17教程(windows版本)
- linux破解卡到抓包,请大家帮帮忙,真的是不会了,本来想做个抓包工具的,结果卡这了...
- 等距排列 html,【任务8-6】创建等距排列的4列式布局网页0806.html.pptx
- 第七周 项目2 - 建立链队算法库
- it行业se是_CS、IT、SE到底有什么区别?
- python语言程序设计基础上海交通大学_python语言程序设计基础第四章答案
- html投票器,自动投票器,自己制作教程!
- excel文件下载下来损坏 js_js文件操作之——导出Excel (js-xlsx)
- Maple: 矩阵转置