Java线程同步方法实例总结
作者:喜欢特别冷的冬天下着雪 发布时间:2022-08-20 20:35:08
本文实例讲述了Java线程同步方法。分享给大家供大家参考,具体如下:
1. Semaphore
1.1 二进制Semaphore
Semaphore算是比较高级点的线程同步工具了,在许多其他语言里也有类似的实现。Semaphore有一个最大的好处就是在初始化时,可以显式的控制并发数。其内部维护这一个c计数器,当计数器小于等于0时,是不允许其他线程访问并发区域的,反之则可以,因此,若将并发数设置为1,则可以确保单一线程同步。下面的例子模拟多线程打印,每个线程提交打印申请,然后执行打印,最后宣布打印结束,代码如下:
import java.util.concurrent.Semaphore;
public class Program{
public static void main(String[] agrs){
PrintQueue p=new PrintQueue();
Thread[] ths=new Thread[10];
for(int i=0;i<10;i++){
ths[i]=new Thread(new Job(p),"Thread"+i);
}
for(int i=0;i<10;i++){
ths[i].start();
}
}
}
class PrintQueue{
private Semaphore s;
public PrintQueue(){
s=new Semaphore(1);//二进制信号量
}
public void printJob(Object document){
try{
s.acquire();
long duration=(long)(Math.random()*100);
System.out.printf("线程名:%s 睡眠:%d",Thread.currentThread().getName(),duration);
Thread.sleep(duration);
}
catch(InterruptedException e){
e.printStackTrace();
}
finally{
s.release();
}
}
}
class Job implements Runnable{
private PrintQueue p;
public Job(PrintQueue p){
this.p=p;
}
@Override
public void run(){
System.out.printf("%s:正在打印一个任务\n ",Thread.currentThread().getName());
this.p.printJob(new Object());
System.out.printf("%s:文件已打印完毕\n ",Thread.currentThread().getName());
}
}
执行结果如下:
Thread0:正在打印一个任务
Thread9:正在打印一个任务
Thread8:正在打印一个任务
Thread7:正在打印一个任务
Thread6:正在打印一个任务
Thread5:正在打印一个任务
Thread4:正在打印一个任务
Thread3:正在打印一个任务
Thread2:正在打印一个任务
Thread1:正在打印一个任务
线程名:Thread0 睡眠:32 Thread0:文件已打印完毕
线程名:Thread9 睡眠:44 Thread9:文件已打印完毕
线程名:Thread8 睡眠:45 Thread8:文件已打印完毕
线程名:Thread7 睡眠:65 Thread7:文件已打印完毕
线程名:Thread6 睡眠:12 Thread6:文件已打印完毕
线程名:Thread5 睡眠:72 Thread5:文件已打印完毕
线程名:Thread4 睡眠:98 Thread4:文件已打印完毕
线程名:Thread3 睡眠:58 Thread3:文件已打印完毕
线程名:Thread2 睡眠:24 Thread2:文件已打印完毕
线程名:Thread1 睡眠:93 Thread1:文件已打印完毕
可以看到,所有线程提交打印申请后,按照并发顺序一次执行,没有任何并发冲突,谁先获得信号量,谁就先执行,其他剩余线程均等待。这里面还有一个公平信号与非公平信号之说:基本上java所有的多线程工具都支持初始化的时候指定一个布尔变量,true时表明公平,即所有处于等待的线程被筛选的条件为“谁等的时间长就选谁进行执行”,有点first in first out的感觉,而false时则表明不公平(默认是不non-fairness),即所有处于等待的线程被筛选执行是随机的。这也就是为什么多线程往往执行顺序比较混乱的原因。
1.2 多重并发控制
若将上面的代码改为s=new Semaphore(3);//即让其每次可以并发3条线程
,则输出如下:
Thread0:正在打印一个任务
Thread9:正在打印一个任务
Thread8:正在打印一个任务
Thread7:正在打印一个任务
Thread6:正在打印一个任务
Thread5:正在打印一个任务
Thread3:正在打印一个任务
Thread4:正在打印一个任务
Thread2:正在打印一个任务
Thread1:正在打印一个任务
线程名:Thread9 睡眠:26线程名:Thread8 睡眠:46线程名:Thread0 睡眠:79 Thread9:文件已打印完毕
线程名:Thread7 睡眠:35 Thread8:文件已打印完毕
线程名:Thread6 睡眠:90 Thread7:文件已打印完毕
线程名:Thread5 睡眠:40 Thread0:文件已打印完毕
线程名:Thread3 睡眠:84 Thread5:文件已打印完毕
线程名:Thread4 睡眠:13 Thread4:文件已打印完毕
线程名:Thread2 睡眠:77 Thread6:文件已打印完毕
线程名:Thread1 睡眠:12 Thread1:文件已打印完毕
Thread3:文件已打印完毕
Thread2:文件已打印完毕
很明显已经并发冲突了。若要实现分组(每组3个)并发吗,则每一组也要进行同步,代码修改如下:
import java.util.concurrent.Semaphore;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Program{
public static void main(String[] agrs){
PrintQueue p=new PrintQueue();
Thread[] ths=new Thread[10];
for(int i=0;i<10;i++){
ths[i]=new Thread(new Job(p),"Thread"+i);
}
for(int i=0;i<10;i++){
ths[i].start();
}
}
}
class PrintQueue{
private Semaphore s;
private boolean[] freePrinters;
private Lock lock;
public PrintQueue(){
s=new Semaphore(3);//二进制信号量
freePrinters=new boolean[3];
for(int i=0;i<3;i++){
freePrinters[i]=true;
}
lock=new ReentrantLock();
}
public void printJob(Object document){
try{
s.acquire();
int printerIndex=getIndex();
long duration=(long)(Math.random()*100);
System.out.printf("线程名:%s 睡眠:%d\n",Thread.currentThread().getName(),duration);
Thread.sleep(duration);
freePrinters[printerIndex]=true;//恢复信号,供下次使用
}
catch(InterruptedException e){
e.printStackTrace();
}
finally{
s.release();
}
}
//返回一个内部分组后的同步索引
public int getIndex(){
int index=-1;
try{
lock.lock();
for(int i=0;i<freePrinters.length;i++){
if(freePrinters[i]){
freePrinters[i]=false;
index=i;
break;
}
}
}
catch(Exception e){
e.printStackTrace();
}
finally{
lock.unlock();
}
return index;
}
}
class Job implements Runnable{
private PrintQueue p;
public Job(PrintQueue p){
this.p=p;
}
@Override
public void run(){
System.out.printf("%s:正在打印一个任务\n ",Thread.currentThread().getName());
this.p.printJob(new Object());
System.out.printf(" %s:文件已打印完毕\n ",Thread.currentThread().getName());
}
}
其中getIndex()
方法主要为了维护内部分组后(支持并发3个)组内数据的同步(用lock来同步)。
输出如下:
Thread0:正在打印一个任务
Thread9:正在打印一个任务
Thread8:正在打印一个任务
Thread7:正在打印一个任务
Thread6:正在打印一个任务
Thread5:正在打印一个任务
Thread4:正在打印一个任务
Thread3:正在打印一个任务
Thread2:正在打印一个任务
Thread1:正在打印一个任务
线程名:Thread0 睡眠:82 打印机:0号
线程名:Thread8 睡眠:61 打印机:2号
线程名:Thread9 睡眠:19 打印机:1号
Thread9:文件已打印完毕
线程名:Thread7 睡眠:82 打印机:1号
Thread8:文件已打印完毕
线程名:Thread6 睡眠:26 打印机:2号
Thread0:文件已打印完毕
线程名:Thread5 睡眠:31 打印机:0号
Thread6:文件已打印完毕
线程名:Thread4 睡眠:44 打印机:2号
Thread7:文件已打印完毕
线程名:Thread3 睡眠:54 打印机:1号
Thread5:文件已打印完毕
线程名:Thread2 睡眠:48 打印机:0号
Thread4:文件已打印完毕
线程名:Thread1 睡眠:34 打印机:2号
Thread3:文件已打印完毕
Thread2:文件已打印完毕
Thread1:文件已打印完毕
2. CountDownLatch
CountDownLatch同样也是支持多任务并发的一个工具。它主要用于“等待多个并发事件”,它内部也有一个计数器,当调用await()
方法时,线程处于等待状态,只有当内部计数器为0时才继续(countDown()
方法来减少计数),也就说,假若有一个需求是这样的:主线程等待所有子线程都到达某一条件时才执行,那么只需要主线程await,然后在启动每个子线程的时候进行countDown操作。下面模拟了一个开会的例子,只有当所有人员都到齐了,会议才能开始。
import java.util.concurrent.CountDownLatch;
public class Program{
public static void main(String[] agrs){
//开启可容纳10人的会议室
VideoConference v=new VideoConference(10);
new Thread(v).start();
//参与人员陆续进场
for(int i=0;i<10;i++){
Participant p=new Participant(i+"号人员",v);
new Thread(p).start();
}
}
}
class VideoConference implements Runnable{
private CountDownLatch controller;
public VideoConference(int num){
controller=new CountDownLatch(num);
}
public void arrive(String name){
System.out.printf("%s 已经到达!\n",name);
controller.countDown();
System.out.printf("还需要等 %d 个成员!\n",controller.getCount());
}
@Override
public void run(){
try{
System.out.printf("会议正在初始化...!\n");
controller.await();
System.out.printf("所有人都到齐了,开会吧!\n");
}
catch(InterruptedException e){
e.printStackTrace();
}
}
}
class Participant implements Runnable{
private VideoConference conference;
private String name;
public Participant(String name,VideoConference conference){
this.name=name;
this.conference=conference;
}
@Override
public void run(){
long duration=(long)(Math.random()*100);
try{
Thread.sleep(duration);
conference.arrive(this.name);
}
catch(InterruptedException e){
}
}
}
输出:
会议正在初始化...!
0号人员 已经到达!
还需要等 9 个成员!
1号人员 已经到达!
还需要等 8 个成员!
9号人员 已经到达!
还需要等 7 个成员!
4号人员 已经到达!
还需要等 6 个成员!
8号人员 已经到达!
还需要等 5 个成员!
5号人员 已经到达!
还需要等 4 个成员!
6号人员 已经到达!
还需要等 3 个成员!
3号人员 已经到达!
还需要等 2 个成员!
7号人员 已经到达!
还需要等 1 个成员!
2号人员 已经到达!
还需要等 0 个成员!
所有人都到齐了,开会吧!
3. Phaser
import java.util.concurrent.Phaser;
import java.util.concurrent.TimeUnit;
import java.util.List;
import java.util.ArrayList;
import java.io.File;
import java.util.Date;
public class Program{
public static void main(String[] agrs){
Phaser phaser=new Phaser(3);
FileSearch system=new FileSearch("C:\\Windows", "log",phaser);
FileSearch apps=new FileSearch("C:\\Program Files","log",phaser);
FileSearch documents=new FileSearch("C:\\Documents And Settings","log",phaser);
Thread systemThread=new Thread(system,"System");
systemThread.start();
Thread appsThread=new Thread(apps,"Apps");
appsThread.start();
Thread documentsThread=new Thread(documents, "Documents");
documentsThread.start();
try {
systemThread.join();
appsThread.join();
documentsThread.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Terminated: "+ phaser.isTerminated());
}
}
class FileSearch implements Runnable{
private String initPath;
private String end;
private List<String> results;
private Phaser phaser;
public FileSearch(String initPath,String end,Phaser phaser){
this.initPath=initPath;
this.end=end;
this.results=new ArrayList<String>();
this.phaser=phaser;
}
private void directoryProcess(File file){
File[] files=file.listFiles();
if(files!=null){
for(int i=0;i<files.length;i++){
if(files[i].isDirectory()){
directoryProcess(files[i]);
}
else{
fileProcess(files[i]);
}
}
}
}
private void fileProcess(File file){
if(file.getName().endsWith(end)){
results.add(file.getAbsolutePath());
}
}
private void filterResults(){
List<String> newResults=new ArrayList<String>();
long actualDate=new Date().getTime();
for(int i=0;i<results.size();i++){
File file=new File(results.get(i));
long fileDate=file.lastModified();
if(actualDate-fileDate<TimeUnit.MILLISECONDS.convert(1,TimeUnit.DAYS)){
newResults.add(results.get(i));
}
}
results=newResults;
}
private boolean checkResults(){
if(results.isEmpty()){
System.out.printf("%s: Phase %d: 0 results.\n",Thread.currentThread().getName(),phaser.getPhase());
System.out.printf("%s: Phase %d: End.\n",Thread.currentThread().getName(),phaser.getPhase());
phaser.arriveAndDeregister();
}
else{
System.out.printf("%s: Phase %d: %d results.\n",Thread.currentThread().getName(),phaser.getPhase(),results.size());
phaser.arriveAndAwaitAdvance();
return true;
}
}
private void showInfo() {
for (int i=0; i<results.size(); i++){
File file=new File(results.get(i));
System.out.printf("%s: %s\n",Thread.currentThread().getName(),file.getAbsolutePath());
}
phaser.arriveAndAwaitAdvance();
}
@Override
public void run(){
File file=new File(initPath);
if(file.isDirectory()){
directoryProcess(file);
}
if(!checkResults()){
return;
}
filterResults();
if(!checkResults()){
return;
}
showInfo();
phaser.arriveAndDeregister();
System.out.printf("%s: Work completed.\n",Thread.currentThread().getName());
}
}
运行结果:
Apps: Phase 0: 4 results.
System: Phase 0: 27 results.
更多java相关内容感兴趣的读者可查看本站专题:《Java进程与线程操作技巧总结》、《Java数据结构与算法教程》、《Java操作DOM节点技巧总结》、《Java文件与目录操作技巧汇总》和《Java缓存操作技巧汇总》
希望本文所述对大家java程序设计有所帮助。
来源:https://blog.csdn.net/kkkkkxiaofei/article/details/19079259
猜你喜欢
- Java操作redis设置第二天凌晨过期场景在做查询数据的时候,遇到了需要设置数据在redis中第二天过期的问题,但是redis又没有对应的
- 有很多同学肯定想学习opencv相关的知识,但是有些情况下每建一次项目都要重新引入下各种文件是不是很苦恼,所以我也面临了这个问题,在网上看到
- 在本文中,我们将看到几个关于如何在Java 8中对List进行排序的示例。1.按字母顺序排序字符串列表List<String>
- public class MD5Check {/*** 默认的密码字符串组合,用来将字节转换成 16 进制表示的字符,apache校验下载的
- 父类空间优先于子类对象产生在每次创建子类对象时,先初始化父类空间,再创建其子类对象本身。目的在于子类对象中包含了其对应的父类空间,便可以包含
- 什么是Hystrix在日常生活用电中,如果我们的电路中正确地安置了保险丝,那么在电压异常升高时,保险丝就会熔断以便切断电流,从而起到保护电路
- 1.下载文件,将文件保存到本地。(只试用excel);2.对文件的标题进行检验;3.获取导入的批次(取一个表的一个值,加1);4.循环获取文
- SpringCloud是分布式微服务架构的一站式解决方案,十多种微服务架构落地技术的集合体,俗称微服务全家桶SpringCloud和Spri
- 简介我们在开发web应用的时候,有时候为了适应浏览器大小的调整,需要动态对页面的组件进行位置的调整。这时候就会用到flow layout,也
- 前言目前正在练手springboot+vue,因为很多步骤会遇到困难,当时查完资料解决,过一段时间就会忘记,所以决定建个系列记录下来。因为中
- 简单介绍下功能1.每隔一段时间(比如1分钟)在京东手机每日一秒杀页面提取产品(手机)链接。 http://sale.360buy.com/a
- 本文实例为大家分享了java代码获取新浪微博应用的access token的具体代码,供大家参考,具体内容如下package test;im
- Spring Web MVC是一种基于Java的实现了Web MVC设计模式的请求驱动类型的轻量级Web框架,即使用了MVC架构模式的思想,
- 1、局部变量在方法或语句块中定义的变量被称为局部变量。变量声明和初始化都是在方法中,方法结束后,变量就会自动销毁。局部变量声明在方法、构造方
- 由于近来学习java,遇到了一些在c++上没有的概念,将它记录下,以自己复习使用,如有不理解妥之处,望大家批评指导。资料均由网上经过自己整合
- Java 8新增了LocalDate和LocalTime接口,为什么要搞一套全新的处理日期和时间的API?因为旧的java.util.Dat
- 一. 首先Swagger是什么?Swagger 是一个规范和完整的框架,用于生成、描述、调用和可视化 RESTful 风格的 Web 服务。
- 1.初始化顺序当Java创建一个对象时,系统先为该对象的所有实例属性分配内存(前提是该类已经被加载过了),接着程序开始对这些实例属性执行初始
- 1. 准备工作首先我们创建一个 Spring Boot 工程,引入 Web 和 Redis 依赖,同时考虑到接口限流一般是通过注解来标记,而
- 前言Basic编码是标准的BASE64编码,用于处理常规的需求:输出的内容不添加换行符,而且输出的内容由字母加数字组成。最近做了个Web模版