「java并发设置共享资源」java并发设置共享资源网站
本篇文章给大家谈谈java并发设置共享资源,以及java并发设置共享资源网站对应的知识点,希望对各位有所帮助,不要忘了收藏本站喔。
本文目录一览:
- 1、在生产者和消费者的实例中,如何实现线程并发和共享资源?
- 2、说一下java程序中的进程以及并发的概念?
- 3、java中这个多线程怎么不共享资源啊?
- 4、电脑培训分享Java 并发编程:核心理论
- 5、java 多进程并发控制怎么做?
- 6、Java编程多个线程如何访问同一个共享资源
在生产者和消费者的实例中,如何实现线程并发和共享资源?
解决思路
在现实应用中,很多时候都需要让多个线程按照一定的次序来访问共享资源,例如,经典的生产者和消费者问题。这类问题描述了这样一种情况,假设仓库中只能存放一件产品,生产者将生产出来的产品放入仓库,消费者将仓库中的产品取走消费。如果仓库中没有产品,则生产者可以将产品放入仓库,否则停止生产并等待,直到仓库中的产品被消费者取走为止。如果仓库中放有产品,则消费者可以将产品取走消费,否则停止消费并等待,直到仓库中再次放入产品为止。显然,这是一个同步问题,生产者和消费者共享同一资源,并且,生产者和消费者之间彼此依赖,互为条件向前推进。但是,该如何编写程序来解决这个问题呢?
传统的思路是利用循环检测的方式来实现,这种方式通过重复检查某一个特定条件是否成立来决定线程的推进顺序。比如,一旦生产者生产结束,它就继续利用循环检测来判断仓库中的产品是否被消费者消费,而消费者也是在消费结束后就会立即使用循环检测的方式来判断仓库中是否又放进产品。显然,这些操作是很耗费CPU资源的,不值得提倡。那么有没有更好的方法来解决这类问题呢?
首先,当线程在继续执行前需要等待一个条件方可继续执行时,仅有 synchronized 关键字是不够的。因为虽然synchronized关键字可以阻止并发更新同一个共享资源,实现了同步,但是它不能用来实现线程间的消息传递,也就是所谓的通信。而在处理此类问题的时候又必须遵循一种原则,即:对于生产者,在生产者没有生产之前,要通知消费者等待;在生产者生产之后,马上又通知消费者消费;对于消费者,在消费者消费之后,要通知生产者已经消费结束,需要继续生产新的产品以供消费。
其实,Java提供了3个非常重要的方法来巧妙地解决线程间的通信问题。这3个方法分别是:wait()、notify()和notifyAll()。它们都是Object类的最终方法,因此每一个类都默认拥有它们。
虽然所有的类都默认拥有这3个方法,但是只有在synchronized关键字作用的范围内,并且是同一个同步问题中搭配使用这3个方法时才有实际的意义。
这些方法在Object类中声明的语法格式如下所示:
final void wait() throws InterruptedException
final void notify()
final void notifyAll()
其中,调用wait()方法可以使调用该方法的线程释放共享资源的锁,然后从运行态退出,进入等待队列,直到被再次唤醒。而调用notify()方法可以唤醒等待队列中第一个等待同一共享资源的线程,并使该线程退出等待队列,进入可运行态。调用notifyAll()方法可以使所有正在等待队列中等待同一共享资源的线程从等待状态退出,进入可运行状态,此时,优先级最高的那个线程最先执行。显然,利用这些方法就不必再循环检测共享资源的状态,而是在需要的时候直接唤醒等待队列中的线程就可以了。这样不但节省了宝贵的CPU资源,也提高了程序的效率。
由于wait()方法在声明的时候被声明为抛出InterruptedException异常,因此,在调用wait()方法时,需要将它放入try…catch代码块中。此外,使用该方法时还需要把它放到一个同步代码段中,否则会出现如下异常:
"java.lang.IllegalMonitorStateException: current thread not owner"
这些方法是不是就可以实现线程间的通信了呢?下面将通过多线程同步的模型: 生产者和消费者问题来说明怎样通过程序解决多线程间的通信问题。
具体步骤
下面这个程序演示了多个线程之间进行通信的具体实现过程。程序中用到了4个类,其中ShareData类用来定义共享数据和同步方法。在同步方法中调用了wait()方法和notify()方法,并通过一个信号量来实现线程间的消息传递。
// 例4.6.1 CommunicationDemo.java 描述:生产者和消费者之间的消息传递过程
class ShareData
{
private char c;
private boolean isProduced = false; // 信号量
public synchronized void putShareChar(char c) // 同步方法putShareChar()
{
if (isProduced) // 如果产品还未消费,则生产者等待
{
try
{
wait(); // 生产者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.c = c;
isProduced = true; // 标记已经生产
notify(); // 通知消费者已经生产,可以消费
}
public synchronized char getShareChar() // 同步方法getShareChar()
{
if (!isProduced) // 如果产品还未生产,则消费者等待
{
try
{
wait(); // 消费者等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
isProduced = false; // 标记已经消费
notify(); // 通知需要生产
return this.c;
}
}
class Producer extends Thread // 生产者线程
{
private ShareData s;
Producer(ShareData s)
{
this.s = s;
}
public void run()
{
for (char ch = 'A'; ch = 'D'; ch++)
{
try
{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
s.putShareChar(ch); // 将产品放入仓库
System.out.println(ch + " is produced by Producer.");
}
}
}
class Consumer extends Thread // 消费者线程
{
private ShareData s;
Consumer(ShareData s)
{
this.s = s;
}
public void run()
{
char ch;
do {
try
{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
e.printStackTrace();
}
ch = s.getShareChar(); // 从仓库中取出产品
System.out.println(ch + " is consumed by Consumer. ");
} while (ch != 'D');
}
}
class CommunicationDemo
{
public static void main(String[] args)
{
ShareData s = new ShareData();
new Consumer(s).start();
new Producer(s).start();
}
}
上面的程序演示了生产者生产出A、B、C、D四个字符,消费者消费这四个字符的全过程,程序结果如图4.6.1所示:
图4.6.1 生产者和消费者举例
通过程序的运行结果可以看到,尽管在主方法中先启动了Consumer线程,但是,由于仓库中没有产品,因此,Consumer线程就会调用 wait()方法进入等待队列进行等待,直到Producer线程将产品生产出来并放进仓库,然后使用notify()方法将其唤醒。
由于在两个线程中都指定了一定的休眠时间,因此也可能出现这样的情况:生产者将产品生产出来放入仓库,并通知等待队列中的Consumer线程,然而,由于休眠时间过长,Consumer线程还没有打算消费产品,此时,Producer线程欲生产下一个产品,结果由于仓库中的产品没有被消费掉,故 Producer线程执行wait()方法进入等待队列等待,直到Consumer线程将仓库中的产品消费掉以后通过notify()方法去唤醒等待队列中的Producer线程为止。可见,两个线程之间除了必须保持同步之外,还要通过相互通信才能继续向前推进。
前面这个程序中,生产者一次只能生产一个产品,而消费者也只能一次消费一个产品。那么现实中也有这样的情况,生产者可以一次生产多个产品,只要仓库容量够大,就可以一直生产。而消费者也可以一次消费多个产品,直到仓库中没有产品为止。
但是,无论是生产产品到仓库,还是从仓库中消费,每一次都只能允许一个操作。显然,这也是个同步问题,只不过在这个问题中共享资源是一个资源池,可以存放多个资源。下面就以栈结构为例给出如何在这个问题中解决线程通信的程序代码。
// 例4.6.2 CommunicationDemo2.java
class SyncStack // 同步堆栈类,可以一次放入多个数据
{
private int index = 0; // 堆栈指针初始值为0
private char[] buffer = new char[5]; // 堆栈有5个字符的空间
public synchronized void push(char c) // 入栈同步方法
{
if (index == buffer.length) // 堆栈已满,不能入栈
{
try
{
this.wait(); // 等待出栈线程将数据出栈
} catch (InterruptedException e) {
}
}
buffer[index] = c; // 数据入栈
index++; // 指针加1,栈内空间减少
this.notify(); // 通知其他线程把数据出栈
}
public synchronized char pop() // 出栈同步方法
{
if (index == 0) // 堆栈无数据,不能出栈
{
try
{
this.wait(); // 等待入栈线程把数据入栈
} catch (InterruptedException e) {
}
}
this.notify(); // 通知其他线程入栈
index--; // 指针向下移动
return buffer[index]; // 数据出栈
}
}
class Producer implements Runnable // 生产者类
{
SyncStack s; // 生产者类生成的字母都保存到同步堆栈中
public Producer(SyncStack s)
{
this.s = s;
}
public void run()
{
char ch;
for (int i = 0; i 5; i++)
{
try
{
Thread.sleep((int) (Math.random() * 1000));
} catch (InterruptedException e) {
}
ch = (char) (Math.random() * 26 + 'A'); // 随机产生5个字符
s.push(ch); // 把字符入栈
System.out.println("Push " + ch + " in Stack"); // 打印字符入栈
}
}
}
class Consumer implements Runnable // 消费者类
{
SyncStack s; // 消费者类获得的字符都来自同步堆栈
public Consumer(SyncStack s)
{
this.s = s;
}
public void run()
{
char ch;
for (int i = 0; i 5; i++)
{
try
{
Thread.sleep((int) (Math.random() * 3000));
} catch (InterruptedException e) {
}
ch = s.pop(); // 从堆栈中读取字符
System.out.println("Pop " + ch + " from Stack"); // 打印字符出栈
}
}
}
public class CommunicationDemo2
{
public static void main(String[] args)
{
SyncStack stack = new SyncStack();
// 下面的消费者类对象和生产者类对象所操作的是同一个同步堆栈对象
Thread t1 = new Thread(new Producer(stack)); // 线程实例化
Thread t2 = new Thread(new Consumer(stack)); // 线程实例化
t2.start(); // 线程启动
t1.start(); // 线程启动
}
}
程序中引入了一个堆栈数组buffer[]来模拟资源池,并使生产者类和消费者类都实现了Runnable接口,然后在主程序中通过前面介绍的方法创建两个共享同一堆栈资源的线程,并且有意先启动消费者线程,后启动生产者线程。请在阅读程序的时候仔细观察例4.6.1和本例的相似点以及区别之处,体会作者的用心。程序结果输出如图4.6.2所示:
图4.6.2 共享资源池的生产者和消费者问题
由于是栈结构,所以符合后进先出原则。有兴趣的读者还可以用符合先进先出原则的队列结构来模拟线程间通信的过程,相信可以通过查阅相关的资料来解决这个问题,在这里就不再给出程序代码了,作为一个思考题供读者练习。
专家说明
本小节介绍了三个重要的方法:wait()、notify()和notifyAll()。使用它们可以高效率地完成多个线程间的通信问题,这样在通信问题上就不必再使用循环检测的方法来等待某个条件的发生,因为这种方法是极为浪费CPU资源的,当然这种情况也不是所期望的。在例4.6.1中,为了更好地通信,引入了一个专门用来传递信息的信号量。利用信号量来决定线程是否等待无疑是一种非常安全的操作,值得提倡。此外,在例4.6.2中引入了资源池作为共享资源,并解决了在这种情况下如何实现多线程之间的通信问题。希望读者能够举一反三,编写出解决更加复杂问题的程序。
专家指点
可以肯定的是,合理地使用wait()、notify()和notifyAll()方法确实能够很好地解决线程间通信的问题。但是,也应该了解到这些方法是更复杂的锁定、排队和并发性代码的构件。尤其是使用 notify()来代替notifyAll()时是有风险的。除非确实知道每一个线程正在做什么,否则最好使用notifyAll()。其实,在 JDK1.5中已经引入了一个新的包:java.util.concurrent 包,该包是一个被广泛使用的开放源码工具箱,里面都是有用的并发性实用程序。完全可以代替wait()和notify()方法用来编写自己的调度程序和锁。有关信息可以查阅相关资料,本书中不再赘述。
相关问题
Java提供了各种各样的输入输出流(stream),使程序员能够很方便地对数据进行操作。其中,管道(pipe)流是一种特殊的流,用于在不同线程间直接传送数据。一个线程发送数据到输出管道,另一个线程从输入管道中读出数据。通过使用管道,达到实现多个线程间通信的目的。那么,如何创建和使用管道呢?
Java提供了两个特殊的专门用来处理管道的类,它们就是PipedInputStream类和PipedOutputStream类。
其中,PipedInputStream代表了数据在管道中的输出端,也就是线程从管道读出数据的一端;PipedOutputStream代表了数据在管道中的输入端,也就是线程向管道写入数据的一端,这两个类一起使用就可以创建出数据输入输出的管道流对象。
说一下java程序中的进程以及并发的概念?
在 Java 程序中,进程是指正在运行的程序的实例。一个 Java 程序可以创建多个进程,每个进程都有自己的内存空间和执行上下文。
并发是指多个进程或线程在同一时间内同时执行。在 Java 中,你可以使用线程来实现并发。线程是进程中的一个执行单元,它共享进程的内存空间和执行上下文。使用线程可以让你的程序同时执行多个任务,从而提高程序的效率。
Java 提供了许多工具和技术来帮助你管理并发,例如线程同步、线程间通信和线程池。使用这些工具可以让你的程序在多个线程之间共享资源,同时避免线程冲突和死锁。
java中这个多线程怎么不共享资源啊?
i是run方法里的局部变量,可以共享的是类的成员变量,你定义一个成员变量,在for循环里引用,就不会有问题了
电脑培训分享Java 并发编程:核心理论
并发编程是Java程序员最重要的技能之一,也是最难掌握的一种技能。它要求编程者对计算机最底层的运作原理有深刻的理解,同时要求编程者逻辑清晰、思维缜密,这样才能写出高效、安全、可靠的多线程并发程序。电脑培训发现本系列会从线程间协调的方式(wait、notify、notifyAll)、Synchronized及Volatile的本质入手,详细解释JDK为我们提供的每种并发工具和底层实现机制。在此基础上,我们会进一步分析java.util.concurrent包的工具类,包括其使用方式、实现源码及其背后的原理。本文是该系列的第一篇文章,是这系列中最核心的理论部分,之后的文章都会以此为基础来分析和解释。
关于java并发编程及实现原理,还可以查阅《Java并发编程:Synchronized及其实现原理》。
一、共享性
数据共享性是线程安全的主要原因之一。如果所有的数据只是在线程内有效,那就不存在线程安全性问题,这也是我们在编程的时候经常不需要考虑线程安全的主要原因之一。但是,在多线程编程中,数据共享是不可避免的。最典型的场景是数据库中的数据,为了保证数据的一致性,我们通常需要共享同一个数据库中数据,即使是在主从的情况下,访问的也同一份数据,主从只是为了访问的效率和数据安全,而对同一份数据做的副本。我们现在,通过一个简单的示例来演示多线程下共享数据导致的问题。
二、互斥性
资源互斥是指同时只允许一个访问者对其进行访问,具有唯一性和排它性。我们通常允许多个线程同时对数据进行读操作,但同一时间内只允许一个线程对数据进行写操作。所以我们通常将锁分为共享锁和排它锁,也叫做读锁和写锁。如果资源不具有互斥性,即使是共享资源,我们也不需要担心线程安全。例如,对于不可变的数据共享,所有线程都只能对其进行读操作,所以不用考虑线程安全问题。但是对共享数据的写操作,一般就需要保证互斥性,上述例子中就是因为没有保证互斥性才导致数据的修改产生问题。
java 多进程并发控制怎么做?
进程间的通讯无非就是读写文件,socket通讯或者使用共享内存。
你不想用读写文件的方式,那就用共享内存或者socket通讯的方式。我个人觉得用socket比较简单,也许是因为我对socket比较熟悉。
下面是一篇java实现共享内存的文章,java没法管理内存,其实他也是靠创建映像文件来实现的。
共享内存在java中的实现
在jdk1.4中提供的类MappedByteBuffer为我们实现共享内存提供了较好的方法。该缓冲区实际上是一个磁盘文件的内存映像。二者的变化将保持同步,即内存数据发生变化会立刻反映到磁盘文件中,这样会有效的保证共享内存的实现。
将共享内存和磁盘文件建立联系的是文件通道类:FileChannel。该类的加入是JDK为了统一对外部设备(文件、网络接口等)的访问方法,并且加强了多线程对同一文件进行存取的安全性。例如读写操作统一成read和write。这里只是用它来建立共享内存用,它建立了共享内存和磁盘文件之间的一个通道。
打开一个文件建立一个文件通道可以用RandomAccessFile类中的方法getChannel。该方法将直接返回一个文件通道。该文件通道由于对应的文件设为随机存取文件,一方面可以进行读写两种操作,另一方面使用它不会破坏映像文件的内容(如果用FileOutputStream直接打开一个映像文件会将该文件的大小置为0,当然数据会全部丢失)。这里,如果用 FileOutputStream和FileInputStream则不能理想的实现共享内存的要求,因为这两个类同时实现自由的读写操作要困难得多。
下面的代码实现了如上功能,它的作用类似UNIX系统中的mmap函数。
// 获得一个只读的随机存取文件对象
RandomAccessFile RAFile = new RandomAccessFile(filename,"r");
// 获得相应的文件通道
FileChannel fc = RAFile.getChannel();
// 取得文件的实际大小,以便映像到共享内存
int size = (int)fc.size();
// 获得共享内存缓冲区,该共享内存只读
MappedByteBuffer mapBuf = fc.map(FileChannel.MAP_RO,0,size);
// 获得一个可读写的随机存取文件对象
RAFile = new RandomAccessFile(filename,"rw");
// 获得相应的文件通道
fc = RAFile.getChannel();
// 取得文件的实际大小,以便映像到共享内存
size = (int)fc.size();
// 获得共享内存缓冲区,该共享内存可读写
mapBuf = fc.map(FileChannel.MAP_RW,0,size);
// 获取头部消息:存取权限
mode = mapBuf.getInt();
如果多个应用映像同一文件名的共享内存,则意味着这多个应用共享了同一内存数据。这些应用对于文件可以具有同等存取权限,一个应用对数据的刷新会更新到多个应用中。
为了防止多个应用同时对共享内存进行写操作,可以在该共享内存的头部信息加入写操作标志。该共享内存的头部基本信息至少有:
int Length; // 共享内存的长度。
int mode; // 该共享内存目前的存取模式。
共享内存的头部信息是类的私有信息,在多个应用可以对同一共享内存执行写操作时,开始执行写操作和结束写操作时,需调用如下方法:
public boolean StartWrite()
{
if(mode == 0) { // 标志为0,则表示可写
mode = 1; // 置标志为1,意味着别的应用不可写该共享内存
mapBuf.flip();
mapBuf.putInt(mode); // 写如共享内存的头部信息
return true;
}
else {
return false; // 指明已经有应用在写该共享内存,本应用不可写该共享内存
}
}
public boolean StopWrite()
{
mode = 0; // 释放写权限
mapBuf.flip();
mapBuf.putInt(mode); // 写入共享内存头部信息
return true;
}
这里提供的类文件mmap.java封装了共享内存的基本接口,读者可以用该类扩展成自己需要的功能全面的类。
如果执行写操作的应用异常中止,那么映像文件的共享内存将不再能执行写操作。为了在应用异常中止后,写操作禁止标志自动消除,必须让运行的应用获知退出的应用。在多线程应用中,可以用同步方法获得这样的效果,但是在多进程中,同步是不起作用的。方法可以采用的多种技巧,这里只是描述一可能的实现:采用文件锁的方式。写共享内存应用在获得对一个共享内存写权限的时候,除了判断头部信息的写权限标志外,还要判断一个临时的锁文件是否可以得到,如果可以得到,则即使头部信息的写权限标志为1(上述),也可以启动写权限,其实这已经表明写权限获得的应用已经异常退出,这段代码如下:
// 打开一个临时的文件,注意同一共享内存,该文件名要相同,可以在共享文件名后加后缀“.lock”。
RandomAccessFile fis = new RandomAccessFile("shm.lock","rw");
// 获得文件通道
FileChannel lockfc = fis.getChannel();
// 获得文件的独占锁,该方法不产生堵塞,立刻返回
FileLock flock = lockfc.tryLock();
// 如果为空,则表明已经有应用占有该锁
if(flock == null) {
...// 不能执行写操作
}
else {
...// 可以执行写操作
}
该锁会在应用异常退出后自动释放,这正是该处所需要的方法。
Java编程多个线程如何访问同一个共享资源
如果该方法不涉及写公共的资源比如一个静态的变量或者写文件,修改某个数据库的值的时候没有影响
比如你这个类里的方法只是对输入的参数做一个计算然后返回计算的值就没有影响。
但是如果是修改公共的资源比如修改数据库中存储的一个value则有可能出现问题,如:
public void writeDb(String key, String value) {
collectiondb();
write(key, value);
closeDbCollection();
}
因为Java的线程运行顺序是不一定的,可以第一个线程运行完连接数据库到后挂起了,这时候第二个线程开始运行,如果你的collectiondb()处理使用的是类中的一个实例变量Connection conn来保存数据库的连接,当第二个线程运行完毕以后conn也被关闭了,第一个线程继续执行write函数写数据库值的时候就会抛出异常。
这是一个例子,还有其他可能产生脏数据的问题
多线程如果使用公共资源的话最好在方法上声明synchronized关键字让其同步
关于java并发设置共享资源和java并发设置共享资源网站的介绍到此就结束了,不知道你从中找到你需要的信息了吗 ?如果你还想了解更多这方面的信息,记得收藏关注本站。