Java多线程简析——Synchronized(同步锁)、Lock以及线程池

2023-10-27

Java多线程

Java中,可运行的程序都是有一个或多个进程组成。进程则是由多个线程组成的。

最简单的一个进程,会包括mian线程以及GC线程。

线程的状态

线程状态由以下一张网上图片来说明:


在图中,红框标识的部分方法,可以认为已过时,不再使用。

(1)wait、notify、notifyAll是线程中通信可以使用的方法。线程中调用了wait方法,则进入阻塞状态,只有等另一个线程调用与wait同一个对象的notify方法。这里有个特殊的地方,调用wait或者notify,前提是需要获取锁,也就是说,需要在同步块中做以上操作。

(2)join方法。该方法主要作用是在该线程中的run方法结束后,才往下执行。如以下代码:

package com.thread.simple;

public class ThreadJoin {

	
	public static void main(String[] args) {

		Thread thread= new Thread(new Runnable() {
			
			@Override
			public void run() {
				System.err.println("线程"+Thread.currentThread().getId()+" 打印信息");
			}
		});
		thread.start();
		
		try {
			thread.join();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		System.err.println("主线程打印信息");
		
	}

}
该方法显示的信息是:

线程8 打印信息

主线程打印信息

如果去掉其中的join方法,则显示如下:

主线程打印信息
线程8 打印信息

(3)yield方法。这个是线程本身的调度方法,使用时你可以在run方法执行完毕时,调用该方法,告知你已可以出让内存资源。

其他的线程方法,基本都会在日常中用到,如start、run、sleep,这里就不再介绍。

Synchronized(同步锁)

在Java中使用多线程,你就不能绕过同步锁这个概念。这在多线程中是十分重要的。

在Java多线程的使用中,你必然会遇到一个问题:多个线程共享一个或者一组资源,这资源包括内存、文件等。

很常见的一个例子是,张三在银行账户存有9999元,经过多次的取100,存100后,账户还有多少钱?

看代码:

以下表示账户信息:

package com.thread.simple;

import java.sql.Time;
import java.util.concurrent.TimeUnit;

public class Account {

	private String name;
	private float amt;
	public Account(String name,float amt) {
		this.name=name;
		this.amt=amt;
	}

	public  void  increaseAmt(float increaseAmt){
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		amt+=increaseAmt;
	}
	
	public  void decreaseAmt(float decreaseAmt){
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		amt-=decreaseAmt;
	}
	
	public void printMsg(){
		System.out.println(name+"账户现有金额为:"+amt);
	}
}

以下是我们操作账户的方法:

<span style="white-space:pre">		</span>final int NUM=100;
		
		Thread[] threads=new Thread[NUM];
		for(int i=0;i<NUM;i++){
			if(threads[i]==null){
				threads[i]=new Thread(new Runnable() {
					
					@Override
					public void run() {
						account.increaseAmt(100f);
						account.decreaseAmt(100f);
					}
				});
				threads[i].start();
			}
		}
		
		for(int i=0;i<NUM;i++){
			try {
				threads[i].join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		account.printMsg();
你会发现,每次打印出来的账户余额都不一定是一样的。这就是同步锁的必要性。

java中,提供了多种使用同步锁的方式。

(1)对动态方法的修饰。

作用的是调用该方法的对象(或者说对象引用)。

public synchronized void doSomething(){}

  ( 2)  对代码块的修饰。

作用的是调用该方法的对象(或者说对象引用)。

public  void  increaseAmt(float increaseAmt){
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		synchronized (this) {
			System.out.println(this);
			amt+=increaseAmt;
		}
	}

(3)对静态方法的修饰。

作用的是静态方法所在类的所有对象(或者说对象引用)。

public synchronized static  void  increaseAmt(float increaseAmt){
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		amt+=increaseAmt;
	}

(4)对类的修饰。

作用的是静态方法所在类的所有对象(或者说对象引用)。

	synchronized (AccountSynchronizedClass.class) {
			amt-=decreaseAmt;
		}

以修饰代码块的方式为例,我们重新运行以上代码后,得到了正确的结果。代码如下:

package com.thread.simple;

import java.util.concurrent.TimeUnit;
/**
 * Synchronized 代码块
 * @author 战国
 *
 */
public class AccountSynchronizedBlock {

	private String name;
	private float amt;
	public AccountSynchronizedBlock(String name,float amt) {
		this.name=name;
		this.amt=amt;
	}

	public  void  increaseAmt(float increaseAmt){
		
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		synchronized (this) {
			System.out.println(this);
			amt+=increaseAmt;
		}
	}
	
	public  void decreaseAmt(float decreaseAmt){
		try {
			TimeUnit.SECONDS.sleep(1);
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		synchronized (this) {
			System.out.println(this);
			amt-=decreaseAmt;
		}
		
	}
	
	public void printMsg(){
		System.out.println(name+"账户现有金额为:"+amt);
	}
}


//多线程synchronized修饰代码块 ,每次计算的值都一样
		
		final AccountSynchronizedBlock account=new AccountSynchronizedBlock("张三", 9999.0f);
		final int NUM=50;
		
		Thread[] threads=new Thread[NUM];
		for(int i=0;i<NUM;i++){
			if(threads[i]==null){
				threads[i]=new Thread(new Runnable() {
					
					@Override
					public void run() {
						account.increaseAmt(100f);
						account.decreaseAmt(100f);
					}
				});
				threads[i].start();
			}
		}
		
		for(int i=0;i<NUM;i++){
			try {
				threads[i].join();
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		account.printMsg();

以上是同步锁的简单说明。

在JDK5中,Java又引入了一个相似的概念Lock,也就是锁。功能与synchronized是类似的。

Lock

Lock对比synchronized有高手总结的差异如下:

总结来说,Lock和synchronized有以下几点不同:

  1)Lock是一个接口,而synchronized是Java中的关键字,synchronized是内置的语言实现;

  2)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生;而Lock在发生异常时,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁;

  3)Lock可以让等待锁的线程响应中断,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去,不能够响应中断;

  4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。

  5)Lock可以提高多个线程进行读操作的效率。

  在性能上来说,如果竞争资源不激烈,两者的性能是差不多的,而当竞争资源非常激烈时(即有大量线程同时竞争),此时Lock的性能要远远优于synchronized。所以说,在具体使用时要根据适当情况选择。

(参考http://www.cnblogs.com/dolphin0520/p/3923167.html)。

Lock的操作与synchronized相比,灵活性更高,而且Lock提供多种方式获取锁,有Lock、ReadWriteLock接口,以及实现这两个接口的ReentrantLock类、ReentrantReadWriteLock类。

对Lock的简单操作代码如下:

package com.thread.simple;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class LockImp {

	
	private Lock lock=new ReentrantLock();
	private ReadWriteLock rwLock=new ReentrantReadWriteLock();
	
	private List<Integer> list=new ArrayList<Integer>();
	
	public void doReentrantLock(Thread thread){
		lock.lock();
		System.out.println(thread.getName()+"获取锁");
		try {
			  for(int i=0;i<10;i++){
		        	list.add(i);
		        }
		} catch (Exception e) {
			
		}finally{
			lock.unlock();
			System.out.println(thread.getName()+"释放锁");
		}
        
	}
	public void doReentrantReadLock(Thread thread){
		rwLock.readLock().lock();
		System.out.println(thread.getName()+"获取读锁");
		try {
			for(int i=0;i<10;i++){
				list.add(i);
			}
		} catch (Exception e) {
			
		}finally{
			rwLock.readLock().unlock();
			System.out.println(thread.getName()+"释放读锁");
		}
		
	}
	public void doReentrantWriteLock(Thread thread){
		rwLock.writeLock().lock();
		System.out.println(thread.getName()+"获取写锁");
		try {
			for(int i=0;i<10;i++){
				list.add(i);
			}
		} catch (Exception e) {
			
		}finally{
			rwLock.writeLock().unlock();
			System.out.println(thread.getName()+"释放写锁");
		}
		
	}
	
	
	
	/**
	 * @param args
	 */
	public static void main(String[] args) {

		final LockImp lockImp=new LockImp();
		
		final Thread thread1=new Thread();
		final Thread thread2=new Thread();
		final Thread thread3=new Thread();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				lockImp.doReentrantLock(thread1);
			}
		}).start();
		
		new Thread(new Runnable() {
					
					@Override
					public void run() {
						lockImp.doReentrantLock(thread2);
					}
				}).start();
		
		new Thread(new Runnable() {
			
			@Override
			public void run() {
				lockImp.doReentrantLock(thread3);
			}
		}).start();
	
		
		lockImp.doReentrantReadLock(thread1);
		lockImp.doReentrantReadLock(thread2);
		lockImp.doReentrantReadLock(thread3);
		
		lockImp.doReentrantWriteLock(thread1);
		lockImp.doReentrantWriteLock(thread2);
		lockImp.doReentrantWriteLock(thread3);
	}

}

Lock的使用中,务必需要lock、unlock同时使用,避免死锁。

线程池的使用

为什么使用线程池?

因为使用它有好处:(1)在界面上,简化了写法,代码更简洁(2)对程序中的线程可以进行适度的管理(3)有效较低了多个线程的内存占有率等。

这是一篇讲述线程池非常好的文章:http://www.cnblogs.com/dolphin0520/p/3932921.html

如果对线程池有不了解的同学,可以参考链接中的文章,讲的深入浅出。

在这里只是简单的封装一个线程池的工具类,仅供参考:

package com.thread.simple;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolUtil {

	 private volatile static ThreadPoolUtil instance;
     private ThreadPoolUtil(){}
     private static ExecutorService threadPool;
     
     
     public static ThreadPoolUtil getInstance(){
    	 if(instance==null){
    		 synchronized (ThreadPoolUtil.class) {
				  instance=new ThreadPoolUtil();
				 threadPool=Executors.newCachedThreadPool();
			}
    	 }
    	 return instance;
     }
     
	public void excute(Runnable runnable){
		threadPool.execute(runnable);
	}
	
	public void shutdown(){
		threadPool.shutdown();
	}
	
	public boolean isActive(){
		if(threadPool.isTerminated()){
			return false;
		}
		return true;
	}
}




本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Java多线程简析——Synchronized(同步锁)、Lock以及线程池 的相关文章

随机推荐

  • 【手把手教你写服务器】客户端程序和服务器程序的简单实现

    文章目录 1 基本TCP客户 服务器程序的套接字函数 2 server c 3 client c 1 基本TCP客户 服务器程序的套接字函数 下图中各个函数的功能 参数及返回值自行查阅 UNIX网络编程卷1 套接字联网API 第4章 2 s
  • Python字符串与Bytes之间的互相转换

    Python字符串与Bytes之间的互相转换 byte转字符串 方式一 data b x31 x32 x33 print data b 123 strdata data decode gbk print strdata 123 strdat
  • .Net core基于xUnit的单元测试查看测试覆盖率

    写代码如何保证代码质量 基本大家都知道要做单元测试 那如何知道你单元测试是不是测试到了所有代码场景呢 这就要通过测试覆盖率来体现了 测试覆盖率 一般来说主要是Line代码行数覆盖率 同样还会有Branch分支覆盖率 Method方法覆盖率等
  • 使用QFrame类实现界面美化

    使用QFrame类实现界面美化 QFrame类是Qt框架中用于创建和显示矩形框架的基本组件 它可以用于美化界面 分割界面等多种场景 在Qt中使用QFrame类非常简单 我们只需要在ui文件中拖动一个QFrame控件并在代码中设置它的属性即可
  • 一刷总结!

    前面都还算顺利 走到贪心和动态规划的时候就感觉比较吃力了 就是那种怎样都感觉自己想不出来的 还需要多多练习和多多理解 有了这个监督之后 已经养成了每天要写算法的习惯 hh不错 希望能继续坚持下去 秋招能有一个好结果
  • Java-1.5

    题目描述 编写程序 计算 9 5 4 5 2 5 3 45 5 3 5 代码 法1 public class Calculate public static void main String args final double a 9 5
  • linux网络编程(三) TCP通信时序与多进程/线程并发服务器的编写

    文章目录 1 TCP通信时序 2 滑动窗口 TCP流量控制 3 出错处理封装函数 4 多进程并发服务器编写 5 多进程并发服务器编写 4 TCP状态转换 5 半关闭 6 2MSL 6 1 2MSL 6 2 端口复用 1 TCP通信时序 下图
  • 用python写注册登录界面web_用Python实现web端用户登录和注册功能

    这篇文章主要介绍了用Python实现web端用户登录和注册功能的教程 需要的朋友可以参考下 用户管理是绝大部分Web网站都需要解决的问题 用户管理涉及到用户注册和登录 用户注册相对简单 我们可以先通过API把用户注册这个功能实现了 RE M
  • GD32F303调试小记(零)之工程创建与编译

    前言 干这行的朋友都知道 真正拿单片机做项目时 作为软件编写人员 你所掌握的肯定不止一款单片机 又或者说你必须有能独立上手新单片机的能力 这里的新指的是对你个人来说是从未接触过的或者不熟悉的 而不一定是说这个单片机有多新 而调试一款新的单片
  • 二分类模型评价指标

    二分类模型指标 混淆矩阵 TP 实际为正预测为正 FP 实际为负但预测为正 TN 实际为负预测为负 FN 实际为正但预测为负 准确率 A c c u r a
  • kafka入门安装及消息发送接受初体验(附源码)

    这里是weihubeats 觉得文章不错可以关注公众号小奏技术 文章首发 拒绝营销号 拒绝标题党 官方文档 https kafka apache org quickstart 版本 3 5 0 安装 这里我们提供两种安装方式 一种是编译好的
  • Async异步处理【Springboot】

    简单实现异步处理 springboot异步处理 先建立一个controller目录 再建一个AsynController java 建立一个service目录 再建立一个AsynController java 启动项目 效果 1 在三秒以后
  • 电子科技大学软件工程期末复习笔记(四):软件设计

    目录 前言 重点一览 软件工程设计 软件设计定义 软件设计包含的两类活动 软件设计包涵 软件的质量属性 各种设计技术 程序结构 深度 宽度 扇入 扇出 完整的设计规格 软件体系架构 用户界面设计的3条原则 用户界面设计的3种分析 结构化设计
  • 5. zksnark 几种实现算法性能对比

    像 zk SNARK 这样的零知识证明有很多应用 Zcash利用零知识证明 来保护隐私 Coda和Mir利用零知识证明将整个区块链压缩到只有几K字节 0x和Matter则利用零知识证明将许多交易封装为以太坊 上的单一证明 1 可信设置 传统
  • Jumpserver 堡垒机

    一 Jumpserver简介 跳板机概述 跳板机就是一台服务器 开发或运维人员在维护过程中首先要统一登录到这台服务器 然后再登录到目标设备进行维护和操作 跳板机缺点 没有实现对运维人员操作行为的控制和审计 使用跳板机的过程中还是会出现误操作
  • unity的常见错误处理

    黄色提示 文件丢失 第一种情况 显示文件丢失 就去把文件拖到丢失的目录下 第二种情况 代码错误 无法识别 一般是调用的参数打错 红色提示 无法识别 当unity出现下列代码的情况时 we can t assign a new guid be
  • rabbitmq-给消息设置过期时间(九)

    TTL 全称 Time To Live 存活时间 过期时间 当消息到达存活时间后 还没有被消费 会被自动清除 RabbitMQ可以对消息设置过期时间 也可以对整个队列 Queue 设置过期时间 方式一 通过给队列配置属性设置消息的过期时间
  • js(art-­template、cookie、session、分页、注册、form、audio、video、移动端event、touch.js)

    art template 1 介绍 art template是一个简单且超快速的模板引擎 可通过范围内预先声明的技术优化模板渲染速度 从而实现接近JavaScript极限的运行时性能 同时它支持nodeJS和浏览器 2 基本使用 引入art
  • k8s之PV、PVC和StorageClass

    PV 什么是PV PV 描述的 则是一个具体的 Volume 的属性 比如 Volume 的类型 挂载目录 远程存储服务器地址等 创建PV 使用yaml来定义PV apiVersion v1 kind PersistentVolume me
  • Java多线程简析——Synchronized(同步锁)、Lock以及线程池

    Java多线程 Java中 可运行的程序都是有一个或多个进程组成 进程则是由多个线程组成的 最简单的一个进程 会包括mian线程以及GC线程 线程的状态 线程状态由以下一张网上图片来说明 在图中 红框标识的部分方法 可以认为已过时 不再使用