Kodomo

Пользователь

Учебная страница курса биоинформатики,
год поступления 2010

Многопоточность. Афинные преобразования.

Треды

Как вообще выполняется наша программа? Рассмотрим простейшую программу: {{#!Java public class T{

} }} её выполнение можно схематично описать так: ->main

<- Стрелочки соединяющие инструкции которые выполняет java-машина являются нитью, или Thread выполнения (на русский однако это слово обычно переводят как поток, но чтобы не путаться с потоками ввода-вывода я буду использовать слова Thread или нить). В Java можно управлять тредами, для этой цели есть класс Thread. Любая программа имеет хотя бы один тред, посмотрим что можно сделать с ним:

   1 public class Test1 {
   2         public static void main(String[] args) {
   3                 Thread t = Thread.currentThread();//получить текущий тред
   4                 System.out.println(t);//напечатать информацию о нем
   5                 t.setName("main thread");//поменять имя треда
   6                 System.out.println("next name: "+t);
   7                 for(int i=0;i<5;i++){           
   8                         try {
   9                                 Thread.sleep(1000);//поспать минутку
  10                         } catch (InterruptedException e) {//если другой поток попробует разбудить этот
  11                                 System.out.println("thread is interrupted");
  12                         }
  13                         System.out.println(i);
  14                 }
  15         }
  16 }

Теперь попробуем сделать программу с несколькими тредами, для этого нам нужно создать новый Thread. А потом запустить его. Для того чтобы определить что будет делать данный тред надо переопределить метод run в Thread, а чтобы начать его выполнение надо вызвать метод start():

   1 class MyThread extends Thread{
   2         public MyThread(String name) {
   3                 super(name);
   4         }
   5         
   6         public void run() {
   7                 for(int i=0;i<5;i++){
   8                         System.out.println(getName()+": "+i);
   9                         try {
  10                                 Thread.sleep(1);
  11                         } catch (InterruptedException e) {
  12                                 System.out.println("thread "+this+"is interrupted");
  13                         }
  14                 }
  15         }
  16 }
  17 
  18 public class Test2 {
  19         public static void main(String[] args) {
  20                 MyThread t1 = new MyThread("thread1");
  21                 MyThread t2 = new MyThread("thread2");
  22                 MyThread t3 = new MyThread("thread3");
  23                 t1.start();
  24                 t2.start();
  25                 t3.start();
  26 
  27                 System.out.println("The end!");
  28 
  29         }
  30 }

В чем проблема написанного кода? дело в том что главный тред стартует все 3 дочерних треда потом напечатает The end и кончится. при этом дочернии потоки скорее всего еще не завершаться и the end окажется напечатан в середине программы. Чтобы это исправить надо попросить главный поток подождать пока дочернии не закончат выполнение:

   1                 try {
   2                         t1.join();
   3                         t2.join();
   4                         t3.join();
   5                 } catch (InterruptedException e) {
   6                         System.out.println("thread is interrupted");
   7                 }
   8                 System.out.println("The end!");

Когда два разных потока обращаются к одному объекту может возникнуть конфликт. Например вы не можете читать и менять текст одновременно, если вы попробуете это сделать, то возможно вы прочитаете гибрид из старого и нового текста. Рассмотрим простой пример:

   1 class Text{     //класс храняший текст
   2         char[] text;
   3         
   4         public Text(String t){
   5                 text = t.toCharArray();
   6         }
   7         
   8         
   9         public /*synchronized*/ void replace(String s){
  10                 for(int i=0;i<Math.min(text.length, s.length());i++){
  11                         text[i] = s.charAt(i);
  12                         try {
  13                                 Thread.sleep(9);
  14                         } catch (InterruptedException e) {
  15                                 System.out.println("thread is interrupted");
  16                         }
  17                 }
  18         }
  19         
  20         public /*synchronized*/ void print(){                   
  21                 for(char c : text){
  22                         try {
  23                                 System.out.print(c);
  24                                 Thread.sleep(10);
  25                         } catch (InterruptedException e) {
  26                                 System.out.println("thread is interrupted");
  27                         }
  28                 }
  29                 System.out.println();
  30         }
  31 }
  32 
  33 class PrinterThread extends Thread{//класс обеспечивающий печать текста в отдельном треде
  34         Text t;
  35         
  36         public PrinterThread(Text t) {
  37                 this.t = t;
  38                 start();
  39         }
  40         
  41         public void run() {
  42                 t.print();
  43         }       
  44 }
  45 public class Test3 {
  46         public static void main(String[] args) {
  47                 Text t = new Text("Маша любит куклу Дашу");
  48                 PrinterThread p1 = new PrinterThread(t);
  49                 t.replace("Вышел месяц из тумана");
  50                 PrinterThread p2 = new PrinterThread(t);
  51                 try {
  52                         p1.join();
  53                         p2.join();
  54                 } catch (InterruptedException e) {
  55                         System.out.println("thread is interrupted");
  56                 }       
  57 
  58         }
  59 }

Если запустить написанный пример несколько раз можно получить самые разные результаты, большинство из которых будут бессмысленны. Чтобы все стало хорошо надо чтобы методы replace и print нельзя было вызвать одновременно. Для этого есть ключевое слово synchronized, если некий тред вошел в синхронизированный метод некоторого объекта, то никакой другой тред не сможет войти ни в один синхронизированный метод данного объекта, он будет ждать, пока первый не тред не покинет синхронизированный метод. Т.е. чтобы спасти ситуацию надо исправить:

   1 public synchronized void replace(String s);
   2 public synchronized void print();

Хочется еще отметить тонкость работы с коллекциями. Если ваша программа имеет несколько тредов, которые обращаются к одной и той же коллекции (добавляют и удаляют ее элементы), то нужно использовать синхронизованные коллекции: Vector (синхронизованный аналог ArrayList) Hashtable (синхронизованный аналог HashMap)

Афинные преобразования

Вернемся к графике. В конце будет понятно, где нам пригодятся и треды :-) В графич. системе Java можно задать преобразование координат плоскости так, чтобы мы работали в исходных координатах, а рисовалось все в новых координатах. Преобразования, которые разрешены при этом, имеют вид:

   1 [ x']   [  m00  m01  m02  ] [ x ]   [ m00x + m01y + m02 ]
   2 [ y'] = [  m10  m11  m12  ] [ y ] = [ m10x + m11y + m12 ]
   3 [ 1 ]   [   0    0    1   ] [ 1 ]   [         1         ]

Это означает, что сохраняется прямолинейность и параллельность линий. Вектор {m00,m10} показывает направление оси x после преобразования, соответственно {m01,m11} – направление оси y. Точка (m02,m12) задает новое начало координат. Конструктор для такого преобразования имеет вид

   1 AffineTransform at = new AffineTransform(double m00, double m10, double m01, double m11, double m02, double m12);
   2 AffineTransform at_2 = new AffineTransform(at);

Самый простой способ использовать функции Graphics2D Полезные функции у Graphics2D:

   1 abstract  AffineTransform getTransform()// возвращает текущее преобразование (систему координат)
   2 abstract  void  rotate(double theta)//поворот системы координат на угол
   3 abstract  void  rotate(double theta, double x, double y)//поворот системы координат на угол вокруг точки
   4 abstract  void  scale(double sx, double sy)//растяжение
   5 abstract  void  translate(int x, int y) // сдвиг
   6 

Делаем свое приложение с поворотами:

   1 package gui;
   2 
   3 import java.awt.Color;
   4 import java.awt.Graphics;
   5 import java.awt.Graphics2D;
   6 import java.awt.geom.AffineTransform;
   7 
   8 import javax.swing.*;
   9 
  10 public class RevolvingSquares extends JFrame
  11 {
  12         private JPanel my_panel;
  13 
  14         public int x1 = 200;
  15 
  16         public int y1 = 200;
  17 
  18         double angle1 = 0;
  19         
  20         public int x2 = 100;
  21 
  22         public int y2 = 100;
  23 
  24         double angle2 = 0;
  25 
  26         public RevolvingSquares()
  27         {
  28                 
  29                 super();
  30                 
  31                 setSize(500, 400);
  32 
  33                 my_panel = new JPanel()
  34                 {
  35                         public void paintComponent(Graphics g)
  36                         {
  37                                 super.paintComponent(g);
  38                                 Graphics2D gr = (Graphics2D) g;
  39                                 AffineTransform old_at = gr.getTransform();
  40                 
  41                                 gr.rotate(angle1, x1, y1);
  42                                 gr.setColor(Color.green);
  43                                 gr.fillRect(x1 - 50, y1 - 50, 50, 50);
  44                                 gr.setColor(Color.black);
  45                                 gr.drawString("Крутящийся квадрат1", x1, y1 + 10);
  46                                 gr.setTransform(old_at);
  47                                 
  48                                 gr.rotate(angle2, x2, y2);
  49                                 gr.setColor(Color.red);
  50                                 gr.setColor(Color.black);
  51                                 gr.drawString("Крутящийся квадрат2", x2, y2 + 10);
  52                                 gr.fillRect(x2 - 50, y2 - 50, 50, 50);
  53                                 
  54                                 gr.setTransform(old_at);
  55                         }
  56                 };
  57                 getContentPane().add(my_panel);
  58         }
  59 
  60         private void startThread()
  61         {
  62                 Thread t = new Thread()
  63                 {
  64                         public void run()
  65                         {
  66                                 for (;;)
  67                                 {
  68                                         try
  69                                         {
  70                                                 Thread.sleep(50);
  71                                         }
  72                                         catch (Exception ex)
  73                                         {
  74                                         }
  75                                         if (isVisible())
  76                                         {
  77                                                 angle1 += 2.0 * Math.PI / 360.0;
  78                                                 angle2 += 3.0 * Math.PI / 360.0;
  79                                                 my_panel.repaint();
  80                                         }
  81                                 }
  82                         }
  83                 };
  84                 t.start();
  85         }
  86         public static void main(String[] args)
  87         {
  88                 RevolvingSquares m = new RevolvingSquares();
  89 
  90                 m.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
  91                 m.setVisible(true);
  92                 m.startThread();
  93         }
  94 }