电脑知识|欧美黑人一区二区三区|软件|欧美黑人一级爽快片淫片高清|系统|欧美黑人狂野猛交老妇|数据库|服务器|编程开发|网络运营|知识问答|技术教程文章 - 好吧啦网

您的位置:首頁(yè)技術(shù)文章
文章詳情頁(yè)

淺談Java并發(fā)編程之Lock鎖和條件變量

瀏覽:7日期:2022-08-27 15:26:17

簡(jiǎn)單使用Lock鎖

Java 5中引入了新的鎖機(jī)制——java.util.concurrent.locks中的顯式的互斥鎖:Lock接口,它提供了比synchronized更加廣泛的鎖定操作。Lock接口有3個(gè)實(shí)現(xiàn)它的類:ReentrantLock、ReetrantReadWriteLock.ReadLock和ReetrantReadWriteLock.WriteLock,即重入鎖、讀鎖和寫(xiě)鎖。lock必須被顯式地創(chuàng)建、鎖定和釋放,為了可以使用更多的功能,一般用ReentrantLock為其實(shí)例化。為了保證鎖最終一定會(huì)被釋放(可能會(huì)有異常發(fā)生),要把互斥區(qū)放在try語(yǔ)句塊內(nèi),并在finally語(yǔ)句塊中釋放鎖,尤其當(dāng)有return語(yǔ)句時(shí),return語(yǔ)句必須放在try字句中,以確保unlock()不會(huì)過(guò)早發(fā)生,從而將數(shù)據(jù)暴露給第二個(gè)任務(wù)。因此,采用lock加鎖和釋放鎖的一般形式如下:

Lock lock = new ReentrantLock();//默認(rèn)使用非公平鎖,如果要使用公平鎖,需要傳入?yún)?shù)true ........ lock.lock(); try { //更新對(duì)象的狀態(tài) //捕獲異常,必要時(shí)恢復(fù)到原來(lái)的不變約束 //如果有return語(yǔ)句,放在這里 } finally { lock.unlock(); //鎖必須在finally塊中釋放 }

ReetrankLock與synchronized比較

性能比較

在JDK1.5中,synchronized是性能低效的。因?yàn)檫@是一個(gè)重量級(jí)操作,它對(duì)性能最大的影響是阻塞的是實(shí)現(xiàn),掛起線程和恢復(fù)線程的操作都需要轉(zhuǎn)入內(nèi)核態(tài)中完成,這些操作給系統(tǒng)的并發(fā)性帶來(lái)了很大的壓力。相比之下使用Java提供的Lock對(duì)象,性能更高一些。Brian Goetz對(duì)這兩種鎖在JDK1.5、單核處理器及雙Xeon處理器環(huán)境下做了一組吞吐量對(duì)比的實(shí)驗(yàn),發(fā)現(xiàn)多線程環(huán)境下,synchronized的吞吐量下降的非常嚴(yán)重,而ReentrankLock則能基本保持在同一個(gè)比較穩(wěn)定的水平上。但與其說(shuō)ReetrantLock性能好,倒不如說(shuō)synchronized還有非常大的優(yōu)化余地,于是到了JDK1.6,發(fā)生了變化,對(duì)synchronize加入了很多優(yōu)化措施,有自適應(yīng)自旋,鎖消除,鎖粗化,輕量級(jí)鎖,偏向鎖等等。導(dǎo)致在JDK1.6上synchronize的性能并不比Lock差。官方也表示,他們也更支持synchronize,在未來(lái)的版本中還有優(yōu)化余地,所以還是提倡在synchronized能實(shí)現(xiàn)需求的情況下,優(yōu)先考慮使用synchronized來(lái)進(jìn)行同步。

下面淺析以下兩種鎖機(jī)制的底層的實(shí)現(xiàn)策略。

互斥同步最主要的問(wèn)題就是進(jìn)行線程阻塞和喚醒所帶來(lái)的性能問(wèn)題,因而這種同步又稱為阻塞同步,它屬于一種悲觀的并發(fā)策略,即線程獲得的是獨(dú)占鎖。獨(dú)占鎖意味著其他線程只能依靠阻塞來(lái)等待線程釋放鎖。而在CPU轉(zhuǎn)換線程阻塞時(shí)會(huì)引起線程上下文切換,當(dāng)有很多線程競(jìng)爭(zhēng)鎖的時(shí)候,會(huì)引起CPU頻繁的上下文切換導(dǎo)致效率很低。synchronized采用的便是這種并發(fā)策略。

隨著指令集的發(fā)展,我們有了另一種選擇:基于沖突檢測(cè)的樂(lè)觀并發(fā)策略,通俗地講就是先進(jìn)性操作,如果沒(méi)有其他線程爭(zhēng)用共享數(shù)據(jù),那操作就成功了,如果共享數(shù)據(jù)被爭(zhēng)用,產(chǎn)生了沖突,那就再進(jìn)行其他的補(bǔ)償措施(最常見(jiàn)的補(bǔ)償措施就是不斷地重拾,直到試成功為止),這種樂(lè)觀的并發(fā)策略的許多實(shí)現(xiàn)都不需要把線程掛起,因此這種同步被稱為非阻塞同步。ReetrantLock采用的便是這種并發(fā)策略。

在樂(lè)觀的并發(fā)策略中,需要操作和沖突檢測(cè)這兩個(gè)步驟具備原子性,它靠硬件指令來(lái)保證,這里用的是CAS操作(Compare and Swap)。JDK1.5之后,Java程序才可以使用CAS操作。我們可以進(jìn)一步研究ReentrantLock的源代碼,會(huì)發(fā)現(xiàn)其中比較重要的獲得鎖的一個(gè)方法是compareAndSetState,這里其實(shí)就是調(diào)用的CPU提供的特殊指令。現(xiàn)代的CPU提供了指令,可以自動(dòng)更新共享數(shù)據(jù),而且能夠檢測(cè)到其他線程的干擾,而compareAndSet() 就用這些代替了鎖定。這個(gè)算法稱作非阻塞算法,意思是一個(gè)線程的失敗或者掛起不應(yīng)該影響其他線程的失敗或掛起。

Java 5中引入了注入AutomicInteger、AutomicLong、AutomicReference等特殊的原子性變量類,它們提供的如:compareAndSet()、incrementAndSet()和getAndIncrement()等方法都使用了CAS操作。因此,它們都是由硬件指令來(lái)保證的原子方法。

用途比較

基本語(yǔ)法上,ReentrantLock與synchronized很相似,它們都具備一樣的線程重入特性,只是代碼寫(xiě)法上有點(diǎn)區(qū)別而已,一個(gè)表現(xiàn)為API層面的互斥鎖(Lock),一個(gè)表現(xiàn)為原生語(yǔ)法層面的互斥鎖(synchronized)。ReentrantLock相對(duì)synchronized而言還是增加了一些高級(jí)功能,主要有以下三項(xiàng):

1、等待可中斷:當(dāng)持有鎖的線程長(zhǎng)期不釋放鎖時(shí),正在等待的線程可以選擇放棄等待,改為處理其他事情,它對(duì)處理執(zhí)行時(shí)間非常上的同步塊很有幫助。而在等待由synchronized產(chǎn)生的互斥鎖時(shí),會(huì)一直阻塞,是不能被中斷的。

2、可實(shí)現(xiàn)公平鎖:多個(gè)線程在等待同一個(gè)鎖時(shí),必須按照申請(qǐng)鎖的時(shí)間順序排隊(duì)等待,而非公平鎖則不保證這點(diǎn),在鎖釋放時(shí),任何一個(gè)等待鎖的線程都有機(jī)會(huì)獲得鎖。synchronized中的鎖時(shí)非公平鎖,ReentrantLock默認(rèn)情況下也是非公平鎖,但可以通過(guò)構(gòu)造方法ReentrantLock(ture)來(lái)要求使用公平鎖。

3、鎖可以綁定多個(gè)條件:ReentrantLock對(duì)象可以同時(shí)綁定多個(gè)Condition對(duì)象(名曰:條件變量或條件隊(duì)列),而在synchronized中,鎖對(duì)象的wait()和notify()或notifyAll()方法可以實(shí)現(xiàn)一個(gè)隱含條件,但如果要和多于一個(gè)的條件關(guān)聯(lián)的時(shí)候,就不得不額外地添加一個(gè)鎖,而ReentrantLock則無(wú)需這么做,只需要多次調(diào)用newCondition()方法即可。而且我們還可以通過(guò)綁定Condition對(duì)象來(lái)判斷當(dāng)前線程通知的是哪些線程(即與Condition對(duì)象綁定在一起的其他線程)。

可中斷鎖

ReetrantLock有兩種鎖:忽略中斷鎖和響應(yīng)中斷鎖。忽略中斷鎖與synchronized實(shí)現(xiàn)的互斥鎖一樣,不能響應(yīng)中斷,而響應(yīng)中斷鎖可以響應(yīng)中斷。

如果某一線程A正在執(zhí)行鎖中的代碼,另一線程B正在等待獲取該鎖,可能由于等待時(shí)間過(guò)長(zhǎng),線程B不想等待了,想先處理其他事情,我們可以讓它中斷自己或者在別的線程中中斷它,如果此時(shí)ReetrantLock提供的是忽略中斷鎖,則它不會(huì)去理會(huì)該中斷,而是讓線程B繼續(xù)等待,而如果此時(shí)ReetrantLock提供的是響應(yīng)中斷鎖,那么它便會(huì)處理中斷,讓線程B放棄等待,轉(zhuǎn)而去處理其他事情。

獲得響應(yīng)中斷鎖的一般形式如下:

ReentrantLock lock = new ReentrantLock();...........lock.lockInterruptibly();//獲取響應(yīng)中斷鎖try { //更新對(duì)象的狀態(tài) //捕獲異常,必要時(shí)恢復(fù)到原來(lái)的不變約束 //如果有return語(yǔ)句,放在這里}finally{lock.unlock(); //鎖必須在finally塊中釋放}

這里有一個(gè)不錯(cuò)的分析中斷的示例代碼(摘自網(wǎng)上)

當(dāng)用synchronized中斷對(duì)互斥鎖的等待時(shí),并不起作用,該線程依然會(huì)一直等待,如下面的實(shí)例:

public class Buffer { private Object lock; public Buffer() { lock = this; } public void write() { synchronized (lock) { long startTime = System.currentTimeMillis(); System.out.println('開(kāi)始往這個(gè)buff寫(xiě)入數(shù)據(jù)…'); for (;;)// 模擬要處理很長(zhǎng)時(shí)間 { if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) { break; } } System.out.println('終于寫(xiě)完了'); } } public void read() { synchronized (lock) { System.out.println('從這個(gè)buff讀數(shù)據(jù)'); } } public static void main(String[] args) { Buffer buff = new Buffer(); final Writer writer = new Writer(buff); final Reader reader = new Reader(buff); writer.start(); reader.start(); new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); for (;;) { //等5秒鐘去中斷讀 if (System.currentTimeMillis() - start > 5000) { System.out.println('不等了,嘗試中斷'); reader.interrupt(); //嘗試中斷讀線程 break; } } } }).start(); // 我們期待“讀”這個(gè)線程能退出等待鎖,可是事與愿違,一旦讀這個(gè)線程發(fā)現(xiàn)自己得不到鎖, // 就一直開(kāi)始等待了,就算它等死,也得不到鎖,因?yàn)閷?xiě)線程要21億秒才能完成 T_T ,即使我們中斷它, // 它都不來(lái)響應(yīng)下,看來(lái)真的要等死了。這個(gè)時(shí)候,ReentrantLock給了一種機(jī)制讓我們來(lái)響應(yīng)中斷, // 讓“讀”能伸能屈,勇敢放棄對(duì)這個(gè)鎖的等待。我們來(lái)改寫(xiě)B(tài)uffer這個(gè)類,就叫BufferInterruptibly吧,可中斷緩存。 }} class Writer extends Thread { private Buffer buff; public Writer(Buffer buff) { this.buff = buff; } @Override public void run() { buff.write(); }} class Reader extends Thread { private Buffer buff; public Reader(Buffer buff) { this.buff = buff; } @Override public void run() { buff.read();//這里估計(jì)會(huì)一直阻塞 System.out.println('讀結(jié)束'); }}

執(zhí)行結(jié)果如下:

淺談Java并發(fā)編程之Lock鎖和條件變量

我們等待了很久,后面依然沒(méi)有輸出,說(shuō)明讀線程對(duì)互斥鎖的等待并沒(méi)有被中斷,也就是該戶吃鎖沒(méi)有響應(yīng)對(duì)讀線程的中斷。 我們?cè)賹⑸厦娲a中synchronized的互斥鎖改為ReentrantLock的響應(yīng)中斷鎖,即改為如下代碼:

import java.util.concurrent.locks.ReentrantLock; public class BufferInterruptibly { private ReentrantLock lock = new ReentrantLock(); public void write() { lock.lock(); try { long startTime = System.currentTimeMillis(); System.out.println('開(kāi)始往這個(gè)buff寫(xiě)入數(shù)據(jù)…'); for (;;)// 模擬要處理很長(zhǎng)時(shí)間 { if (System.currentTimeMillis() - startTime > Integer.MAX_VALUE) { break; } } System.out.println('終于寫(xiě)完了'); } finally { lock.unlock(); } } public void read() throws InterruptedException { lock.lockInterruptibly();// 注意這里,可以響應(yīng)中斷 try { System.out.println('從這個(gè)buff讀數(shù)據(jù)'); } finally { lock.unlock(); } } public static void main(String args[]) { BufferInterruptibly buff = new BufferInterruptibly(); final Writer2 writer = new Writer2(buff); final Reader2 reader = new Reader2(buff); writer.start(); reader.start(); new Thread(new Runnable() { @Override public void run() { long start = System.currentTimeMillis(); for (;;) { if (System.currentTimeMillis() - start > 5000) { System.out.println('不等了,嘗試中斷'); reader.interrupt(); //此處中斷讀操作 break; } } } }).start(); }} class Reader2 extends Thread { private BufferInterruptibly buff; public Reader2(BufferInterruptibly buff) { this.buff = buff; } @Override public void run() { try { buff.read();//可以收到中斷的異常,從而有效退出 } catch (InterruptedException e) { System.out.println('我不讀了'); } System.out.println('讀結(jié)束'); }} class Writer2 extends Thread { private BufferInterruptibly buff; public Writer2(BufferInterruptibly buff) { this.buff = buff; } @Override public void run() { buff.write(); } }

執(zhí)行結(jié)果如下:

淺談Java并發(fā)編程之Lock鎖和條件變量

從結(jié)果中可以看出,嘗試中斷后輸出了catch語(yǔ)句塊中的內(nèi)容,也輸出了后面的“讀結(jié)束”,說(shuō)明線程對(duì)互斥鎖的等待被中斷了,也就是該互斥鎖響應(yīng)了對(duì)讀線程的中斷。條件變量實(shí)現(xiàn)線程間協(xié)作 在生產(chǎn)者——消費(fèi)者模型一文中,我們用synchronized實(shí)現(xiàn)互斥,并配合使用Object對(duì)象的wait()和notify()或notifyAll()方法來(lái)實(shí)現(xiàn)線程間協(xié)作。Java 5之后,我們可以用Reentrantlock鎖配合Condition對(duì)象上的await()和signal()或signalAll()方法來(lái)實(shí)現(xiàn)線程間協(xié)作。在ReentrantLock對(duì)象上newCondition()可以得到一個(gè)Condition對(duì)象,可以通過(guò)在Condition上調(diào)用await()方法來(lái)掛起一個(gè)任務(wù)(線程),通過(guò)在Condition上調(diào)用signal()來(lái)通知任務(wù),從而喚醒一個(gè)任務(wù),或者調(diào)用signalAll()來(lái)喚醒所有在這個(gè)Condition上被其自身掛起的任務(wù)。另外,如果使用了公平鎖,signalAll()的與Condition關(guān)聯(lián)的所有任務(wù)將以FIFO隊(duì)列的形式獲取鎖,如果沒(méi)有使用公平鎖,則獲取鎖的任務(wù)是隨機(jī)的,這樣我們便可以更好地控制處在await狀態(tài)的任務(wù)獲取鎖的順序。與notifyAll()相比,signalAll()是更安全的方式。另外,它可以指定喚醒與自身Condition對(duì)象綁定在一起的任務(wù)。 下面將生產(chǎn)者——消費(fèi)者模型一文中的代碼改為用條件變量實(shí)現(xiàn),如下:

import java.util.concurrent.*;import java.util.concurrent.locks.*; class Info{// 定義信息類private String name = 'name';//定義name屬性,為了與下面set的name屬性區(qū)別開(kāi)private String content = 'content' ;// 定義content屬性,為了與下面set的content屬性區(qū)別開(kāi)private boolean flag = true ;// 設(shè)置標(biāo)志位,初始時(shí)先生產(chǎn)private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition(); //產(chǎn)生一個(gè)Condition對(duì)象public void set(String name,String content){lock.lock();try{while(!flag){condition.await() ;}this.setName(name) ;// 設(shè)置名稱Thread.sleep(300) ;this.setContent(content) ;// 設(shè)置內(nèi)容flag = false ;// 改變標(biāo)志位,表示可以取走condition.signal();}catch(InterruptedException e){e.printStackTrace() ;}finally{lock.unlock();}} public void get(){lock.lock();try{while(flag){condition.await() ;}Thread.sleep(300) ;System.out.println(this.getName() + ' --> ' + this.getContent()) ;flag = true ;// 改變標(biāo)志位,表示可以生產(chǎn)condition.signal();}catch(InterruptedException e){e.printStackTrace() ;}finally{lock.unlock();}} public void setName(String name){this.name = name ;}public void setContent(String content){this.content = content ;}public String getName(){return this.name ;}public String getContent(){return this.content ;}}class Producer implements Runnable{// 通過(guò)Runnable實(shí)現(xiàn)多線程private Info info = null ;// 保存Info引用public Producer(Info info){this.info = info ;}public void run(){boolean flag = true ;// 定義標(biāo)記位for(int i=0;i<10;i++){if(flag){this.info.set('姓名--1','內(nèi)容--1') ;// 設(shè)置名稱flag = false ;}else{this.info.set('姓名--2','內(nèi)容--2') ;// 設(shè)置名稱flag = true ;}}}}class Consumer implements Runnable{private Info info = null ;public Consumer(Info info){this.info = info ;}public void run(){for(int i=0;i<10;i++){this.info.get() ;}}}public class ThreadCaseDemo{public static void main(String args[]){Info info = new Info();// 實(shí)例化Info對(duì)象Producer pro = new Producer(info) ;// 生產(chǎn)者Consumer con = new Consumer(info) ;// 消費(fèi)者new Thread(pro).start() ;//啟動(dòng)了生產(chǎn)者線程后,再啟動(dòng)消費(fèi)者線程try{Thread.sleep(500) ;}catch(InterruptedException e){e.printStackTrace() ;} new Thread(con).start() ;}}

執(zhí)行后,同樣可以得到如下的結(jié)果:

姓名--1 --> 內(nèi)容--1姓名--2 --> 內(nèi)容--2姓名--1 --> 內(nèi)容--1姓名--2 --> 內(nèi)容--2姓名--1 --> 內(nèi)容--1姓名--2 --> 內(nèi)容--2姓名--1 --> 內(nèi)容--1姓名--2 --> 內(nèi)容--2姓名--1 --> 內(nèi)容--1姓名--2 --> 內(nèi)容--2

從以上并不能看出用條件變量的await()、signal()、signalAll()方法比用Object對(duì)象的wait()、notify()、notifyAll()方法實(shí)現(xiàn)線程間協(xié)作有多少優(yōu)點(diǎn),但它在處理更復(fù)雜的多線程問(wèn)題時(shí),會(huì)有明顯的優(yōu)勢(shì)。所以,Lock和Condition對(duì)象只有在更加困難的多線程問(wèn)題中才是必須的。

讀寫(xiě)鎖

另外,synchronized獲取的互斥鎖不僅互斥讀寫(xiě)操作、寫(xiě)寫(xiě)操作,還互斥讀讀操作,而讀讀操作時(shí)不會(huì)帶來(lái)數(shù)據(jù)競(jìng)爭(zhēng)的,因此對(duì)對(duì)讀讀操作也互斥的話,會(huì)降低性能。Java 5中提供了讀寫(xiě)鎖,它將讀鎖和寫(xiě)鎖分離,使得讀讀操作不互斥,獲取讀鎖和寫(xiě)鎖的一般形式如下:

ReadWriteLock rwl = new ReentrantReadWriteLock(); rwl.writeLock().lock() //獲取寫(xiě)鎖rwl.readLock().lock() //獲取讀鎖

用讀鎖來(lái)鎖定讀操作,用寫(xiě)鎖來(lái)鎖定寫(xiě)操作,這樣寫(xiě)操作和寫(xiě)操作之間會(huì)互斥,讀操作和寫(xiě)操作之間會(huì)互斥,但讀操作和讀操作就不會(huì)互斥。

《Java并發(fā)編程實(shí)踐》一書(shū)給出了使用 ReentrantLock的最佳時(shí)機(jī):

當(dāng)你需要以下高級(jí)特性時(shí),才應(yīng)該使用:可定時(shí)的、可輪詢的與可中斷的鎖獲取操作,公平隊(duì)列,或者非塊結(jié)構(gòu)的鎖。否則,請(qǐng)使用synchronized。

到此這篇關(guān)于淺談Java并發(fā)編程之Lock鎖和條件變量的文章就介紹到這了,更多相關(guān)Java并發(fā)編程之Lock鎖和條件變量?jī)?nèi)容請(qǐng)搜索好吧啦網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持好吧啦網(wǎng)!

標(biāo)簽: Java
相關(guān)文章:
主站蜘蛛池模板: 四探针电阻率测试仪-振实密度仪-粉末流动性测定仪-宁波瑞柯微智能 | 深圳标识制作公司-标识标牌厂家-深圳广告标识制作-玟璟广告-深圳市玟璟广告有限公司 | 阻垢剂-反渗透缓蚀阻垢剂厂家-山东鲁东环保科技有限公司 | 视觉检测设备_自动化检测设备_CCD视觉检测机_外观缺陷检测-瑞智光电 | 龙门加工中心-数控龙门加工中心厂家价格-山东海特数控机床有限公司_龙门加工中心-数控龙门加工中心厂家价格-山东海特数控机床有限公司 | LED投光灯-工矿灯-led路灯头-工业灯具 - 山东普瑞斯照明科技有限公司 | 深圳希玛林顺潮眼科医院(官网)│深圳眼科医院│医保定点│香港希玛林顺潮眼科中心连锁品牌 | 升降炉_真空气氛炉_管式电阻炉厂家-山东中辰电炉有限公司 | 钢格栅板_钢格板网_格栅板-做专业的热镀锌钢格栅板厂家-安平县迎瑞丝网制造有限公司 | 深圳3D打印服务-3D打印加工-手板模型加工厂-悟空打印坊 | 书信之家_书信标准模板范文大全| 热缩管切管机-超声波切带机-织带切带机-无纺布切布机-深圳市宸兴业科技有限公司 | 盘古网络技术有限公司| 细沙回收机-尾矿干排脱水筛设备-泥石分离机-建筑垃圾分拣机厂家-青州冠诚重工机械有限公司 | 广州物流公司_广州货运公司_广州回程车运输 - 万信物流 | 培训一点通 - 合肥驾校 - 合肥新亚驾校 - 合肥八一驾校 | 济南拼接屏_山东液晶拼接屏_济南LED显示屏—维康国际官网 | 吉祥新世纪铝塑板_生产铝塑板厂家_铝塑板生产厂家_临沂市兴达铝塑装饰材料有限公司 | 【铜排折弯机,钢丝折弯成型机,汽车发泡钢丝折弯机,线材折弯机厂家,线材成型机,铁线折弯机】贝朗折弯机厂家_东莞市贝朗自动化设备有限公司 | 冷却塔改造厂家_不锈钢冷却塔_玻璃钢冷却塔改造维修-广东特菱节能空调设备有限公司 | 环氧乙烷灭菌器_压力蒸汽灭菌器_低温等离子过氧化氢灭菌器 _低温蒸汽甲醛灭菌器_清洗工作站_医用干燥柜_灭菌耗材-环氧乙烷灭菌器_脉动真空压力蒸汽灭菌器_低温等离子灭菌设备_河南省三强医疗器械有限责任公司 | 杭州货架订做_组合货架公司_货位式货架_贯通式_重型仓储_工厂货架_货架销售厂家_杭州永诚货架有限公司 | 带锯机|木工带锯机圆木推台锯|跑车带锯机|河北茂业机械制造有限公司| | 体视显微镜_荧光生物显微镜_显微镜报价-微仪光电生命科学显微镜有限公司 | 压力变送器-上海武锐自动化设备有限公司| 物联网卡_物联网卡购买平台_移动物联网卡办理_移动联通电信流量卡通信模组采购平台? | 浙江美尔凯特智能厨卫股份有限公司| 武汉天安盾电子设备有限公司 - 安盾安检,武汉安检门,武汉安检机,武汉金属探测器,武汉测温安检门,武汉X光行李安检机,武汉防爆罐,武汉车底安全检查,武汉液体探测仪,武汉安检防爆设备 | 潍坊青州古城旅游景点攻略_青州酒店美食推荐-青州旅游网 | 丝杆升降机-不锈钢丝杆升降机-非标定制丝杆升降机厂家-山东鑫光减速机有限公司 | 10吨无线拉力计-2吨拉力计价格-上海佳宜电子科技有限公司 | 河南道路标志牌_交通路标牌_交通标志牌厂家-郑州路畅交通 | 温控器生产厂家-提供温度开关/热保护器定制与批发-惠州市华恺威电子科技有限公司 | 奶茶加盟,奶茶加盟店连锁品牌-甜啦啦官网 | 全温恒温摇床-水浴气浴恒温摇床-光照恒温培养摇床-常州金坛精达仪器制造有限公司 | 原色会计-合肥注册公司_合肥代理记账公司_营业执照代办 | 医疗仪器模块 健康一体机 多参数监护仪 智慧医疗仪器方案定制 血氧监护 心电监护 -朗锐慧康 | 立式矫直机_卧式矫直机-无锡金矫机械制造有限公司 | 避光流动池-带盖荧光比色皿-生化流动比色皿-宜兴市晶科光学仪器 东莞爱加真空科技有限公司-进口真空镀膜机|真空镀膜设备|Polycold维修厂家 | 并离网逆变器_高频UPS电源定制_户用储能光伏逆变器厂家-深圳市索克新能源 | Eiafans.com_环评爱好者 环评网|环评论坛|环评报告公示网|竣工环保验收公示网|环保验收报告公示网|环保自主验收公示|环评公示网|环保公示网|注册环评工程师|环境影响评价|环评师|规划环评|环评报告|环评考试网|环评论坛 - Powered by Discuz! |