Java - synchronised, wątki

0

Witam, czy mógłby mi ktoś powiedzieć co robi dokładnie blok synchronizowany w javie?

Niżej kod, w którym nie rozumiem co się dokładnie dzieje (chodzi mi o czas wykonywania?wątku)

Chodzi mi o te zakomentowane synchronized{} - jak jest zakomentowane to grafika lepiej reaguje, ale dlaczego?

import java.awt.Button;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;


public class Watek extends Frame{
	private static final long serialVersionUID = 1L;

	private Button b1;
	private TextArea tekst;
	private Licznik lcz;
	private Button b2;
	
	public Watek(){
		this.addWindowListener(new WindowAdapter(){
             public void windowClosing(WindowEvent e)
            {
                dispose();
                System.exit(0);
            }
         });
		b1 = new Button("Start/Stop");
		tekst = new TextArea(10, 24);
		lcz = new Licznik(tekst);
		b2 = new Button("Znisz/Stwórz");
		
		b1.addActionListener(lcz);
		b2.addActionListener(lcz);
		add(b1);
		add(b2);
		add(tekst);
		
		setLayout(new FlowLayout());
		setVisible(true);
		setSize(400, 300);
	}
	
	private class Licznik implements Runnable, ActionListener{
		private TextArea tout;
		public boolean koniec = false;
		public long licznik = 0;
		public boolean b1wcisniety = false;
		Thread nt;
		
		public Licznik(TextArea t){
			tout = t;
			nt = new Thread(this);
			nt.start();
		}
		
		public void run(){
			while(!koniec){
				synchronized (this) {
					try {
						if(b1wcisniety==true){
							tout.append("Linia"+licznik+"\n");
							licznik++;
							Thread.sleep(200);
						}
						
					} catch (InterruptedException e) {

						e.printStackTrace();
					}
				}
			}
		}
		
		public void actionPerformed(ActionEvent e){
			tout.append("Przycisk\n");
				//synchronized (this) {			
				if(e.getSource()==b1 && b1wcisniety==false){
					tout.append("Wciśnięto (Start)\n");
					b1wcisniety=true;
				}
				else if(e.getSource()==b1 && b1wcisniety==true){
					tout.append("Wciśnięto (Stop)\n");
					b1wcisniety=false;
					notify();
				}
				else if(e.getSource()==b2 && koniec==false){
					koniec=true;
					tout.append("Wyłączono wątek licznika\n");
				}
				else if(e.getSource()==b2 && koniec==true){
					koniec=false;
					tout.append("Stworzono nowy wątek licznika\n");
					nt = new Thread(this);
					nt.start();
				}
			//}
		}
	}
	
	public static void main(String argv[]){
		Watek w = new Watek();
		w.setVisible(true);
	}
}
1

Oznacza to że dostęp do this w danej chwili może mieć tylko jeden wątek i ten właśnie wątek ma też dostęp do owego bloku.
Załóżmy że masz kilka wątków które współdzielą pewną zmienną i wykonują na niej operacje. Gdyby każdy miał do niej nieograniczony i jednoczesny dostęp do mogłyby się dziać cuda. Logiczne jest że chcemy żeby operacje zapisu/modyfikacji (ale nie tylko) były dostępne w danej chwili tylko dla jednego wątku (W innym przypadku np.: dwa wątki pobierają wartość zmiennej i tworzą jej "nową wersję", pierwszy wątek zapisuje tą nową wartość do zmiennej i potem robi to samo drugi wątek. Zmienna teraz wygląda tak jakby zmienil ją tylko ten drugi wątek a działanie tego pierwszego zostało całkowicie usunięte, a przecież nie o to chodziło).

Przykład:
Współdzielimy zmienną int o wartości początkowej 0. Puszczamy dwa wątki, oba mają zwiększyć wartość zmiennej o 1 i sie zakończyć. Jeśli nie ma synchronizacji to moze się okazać ze pierwszy wątek pobierze wartość zmiennej (0) i drugi wątek pobierze wartość zmiennej (0). Pierwszy wątek zwiększy zmienną i zapisze wynik (1) i drugi wątek zwiększy zmienną i zapisze wynik (1). W efekcie mamy zmienną o wartości 1, zamiast 2 jak byśmy oczekiwali. Gdybyśmy operacje inkrementacji dali w bloku synchronizowanym ową zmienną int, to wynik operacji byłby równy 2, tak jakbyśmy tego chcieli.

W związku z tym możemy ustalic synchronizacje wątków, tak żeby potrafiły sie dzielić takimi zasobami. W tym ogólnym przypadku dalibyśmy:

synchronized(zmienna) {
//operacje na zmiennej współdzielonej przez wątki, mamy pewność że w danej chwili operacje wykonuje tylko jeden wątek
}
1

Shalom, z Twojego opisu ( a dokladniej przykladu ) ktos moglby wywnioskowac ze synchronizuje sie "zmienna", jakakolwiek. W Twoim przypadku zmienna typu int.

Wiec lepiej to wyprostowac od razu.

synchronized(zmienna)
{
}

Po pierwsza zmienna musi byc typu obiektowego.
Po drugie: z kazdym obiektem w javie jest zwiazana blokada, wejscie do bloku synchronized na danym obiekcie oznacza "podniesienie" tej blokady, natomiast wyjscie z tego bloku oznacza "opuszczenie" (zwolnienie) tej blokady.

0

Można synchronizować wątek?

2

PZ, nie miałoby to żadnego sensu, ale mozna.

Żeby Ci bardziej zobrazować działanie synchronizacji podam przykład z życia wzięty ( na którym zreszta wzorowała sie osoba, która wprowadziła synchronizację do jezyków programowania ).

Wyobraź sobie szyny kolejowe, po ktorych jeżdżą pociągi. Żeby nie dochodziło do ich kolizji istnieje coś takiego jak semafor. Jak pociąg wjeżdża na odcinek torów, to podnosi ten semafor. To sprawia, że jeśli z drugiej strony jedzie pociąg, to nie wjędzie on na ten odcinek torów ( bo widzi podniesiony semafor ), gdy pociąg (ktory podniosł semafor ) opóści odcinek torów to zwalnia semafor, i w tedy inne pociagi moga jechac.

Teraz odwolanie tego do swiata programowania

synchronized(zmiennaReferencyjna)
{
// jakies operacje
}

[b]Obiekt[/b] na ktory wskazuje [b]zmiennaReferencyjna[/b] jest tytaj odpowiednikiem semafora. [b]Wątek[/b] jest natomiast pociągiem. [b]//jakies instrukcje[/b] sa natomiast tym odcinkiem torów, gdzie chcemy zeby tylko jeden wątek się znajdował. Jesli jakis watek wchodzi do tego bloku, to "podnosi semafor" ( ktorym jest obiekt wskazywany przez zmiennaReferencyjna ), jesli inny obiekt bedzie chcial wejsc do tego bloku, to bedzie musial czekac, az semafor zostanie opuszczony, przez watek, ktory go podniosł.
To co tak na prawde jest synchronizowane, to zestaw instrukcji, a nie jakas zmienna czy obiekt. Obiekt jest semaforem.
Zeby pociagnac to dalej, mozesz sobie wyobrazic, ze ten semafor ( ten jeden obiekt ) jest uzywany do synchronizacji roznych blokow kodu, np:

class JakasKlasa
{
   Obiekt semafor = ....

  public void metoda1()
  {
     synchronized(semafor)
     {
         // jakis zestaw instruckji nr 1
     }
  }
  public void metoda2()
  {
     synchronized(semafor)
     {
         // jakis zestaw instruckji nr 2
     }
  }
}

Zauwaz ze dwa bloki kodu ( w dwoch roznych metodach ) sa synchronizowane przy uzyciu tego samego obiektu. To oznacza, ze jezeli jakis watek W1 wejdzie do synchronizowanego bloku w metoda1 to zaden inny watek nie moze wejsc do synchronizawanego bloku metoda2 ( ani tez metoda1 oczywiscie ) tego samego obiektu.

Czesto chcemy synchronizowac dostep do jakiejs kolekcji, wtedy odpowiednie bloki kody synchronizujemy bezposrednio na obiekcie danej kolekcji. (Gdyz tak jak opisalem we wczesniejszym poscie, z kazdym obiektem w javie jest powiazana blokada)

mam nadzieje, ze bylo to zrozumiale, jak masz jakies pytania zwiazane z synchronizacja, to wal smialo

0

To mi trochę rozjaśniło - trudno było mi to zrozumieć, bo w ogóle nie działał mi żaden kod tak jak chciałem. Napisałem niżej programik, który robi dwa niezależne liczniki, które mozna zatrzymać i puścić dalej (działa tak jak chciałem żeby działał). Ale nie wiem jak zrobić, żeby np te dwa wątki operowały na jednym obiekcie - np u mnie na liczniku L1). Tzn działały tak, żeby ten program dalej mógł wyglądać jak wygląda (jeśli chodzi o funkcjonalność). Jeśli uruchomię drugi wątek na l1 (zamiast l2), to się zawiesza (czyżby nie dało się wtedy ruszyć ponownie waitowanego wątku?). Jesli dodatkowo wyłącze synchronizajcę (tę w actionPerfrormed), bo wydaje mi się, że jest w sumie zbędna(?) to niby się nie zawiesza, ale traci wskazanie(?) na obiekt(?). No nie wiem, może mi ktoś to wyjaśni :)

import java.awt.Button;
import java.awt.Dimension;
import java.awt.FlowLayout;
import java.awt.Frame;
import java.awt.GridLayout;
import java.awt.Panel;
import java.awt.TextArea;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;



public class GUI extends Frame{
	
	private Button b1;
	private Button b2;
	private Button b3;
	private Button b4;
	private Panel lewy;
	private Panel prawy;
	
	private TextArea t1;
	private Licznik l1;
	private Licznik l2;
	
	private Thread w1;
	private Thread w2;
	
	public GUI(){
		this.addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent e) {
				dispose();
				System.exit(0);
			}
		});
		
		b1 = new Button("W1 STOP");
		b2 = new Button("W1 START");
		b3 = new Button("W2 STOP");
		b4 = new Button("W2 START");
		t1 = new TextArea();
		lewy = new Panel(new FlowLayout(FlowLayout.CENTER));
		prawy = new Panel(new GridLayout(1,1));
		
		l1 = new Licznik(t1);
		w1 = new Thread(l1);
		w1.start();
		
		l2 = new Licznik(t1);
		w2 = new Thread(l2);
		w2.start();
		
		b1.addActionListener(l1);
		b2.addActionListener(l1);
		b3.addActionListener(l2);
		b4.addActionListener(l2);
		
		lewy.add(b1);
		lewy.add(b2);
		lewy.add(b3);
		lewy.add(b4);
		prawy.add(t1);
		
		Dimension ml = new Dimension(100, 500);
		Dimension mp = new Dimension(300, 500);
		
		lewy.setMinimumSize(ml);
		prawy.setMinimumSize(mp);

		
		setLayout(new GridLayout(1,2));
		add(lewy);
		add(prawy);
		
		Dimension d = new Dimension(400, 500);
		setSize(d);
		setVisible(true);
		

	}
	
	private class Licznik implements Runnable, ActionListener{
		private int licznik=0;
		private boolean koniec=false;
		private TextArea t;
		private boolean stop=false;

		
		public Licznik(TextArea ta){
			t=ta;
		}
		
		public void setKoniec(boolean k){
			koniec=k;
		}
		
		public boolean getKoniec(){
			return koniec;
		}
		
		public void run(){
			while(!koniec){
				synchronized (this) {
					if(stop!=true){
						t.append(licznik + "\n");
						licznik++;
					} else{
						try {
							wait();
						} catch (InterruptedException e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
					}
					try {
						Thread.sleep(300);
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
				}
			}
			
		}
		
		public void actionPerformed(ActionEvent e){
			synchronized (this) {
				if(e.getSource()==b1){
					t.append("Wciśnięto b1\n");
					stop = true;
				}
				else if(e.getSource()==b2){
					t.append("Wciśnięto b2\n");
					if(stop==true){
						stop=false;
						notify();	
					}
				}
				if(e.getSource()==b3){
					t.append("Wciśnięto b3\n");
					stop = true;
				}
				else if(e.getSource()==b4){
					t.append("Wciśnięto b4\n");
					if(stop==true){
						stop=false;
						notify();
					}
				}
			}
		}
	}
	
	public static void main(String argv[]){
		GUI g = new GUI();
		g.setVisible(true);
	}
	
}
0

Jesli dobrze, zrozumialem, to chcesz zeby oba watki zmienialy ten jeden licznik, tak ?

0

Tak

0

Do tego co obrazowo napisał Luno można dodać tylko tę jedną różnicę między samaforem kolejowym, a programowym, że semafor kolejowy podnosi się i opuszcza tylko raz. Natomiast "semafor programowy" jakim jest obiekt na bloku synchronizującym można podnosić wielokrotnie (przez ten sam pociąg).
To tak jakby przy wjeżdżaniu na tor podnosiło się semafor o trochę, a przy ponownym wjeżdżaniu bez wcześniejszego wyjeżdżania (tu to możliwe) podnosiłoby się go znowu o trochę.

Ten dziwny przypadek zachodzi kiedy w bloku synchronizowanym wywoła się jakąś inną metodę, która też będzie miała blok synchronizowany na tym samym obiekcie. Gdyby nie było wielokrotnej blokady, to w przypadku wywołania przez metodę synchronizowaną innej metody synchronizowanej wątek zatrzymałby się zakleszczony bo nie miałby już żadnej szansy wyjścia z wcześniejszego bloku synchronizowanego.
Dzięki tej własności można zaimplementować synchronizowaną metodę set w taki sposób, że może ona wywoływać synchronizowaną get dla uzyskania dotychczasowej wartości zmiennej.

Co do tego, że grafika "lepiej reaguje", kiedy usunie się synchronizację, to wynika to z tego, że synchronizacja jest czasowo niesamowicie kosztowna. Na przykład z tego powodu kontener Vector jest o wiele wolniejszy od ArrayList. Z tego powodu jeżeli tylko się da używa się technik, które pozwalają na atomowe (niepodzielne na poziomie CPU) operacje na zmiennych. W takim celu Sun wykombinował klasy z rodziny Atomic (AtomicInteger, AtomicLong, AtomicReference), które bazują na niepodzielnych operacjach przypisania i odczytu, dzięki którym konkurujące wątki nie są w stanie uszkodzić tej samej danej, które modyfikują "w tym samym momencie" (bo wtedy to już nie jest ten sam moment).
Oprócz tego również odczyt i przypisanie zmiennej prostego typu boolean jest atomowe. Atomowość innych typów jest zależna od implementacji, więc lepiej na tym nie bazować. Na przykład przypisanie i odczyt long na systemach 32-bitowych na pewno nie jest atomowe. Również atomowa nie jest inkrementacja i dekrementacja - nawet jeżeli odbywa się na typie byte.
Ale zamiana synchronizacji na operacje atomowe jest bardzo trudnym i grząskim tematem. Raczej dla speców. Raz się uda, 10 razy się nie uda.

0
Olamagato napisał(a)

Oprócz tego również odczyt i przypisanie zmiennej prostego typu boolean jest atomowe. Atomowość innych typów jest zależna od implementacji, więc lepiej na tym nie bazować. Na przykład przypisanie i odczyt long na systemach 32-bitowych na pewno nie jest atomowe. Również atomowa nie jest inkrementacja i dekrementacja - nawet jeżeli odbywa się na typie byte.

Przypisywanie wszystkich typow prostych poza long i double (ktore sa 64 bitowe) sa w specyfikacji atomowe. long i double rowniez moga tak byc zaimplementowane, jednak nie ma gwarancji. Przypisywanie referencji rowniez jest gwarantowane specyfikacja ze jest atomowe, nawet jesli referencje to tak naprawde 64bitowe adresy czy cokolwiek. To tak gwoli uzupelnienia.

0

Hmm, od której wersji? (jeżeli pamiętasz)

0

Ze specyfikacji, 3 edycja (http://java.sun.com/docs/books/jls), zdaje sie ze od Java 5:

17.7. Non-atomic Treatment of double and long
Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32 bit values. For efficiency's sake, this behavior is implementation specific; Java virtual machines are free to perform writes to long and double values atomically or in two parts.
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64 bit value from one write, and the second 32 bits from another write. Writes and reads of volatile long and double values are always atomic. Writes to and reads of references are always atomic, regardless of whether they are implemented as 32 or 64 bit values.
VM implementors are encouraged to avoid splitting their 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.

Tylko ta czytalem, ale w spec edycji 2 nie moglem znalezc nic o referencjach (odpowiedni punkt to 8.4).

Pozdrawiam.

0

Ups, poprawka, jednak jest to we wszystkich wersjach poza jedna ktora mialem na dysku ;d

0

Dzięki za kolejne wyjaśnienia. Spróbuje to porobić z deklarowaniem zmiennych jako volatile i trochę przemyślę jeszcze moment i warunek wywoływania blokady na obiekcie przez wątki.

1 użytkowników online, w tym zalogowanych: 0, gości: 1