* Синхронизация на нишки
Публикувано на 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");
}
По този начин е съвсем възможно друга нишка да достъпва и променя други "член-обекти" от текущия клас.
Добави коментар