* Синхронизация на нишки
Публикувано на 17 октомври 2009 в раздел ПИК3 Java.
Когато имаме многонишково приложение, ние много често работим и със споделени ресурси. Нека демонстрираме с един пример - имаме масив с наредени числа. Имаме две нишки - такава, която променя числата с произволни и такава, която сортира числата по големина:
public class myfirstprogram{ public static void main(String[] args){ ArrayClass arr = new ArrayClass(); ChangeThread c = new ChangeThread("Change Thread", arr); SortThread s = new SortThread("Sort Thread", arr); c.T.start(); s.T.start(); try{ c.T.join(); s.T.join(); } catch(java.lang.InterruptedException e){} arr.showArray(); } } class ArrayClass{ int[] arr; public ArrayClass(){ this.arr = new int[200]; for(int i=this.arr.length-1; i>=0; i--){ this.arr[i] = i; } } public void changeArray(){ for(int i=0; i<this.arr.length; i++){ this.arr[i] = (int)Math.round(Math.random()*100); } System.out.println("Change finished"); } public void sortArray(){ java.util.Arrays.sort(this.arr); System.out.println("Sort finished"); } public void showArray(){ for(int i: this.arr){ System.out.print(i+" "); } } } class SortThread implements Runnable { private String threadName; ArrayClass arr; Thread T; public SortThread(String threadName, ArrayClass arr){ this.threadName = threadName; this.T = new Thread(this, this.threadName); this.arr = arr; } public void run(){ arr.sortArray(); } } class ChangeThread implements Runnable { private String threadName; ArrayClass arr; Thread T; public ChangeThread(String threadName, ArrayClass arr){ this.threadName = threadName; this.arr = arr; this.T = new Thread(this, this.threadName); } public void run() { arr.changeArray(); } }
При изполнение на програмата ще забележите, че нито числата са подредени произволно, нито масива е сортиран напълно. Ще има няколко поредици от сортирани числа - нещо, което определено нито една от нишките не е пожелала.
За да поправим този проблем, когато една нишка "бърка" в данните на друга, използваме т.нар. "синхронизирани методи". Въвежда се функционалност подобна на тази при трансакциите при бази от данни - достъпваните данни се "заключват", променят се и се "отключват" за достъп от други:
public class myfirstprogram{ public static void main(String[] args){ ArrayClass arr = new ArrayClass(); ChangeThread c = new ChangeThread("Change Thread", arr); SortThread s = new SortThread("Sort Thread", arr); c.T.start(); s.T.start(); try{ c.T.join(); s.T.join(); } catch(java.lang.InterruptedException e){} arr.showArray(); } } class ArrayClass{ int[] arr; public ArrayClass(){ this.arr = new int[200]; for(int i=this.arr.length-1; i>=0; i--){ this.arr[i] = i; } } public synchronized void changeArray(){ for(int i=0; i<this.arr.length; i++){ this.arr[i] = (int)Math.round(Math.random()*100); } System.out.println("Change finished"); } public synchronized void sortArray(){ java.util.Arrays.sort(this.arr); System.out.println("Sort finished"); } public void showArray(){ for(int i: this.arr){ System.out.print(i+" "); } } } class SortThread implements Runnable { private String threadName; ArrayClass arr; Thread T; public SortThread(String threadName, ArrayClass arr){ this.threadName = threadName; this.T = new Thread(this, this.threadName); this.arr = arr; } public void run(){ arr.sortArray(); } } class ChangeThread implements Runnable { private String threadName; ArrayClass arr; Thread T; public ChangeThread(String threadName, ArrayClass arr){ this.threadName = threadName; this.arr = arr; this.T = new Thread(this, this.threadName); } public void run() { arr.changeArray(); } }
Когато една нишка извика "синхронизиран" метод от един обект, всички други нишки, които в същия момент извикат същия или друг синхронизиран метод от същия обект "заспиват" и изчакват изпълнението си в опашка. В този смисъл ако първо се стартира ChangeThread, той ще извика метод changeArray(). Ако стартираме в същия момент SortThread, той ще извика функцията sortArray(). Тъй като и двете извикани функции са синхронизирани (с ключова дума synchronized), SortThread ще "заспи" и ще изчака ChangeThread да си свърши работата. Едва след това метод sortArray() ще бъде стартиран.
Единствените методи, които не могат да бъдат синхронизирани са конструкторите. При тях естествено такава операция е напълно безсмислена - не е възможно две нишки да "създават" един и същи обект едновременно.
В предишни статии ние споменахме за два обекта, които се различаваха по това как са реализирани техните методи. Това бяха StringBuilder (притежаващ не-синхронизирани методи) и StringBuffer (чийто методи са синхронизирани). Когато пишем подобни класове е хубаво винаги да преценяваме дали те ще бъдат използвани в многонишкови приложения или не. Синхронизирането трябва да се изпълнява много внимателно - в противен случай е напълно възможно при да се появят трудни за локализиране "бъгове" в последствие при реална експлоатация на софтуера.
Понякога обаче ни се налага да не правим синхронизация на абсолютно целия метод, а само на фрагмент от него. Така на практика оптимизираме програмите, като заключваме нишките за по-кратко време. Синтаксисът е следният:
Оператори 1; synchronized(<име на обект>){ Оператори 2; } Оператори 3;
По този начин по време на изпълнението на групата "Оператори 2" обектът дефиниран в блока ще бъде заключен за другите нишки. Чак след излизане от блока те ще бъдат отключени. От предишния пример бихме могли да постигнем аналогична функционалност ако променим методите changeArray и sortArray по следния начин:
public void changeArray(){ synchronized(this){ for(int i=0; i<this.arr.length; i++){ this.arr[i] = (int)Math.round(Math.random()*100); } System.out.println("Change finished"); } public void sortArray(){ synchronized(this){ java.util.Arrays.sort(this.arr); } System.out.println("Sort finished"); }
Виждаме, че вече сме оградили само и единствено блокът, където се прави промяна на данните. Извикването на System.out.println() не променя никакви данни и поради тази причина няма нужда да бъде изчакван от другите нишки.
Още повече - не е задължително блоковете да заключват всички обекти от текущия клас (в горния пример заключихме за синхронизация this). Можем да заключваме само и единствено обектът, с който работим, а именно - arr.this:
public void changeArray(){ synchronized(this.arr){ for(int i=0; i<this.arr.length; i++){ this.arr[i] = (int)Math.round(Math.random()*100); } } System.out.println("Change finished"); } public void sortArray(){ synchronized(this.arr){ java.util.Arrays.sort(this.arr); } System.out.println("Sort finished"); }
По този начин е съвсем възможно друга нишка да достъпва и променя други "член-обекти" от текущия клас.
Добави коментар