Модераторы: LSD, AntonSaburov
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Многопоточное программирование, Проблема с остановкой 
:(
    Опции темы
simanyay
Дата 17.6.2004, 21:21 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Антон Ковалёв
****


Профиль
Группа: Участник
Сообщений: 2053
Регистрация: 22.8.2002

Репутация: 3
Всего: 36



Привет. Вот только вчера связался с многпоточным программированием в Java и тут же вопрос:
Как мне остановить поток? Я пытаюсь с помощью wait(), но поток не останавливается. Мало того, основной поток ждёт конца его выполнения.
Вот код:

Код

package enc.util;

// JLayer imports
import javazoom.jl.player.advanced.*;
import javazoom.jl.decoder.*;

// Other imports
import java.io.*;

public class SoundPlayer extends Thread {
// JLayer objects. See 'JLayer' documentation for details.
private AdvancedPlayer player;

// Internal imports
private String fileName;

public SoundPlayer(String file, String name) throws FileNotFoundException, JavaLayerException {
 super(name);
 fileName = file;
 
 FileInputStream fis = new FileInputStream(fileName);
 player = new AdvancedPlayer(fis);
}

public void run() {
 try {
  player.play();
 } catch(JavaLayerException jle) {
  System.out.println("Internal Exception in SoundPlayer thread: " + jle);
 }
}

public synchronized void pause() {
 try {
  wait();
 } catch(InterruptedException ie) {
  System.out.println("Internal Exception in SoundPlayer thread: " + ie);
 }
}
}


Вызов:
Код

...
try {
soundPlayer = new SoundPlayer(AppPath.getDataPath() + "media/background.mp3", "BGSound");
soundPlayer.start();
soundPlayer.pause();
} catch(FileNotFoundException fnfe) {
JOptionPane.showMessageDialog(this, "Could not open music file. Contact technical support, please.",
      "Initialization error", JOptionPane.ERROR_MESSAGE);  
} catch(JavaLayerException jle) {
JOptionPane.showMessageDialog(this, "Could not open music file. Contact technical support, please.",
      "Initialization error", JOptionPane.ERROR_MESSAGE);  
}
...


Надеюсь на Вашу помощь. Заранее благодарен.

P.S. При проигрывании музыки через AdvancedPlayer, приложение полностью виснет до конца песни. Поэтому мне и нужно выделить его в отдельный поток


--------------------
«It's better to be a pirate than to join the Navy» — Steve Jobs.
PM MAIL WWW   Вверх
Domestic Cat
Дата 17.6.2004, 22:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Вот что Вы делаете:
1. Запускаете поток (он выполняет play())
2. из main вызываете pause

то есть поток 1 (SoundPlayer) играет play
поток 2 (main) выполняет wait и останавливаетса

Если хототе сделать паузу, то скорее всего должен быт метод типа pause у самого плеера.

Код

class Example extends Thread
{
  Player player;

  public void run()
  {
       play();
   }

  public void play()
 {
      player.play();
  }

  public void stop()
 {
      player.stop();
  }
}

class ....
{
  ....
  ....
  Example example = new Example();
  example.start();
  example.stop();
  ...
}



Это сообщение отредактировал(а) Domestic Cat - 17.6.2004, 22:28


--------------------

PM   Вверх
LSD
Дата 17.6.2004, 22:26 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Leprechaun Software Developer
****


Профиль
Группа: Модератор
Сообщений: 15718
Регистрация: 24.3.2004
Где: Dublin

Репутация: 210
Всего: 538



Есть 2 варианта:
1) Thread.stop() - но он не рекомендуется так как может вызывать блокировки
2) попытаться у player вызвать метод stop()


--------------------
Disclaimer: this post contains explicit depictions of personal opinion. So, if it sounds sarcastic, don't take it seriously. If it sounds dangerous, do not try this at home or at all. And if it offends you, just don't read it.
PM MAIL WWW   Вверх
Domestic Cat
Дата 17.6.2004, 23:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Может, чего-нибудь в фак по потокам накропать?


--------------------

PM   Вверх
Sun
Дата 18.6.2004, 10:13 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Account removed
***


Профиль
Группа: Экс. модератор
Сообщений: 1611
Регистрация: 14.8.2002

Репутация: 8
Всего: 48



Цитата(Domestic @ 17.6.2004, 20:58)
Может, чего-нибудь в фак по потокам накропать?

Отличная идея, если вам не трудно, будем очень признательны smile.gif


--------------------
Account removed
PM MAIL   Вверх
cardinal
Дата 18.6.2004, 12:07 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Инженер
****


Профиль
Группа: Экс. модератор
Сообщений: 6003
Регистрация: 26.3.2002
Где: Германия

Репутация: нет
Всего: 99



Я сделал одну глобальную переменную и управлял ей всем потоком. Поможет это тебе или нет я не знаю, т.к. твой код в Java для меня выглядит довольно непривычно smile.gif Думаю надо сделать все в корне подругому...

Прочитай мое сообщение от 28.10.2003, 16:41 вот тут (в нем говорится об остановке цикла, но это не суть - принцип тот же):
http://forum.vingrad.ru/index.php?showtopi...оток,and,глобал


--------------------
Немецкая оппозиция потребовала упростить натурализацию иммигрантов
В моем блоге: Разные истории из жизни в Германии

"Познание бесконечности требует бесконечного времени, а потому работай не работай - все едино".  А. и Б. Стругацкие
PM   Вверх
Domestic Cat
Дата 18.6.2004, 15:11 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



2Cardinal
Я че-то не поиму, а как связан VB с Java? В Java глобальных переменных нет, кстати.

Sun
ok segodnja posizhu, esli uspeju - sdelaju post zdes' smile.gif

PS. a kak pri ispravlenii iz translita v russkii peregnat sad.gif

Это сообщение отредактировал(а) Domestic Cat - 18.6.2004, 15:13


--------------------

PM   Вверх
Sun
Дата 18.6.2004, 15:39 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Account removed
***


Профиль
Группа: Экс. модератор
Сообщений: 1611
Регистрация: 14.8.2002

Репутация: 8
Всего: 48



Цитата(Domestic @ 18.6.2004, 12:11)
PS. a kak pri ispravlenii iz translita v russkii peregnat sad.gif

Я могу потом подредактировать. Можете вставить ответ прямо в этой теме, а я поправлю транслит на кириллицу и перенесу этот пост в FAQ.


--------------------
Account removed
PM MAIL   Вверх
cardinal
Дата 18.6.2004, 18:53 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Инженер
****


Профиль
Группа: Экс. модератор
Сообщений: 6003
Регистрация: 26.3.2002
Где: Германия

Репутация: нет
Всего: 99



Цитата
Я че-то не поиму, а как связан VB с Java? В Java глобальных переменных нет, кстати.

Никак, но поток он и в Африке поток smile.gif. Вопрос то был как им управлять. А то что глобальных переменных нет - это что-то странное, неужели на самом деле нет? Java это же собрат C++!


--------------------
Немецкая оппозиция потребовала упростить натурализацию иммигрантов
В моем блоге: Разные истории из жизни в Германии

"Познание бесконечности требует бесконечного времени, а потому работай не работай - все едино".  А. и Б. Стругацкие
PM   Вверх
Sun
Дата 18.6.2004, 18:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Account removed
***


Профиль
Группа: Экс. модератор
Сообщений: 1611
Регистрация: 14.8.2002

Репутация: 8
Всего: 48



Цитата(cardinal @ 18.6.2004, 15:53)
А то что глобальных переменных нет - это что-то странное, неужели на самом деле нет? Java это же собрат C++!

Вместо глобальных переменных используются статические переменные класса. Статическая переменная существует в одном экземпляре для данной виртуальной машины.


--------------------
Account removed
PM MAIL   Вверх
Domestic Cat
Дата 18.6.2004, 19:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Napisal, tol'ko teper' zapostit' ne mogu - pochemu-to poluchaetsja odna ] adv/57.gif

Это сообщение отредактировал(а) Domestic Cat - 18.6.2004, 19:15


--------------------

PM   Вверх
Domestic Cat
Дата 18.6.2004, 19:16 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Синхронизация потоков
Если на вашем компьютере один процессор, то в данныи момент времени может выполняться может только один поток - все остальные потоки приостановены (suspended). Планировшик занимается переключением потоков и распределением времени их выполнения. Отсюда 2 вывода:

- Thread.sleep(int ms) не обязателно "спит" именно ms миллисекунд. Когда поток "проснется" зависит от планировшика.
- установка приоритета потока - всего лишь пожелание; это значит что скорее всего поток с приоритетом 9 получит больше времени на исполнение чем поток с приоритетом 8. Хотя для HotSpot наверное, так и будет, а вот для браузеров - вряд ли.

2. Перейдем собственно к синхронизации. Зачем она нужна? Посмотрим на такую программу:
Код
public class Test extends Thread
{
   private static A a = new A();
   
   public static void main(String [] args)
   {
       for (int i = 0; i < Integer.parseInt(args[0], 10); i++)
       {
           Test test = new Test();
           test.start();
       }
   }
   public void run()
   {
       doSomeWork();
   }
   
   public void doSomeWork()
   {
       a.doWork();
   }
}

class A
{
   private static int a1 = 0;
   private int a2 = 0;

   public void doWork()
   {
       int a3 = 0;
       a1++; a2++; a3++;
       try
       {
           Thread.sleep((int) (Math.random() * 100));
       }
       catch (InterruptedException ie)
       {
           System.out.println("Interrupted");
       }
       a1--; a2--; a3--;
       System.out.println("Thread:  " + Thread.currentThread().getName() +
                     " :   a1 = " + a1 + ", a2 = " + a2 + ", a3 = " + a3);
   }
}


Если запустить ее: java Test <N> то она запустит N потоков, передав каждому ссылку на объект класса А; ну а потоки вызывают метод которыи меняет статическую переменную а1, переменную объектa а2 и локальную переменную а3. Если запустить один поток, то мы увидим, естественно:
Код
$> Thread:  Thread-0 :   a1 = 0, a2 = 0, a3 = 0

A вот для 100 потоков результат будет инои:
Код

...
Thread:  Thread-81 :   a1 = 2, a2 = 2, a3 = 0
Thread:  Thread-79 :   a1 = 1, a2 = 1, a3 = 0
Thread:  Thread-84 :   a1 = 0, a2 = 0, a3 = 0
Thread:  Thread-95 :   a1 = 13, a2 = 13, a3 = 0
...

Получилось так потому, что а1 и а2 не thread-safe. Дело в том, что при создании каждыи поток получает свои участок памяти (он называется "рабочая память") на стеке; и соответственно, каждыи поток получает свою копию локальних переменных. Именно поэтому а3 всегда в результате равно 0 - каждыи поток оперирует своей а3. Локальные переменные всегда thread-safe.

Проблема возникает для переменных класса и объекта. Oбъект создается на хипе (heap - куча), потоки получают копию ссылки на объект, и поетому работают с одним и тем же объектом. Поскольку мы не знаем как планировшик распределит время выполнения между потоками, то и предсказать результат невозможно: попав внутрь метода doWork() поток изменяет а1, а2, и останавливается (sleep); в это время другие потоки изменяют а1, а2. В резyльтате для потока а1 и а2 до sleep бутут иными чем после sleep.

В целом проблема в следуюшем : поскольку потоки имеют доступ к не- thread-safe переменным, и действия планировшика неопредсказуемы, получается что программист ограничен работать только с локальными переменными в многопотоковом коде; иначе результаты предсказать нелзя.

3. Чтобы снять это ограничение, и введена синхронизация. Каждыи объект в Java получает "лок" (lock - замок); и этот лок только один. Встретив слово synchronized поток пытается забрать лок у объекта; если ему ето удается, он попадает внутрь (метода или блока). Если второи (третии итп) потоки попытаются теперь выполнить любой synchronized метод или блок на данном объекте, они не смогут получить лок и будут приостановлены. После того как поток покидает synchronized блок, он возврашяет лок объекту и ждушие потоки начинают "борьбу" за лок (это называется "concurrent access").
Заметим, что на не-synchronized методы/kод это не распространяется, даже если один из потоков взял лок, не-synchronized методы могут быть вызваны любым количеством потоков одновременно.
Переделаннаыа программа быдет иметь вид:
Код

public class Test extends Thread
{
   private static A a = new A();
   
   public static void main(String [] args)
   {
       for (int i = 0; i < Integer.parseInt(args[0], 10); i++)
       {
           Test test = new Test();
           test.start();
       }
   }
   public void run()
   {
       doSomeWork();
   }
   
   public void doSomeWork()
   {
       a.doWork();
   }
}

class A
{
   private static int a1 = 0;
   private int a2 = 0;

   public synchronized void doWork()
   {
       int a3 = 0;
       a1++; a2++; a3++;
       try
       {
           Thread.sleep((int) (Math.random() * 100));
       }
       catch (InterruptedException ie)
       {
           System.out.println("Interrupted");
       }
       a1--; a2--; a3--;
       System.out.println("Thread:  " + Thread.currentThread().getName() +
                     " :   a1 = " + a1 + ", a2 = " + a2 + ", a3 = " + a3);
   }
}

Результат будет такои:
Код

...
Thread:  Thread-93 :   a1 = 0, a2 = 0, a3 = 0
Thread:  Thread-94 :   a1 = 0, a2 = 0, a3 = 0
Thread:  Thread-95 :   a1 = 0, a2 = 0, a3 = 0
Thread:  Thread-96 :   a1 = 0, a2 = 0, a3 = 0
...

Чем меньшии блок вы синхронизируете, тем лучше. Запустив этот код вы увидите что скорость по сравнению с первои программои значительно снизилась. Если раньше потоки выполняли метод одновременно, то теперь - по очереди, что и снижает скорость. Понятно что для нескольких потоков код :
Код

int methodA()
{
    ...
    synchronized (this)
    {
         doSomeStuff();
    }
    ...
}

будет всегда выгоднее чем:
Код

synchronized int methodA()
{
    ...
    doSomeStuff();
    ...
}

Синхронизируя метод, мы синхронизируем на объекте "this" (то есть поток будет забирать лок у самого объекта ); но никто не мешает синхронизировать на любом другом объекте:
Код

synchronized (MyClass.class) {...}
synchronized (object) {...}
..

4. Теперь посмотрим на такую программу:
Код

public class Test
{

   public static void main(String [] args)
   {
       A a = new A();
       Thread1 p1 = new Thread1(a);
       Thread2 p2 = new Thread2(a);
       p1.start();
       p2.start();
   }
}

class A
{
   private int result;
   public int getResult()
   {
       return result;
   }
   public void doWork()
   {
       for (int i = 0; i < 100; i++, result++);
   }
}

class Thread1 extends Thread
{
   private A a;
   Thread1 (A a)
   {
       this.a = a;
   }
   public void run()
   {
       a.doWork();
   }
}
class Thread2 extends Thread
{
   private A a;
   Thread2 (A a)
   {
       this.a = a;
   }
   public void run()
   {
       System.out.println(a.getResult());
   }
}

Задумка такая : один поток считает, второй "подбирает" результат. Все бы хорошо, но опять проблема: результат будет зависеть от порядка старта потоков: для p1.start(); p2.start(); мы получим 100, а для p2.start(); p1.start() - 0.

Дело в том, что потоки понятия не имеют друг о друге; во втором случае поток 2 (p2) получает значение переменнои раньше чем p1 успевает его посчитать. Да и то что в первом случае результат будет всегда 100 при повторных запусках никто не гарантирует - неизвестно успеет ли p1 закончить вычисления до того как p2 затребует результат.

Для коммуникатции потоков используыутся методы wait(), notify() и notifyAll().

wait заставяет поток остановиться и выпустить лок; так что другои поток (ждушии "снаружи" synchronized) сможет его затребовать и воити в блок/метод.
notifyAll сообшает всем ждущим на wait потокам, что данныи поток собирается выпустить лок (ну а notify - одному из ждуших потоков, первом попавшим на wait). Как это работает мы посмотрим на исправленном варианте кода:
Код

public class Test
{

   public static void main(String [] args)
   {
       A a = new A();
       Thread1 p1 = new Thread1(a);
       Thread2 p2 = new Thread2(a);
       p1.start();
       p2.start();
   }
}

class A
{
   private int result;
   private boolean isReady = false;
   
   public synchronized int getResult()
   {
       try
       {
           while (!isReady) wait();
       }
       catch (InterruptedException ie)
       {}
       isReady = false;
       return result;
   }
   public synchronized void doWork()
   {
       for (int i = 0; i < 100; i++, result++);
       isReady = true;
       notify();
   }
}

class Thread1 extends Thread
{
   A a;
   Thread1(A a)
   {
       this.a = a;
   }
   public void run()
   {
       a.doWork();
   }
}
class Thread2 extends Thread
{
   A a;
   Thread2(A a)
   {
       this.a = a;
   }
   public void run()
   {
       System.out.println(a.getResult());
   }
}

Возможны 2 варианта:
a. p2 пытаетса выполнить getResult, и получает лок от объекта a. Поскольку isReady равен false, p2 попадает в цикл, выполняет wait и отпускает лок. p1 попадает в doWork, считает, изменяет isReady и уведомляет (notify) p2 что все готово. p2 выходит из цикла и получает результат.
б. p1 первым попадает в doWork; он получает лок, изменяет isReady и выполняет notify которыи ничего не делает, посколку p2 попросту не может попасть внутрь getResult. p1 отдает лок, p2 его получает, благополучно пропускает цикл и получает результат.
Здесь результат уже от порядка вызова методов зависеть не будет и всегда равен 100.

Заметим, что без wait/notify это работать не будет!
Если wait убрать, поток p2 будет крутить цикл "вхолостую", отнимая ценные ресурсы; но это не главное.
Проблема возникнет если p2 первым получит лок - он навсегда останетса в цикле, в то время как p1 не сможет попасть в doWork. Такая ситуация называетса deadlock. Вот более красивыи пример:
Код

class A
{
    private B b;
    A (B b)
    {
         this.b = b;
    }
   
    synchronized void methodA()
    {
          b.methodB();
    }
}
class B
{
    private A a;
    B (A a)
    {
         this.a = a;
    }
   
    synchronized void methodB()
    {
          a.methodA();
    }
}

Пусть p1 получил лок на объекте a, а p2 - на объекте b. Для продолжения работы p1 нужно получить лок от b - но этот лок у p2; соответственно, p2 нужен лок от b, но он у p1!

5. Наконец, последнее. На самом деле поток может кэшировать переменных (хотя он и имеет ссылку на соответствующий объект на хипе). Рассмотрим этот вопрос на примере переменных а1 и а2 из первой программы.
Java хранит значения переменных а1 и а2 в так называемой "главной памяти" ("main memory"). Тем не менее поток может сохранить значение этих переменных "для себя", чтобы не обновлять их каждый раз. В результате значения переменных у различных потоков будут несогласованными. Если вы хотите, чтобы поток каждый раз "освежал" значение переменной, получая его из main памяти, используйте volatile, например:
Код

class A
{
      private static volatile int a1;
      private volatile int a2;
      .....

Проверить действие volatile можно с помошю модификации первого примера:
Код

public class Test extends Thread
{
   public static A a = new A();
   
   public static void main(String [] args)
   {
       for (int i = 0; i < Integer.parseInt(args[0], 10); i++)
       {
           Test test = new Test();
           test.start();
       }
   }
   public void run()
   {
       doSomeWork();
   }
   
   public void doSomeWork()
   {
       for (int j = 0; j < 100; j++) a.doWork();
   }
}

class A
{
   volatile int a1 = 0;
   int a2 = 0;

   public void doWork()
   {
       int a3 = 0;
       a1++; a2++; a3++;                
       try
       {
           Thread.sleep((int) (Math.random() * 100));
       }
       catch (InterruptedException ie)
       {
           System.out.println("Interrupted");
       }
       a1--; a2--; a3--;

       if (a1 != a2) System.out.println("a1 = " + a1 + "   a2  = " + a2 );
   }
}

Поскольку а1 синхронизируется с главной памятью, а а2 - нет, то при запуске java Test 1000 вы получите что-то вроде:
Код

...
a1 = 53   a2  = 391
a1 = 52   a2  = 390
a1 = 51   a2  = 389
a1 = 50   a2  = 388
a1 = 49   a2  = 387
...

Как видим, потоки закэшировали а2, в результате чего каждый поток изменяет свою копию а2, в то время как а1 синхронизирована на всех потоках.

Это сообщение отредактировал(а) Domestic Cat - 19.6.2004, 01:50


--------------------

PM   Вверх
Domestic Cat
Дата 20.6.2004, 01:06 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Экс. модератор
Сообщений: 5452
Регистрация: 3.5.2004
Где: Dallas, US

Репутация: 50
Всего: 172



Хм, в случае с volatile я не учел цто пока поток пытается напечатать а1 и а2 другие потоки могут их
изменит. Сегодня попробовал запустить - что с volatile что без, разницы нет. Где-то я читал что volatile штука загадочная, на некоторых JVM он на работает.


--------------------

PM   Вверх
Sardar
Дата 20.6.2004, 02:46 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Бегун
****


Профиль
Группа: Модератор
Сообщений: 6986
Регистрация: 19.4.2002
Где: Нидерланды, Groni ngen

Репутация: 4
Всего: 317



По идее это volatile переменные изменяемые внезапно железом, компилятор при каждом обращении к такой переменной должен обновлять ее значение.


--------------------
 Опыт - сын ошибок трудных  © А. С. Пушкин
 Процесс написания своего велосипеда повышает профессиональный уровень программиста. © Opik
 Оценить мои качества можно тут.
PM   Вверх
ElectricalStorm
Дата 22.6.2004, 09:58 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник Клуба
Сообщений: 547
Регистрация: 22.1.2004

Репутация: 5
Всего: 9



Domestic Cat - интрересно


--------------------
Нужно знать инструмент, которым пользуешься
PM MAIL   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Правила форума "Java"
LSD   AntonSaburov
powerOn   tux
javastic
  • Прежде, чем задать вопрос, прочтите это!
  • Книги по Java собираются здесь.
  • Документация и ресурсы по Java находятся здесь.
  • Используйте теги [code=java][/code] для подсветки кода. Используйтe чекбокс "транслит", если у Вас нет русских шрифтов.
  • Помечайте свой вопрос как решённый, если на него получен ответ. Ссылка "Пометить как решённый" находится над первым постом.
  • Действия модераторов можно обсудить здесь.
  • FAQ раздела лежит здесь.

Если Вам помогли, и атмосфера форума Вам понравилась, то заходите к нам чаще! С уважением, LSD, AntonSaburov, powerOn, tux, javastic.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Java: Общие вопросы | Следующая тема »


 




[ Время генерации скрипта: 0.1034 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.