четверг, 9 августа 2012 г.

Синхронизация одной переменной в языке Java

В языке Java для атомарной работы с одной переменной в многопоточном приложении существуют несколько способов. Напрмер можно использовать обобщённый способ, который подходит не только для атомарного доступа к переменной, но и для других целей. Это использование блоков или методов synchronized, например так:

public class Test2 extends Thread {
 
 public Test2(String name){
  super(name);
 }
 
 public synchronized static void incrementCount(){
  count++;
 }
 
 @Override
 public void run(){
  int sleepTime = 0;
  for(int i = 0; i < 100; i++){
   sleepTime = (int)(Math.random()*10);
   try {
    sleep(sleepTime);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   incrementCount();
  }
 }
 
 public static int getCount(){
  return count;
 }
 
 private static int count = 0; 
}

Но в языке Java для атомарного доступа к одной переменной существует более элегантный способ. Это классы пакета java.util.concurrent.atomic. Напрмер для целочисленной переменной код будет выглядеть вот так:

import java.util.concurrent.atomic.AtomicInteger;

public class Test4 extends Thread {
  
 public Test4(String name){
  super(name);
 }
 
 @Override
 public void run(){
  int sleepTime = 0;
  for(int i = 0; i < 100; i++){
   sleepTime = (int)(Math.random()*10);
   try {
    sleep(sleepTime);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   count.incrementAndGet();
  }
 }
 
 public static int getCount(){
  return count.intValue();
 }

 private final static AtomicInteger count = new AtomicInteger(0);
}

воскресенье, 3 июня 2012 г.

Try с ресурсами Java 7

Управление ресурсами с помощью конструкции Try-Catch-Finally, как это было раньше

До выхода Java 7 управление ресурсами требовало явного закрытия, что было несколько утомительно.

Посмотрите на этот метод, который читает данные из файла и выводит их в System.out:
private static void printFile() throws IOException {
    InputStream input = null;

    try {
        input = new FileInputStream("file.txt"); //!

        int data = input.read(); //!
        while(data != -1){
            System.out.print((char) data);
            data = input.read(); //!
        }
    } finally {
        if(input != null){
            input.close(); //!
        }
    }
}
Код, помеченный восклицательным знаком, это места где код может выбросить исключение. Как вы можете видеть это может случиться в трёх местах внутри блока try и в одном месте внутри блока finally.

Блок finally всегда выполняется вне зависимости будет выброшено исключение в блоке try или нет. Это означает, что InputStream будет закрыт вне зависимости от того, что произойдёт в блоке try. Метод close() класса InputStream так же может выбрасывать исключение, если не удалось закрыть поток.

Представте, что исключение возникает в блоке try. Блок finally будет выполнен. После чего представте, что в блоке finally так же возникнет исключение. Как вы думаете, какое из исключений будет на верху стека вызовов?

Исключение из блока finally будет на верху стека вызовов, даже не смотря на то, что исключение из блока try, вероятно,  будет более подходящим.

try с ресурсами

В Java 7 вы можете написать код из примера выше используя try c ресурсами например так:

private static void printFileJava7() throws IOException {

    try(FileInputStream input = new FileInputStream("file.txt")) {

        int data = input.read();
        while(data != -1){
            System.out.print((char) data);
            data = input.read();
        }
    }
}
Обратите на первую строку метода:
try(FileInputStream input = new FileInputStream("file.txt")) {
Это констукция try с ресурсами. FileInputStream объявлена в круглых скобках после ключевого слова try. Кроме того для класса FileInputStream создана и объявлена переменная.

В конце блока try FileInputStream будет закрыт автоматически. Это возможно потому, что FileInputStream реализует интерфейс java.lang.AutoCloseable. Все классы, реализующие этот интерфейс, могут быть использованы в конструкции try с ресурсами.

Если исключение будет выброшенно и в конструкции try с ресурсами и при закрытии FileInputStream (вызову метода close()), то исключение выброшенное в внутри блока try будет выборшенно во внешний мир. Исключение, выбрасываемое во время закрытия FileInputStream будет подавлено. Это альтернатива тому, что происходит, когда мы используем конструкцию try-finally.


Использование нескольких ресурсов

Внутри констукции try с ресурсами вы можете использовать несколько ресурсов и все они будут автоматически закрыты. Например:

private static void printFileJava7() throws IOException {

    try(  FileInputStream     input         = new FileInputStream("file.txt");
          BufferedInputStream bufferedInput = new BufferedInputStream(input)
    ) {

        int data = bufferedInput.read();
        while(data != -1){
            System.out.print((char) data);
    data = bufferedInput.read();
        }
    }
}
В этом примере создаются два ресурса внутри круглых скобок после ключевого слова try — FileInputStream  и  BufferedInputSream. Оба ресурса будут автоматически закрыты, как только код дойдёт до конца блока try

Ресурсы будут закрыты в обратном порядке тому, в котором они создавались в круглых скобках. Сначала будет закрыт BufferedInputSream, затем FileInputStream.


Создание классов реализующих интерфейс AutoClosable

Конструкция try с ресурсами может работать не только со встроенными в стандартную библиотеку языка Java классами. Вы так же можете реализовывать интерфейс java.lang.AutoCloseable в вашем собственном классе, и затем использовать его в конструкции try с ресурсами.

Интерфейс AutoCloseable описан всего один метод с именем close(). Вот как он выглядит:

public interface AutoClosable {

    public void close() throws Exception;
}
Любой класс, реализующий этот интерфейс может быть использован в конструкции  try с ресурсами. Вот пример такого класса:

public class MyAutoClosable implements AutoCloseable {

    public void doIt() {
        System.out.println("MyAutoClosable Работает!");
    }

    @Override
    public void close() throws Exception {
        System.out.println("MyAutoClosable закрыт!");
    }
}
Метод doIt() не является частью интерфейса AutoCloseable. Он присутствует в нашем классе потому, что мы хотим сделать нечто более, чем просто закрыть объект.

Приведём пример, как можно использовать созданный нами класс в конструкции try с ресурсами:

private static void myAutoClosable() throws Exception {

    try(MyAutoClosable myAutoClosable = new MyAutoClosable()){
        myAutoClosable.doIt();
    }
}
Если мы запустим этот код, то увидим строку, выведенную с помощью System.out из вызванного метода myAutoClosable():
MyAutoClosable работает!
MyAutoClosable закрыт!
Как вы можете видеть, конструкция try с ресурсами достаточно мощный способ быть уверенным в том, что ресурсы внутри try-catch блока будут закрыты корректно, вне зависимости создали мы эти ресурсы на основе своих собственных классов, или с помощью встроенных в стандартную библиотеку языка Java.

Это перевод статьи.

суббота, 2 июня 2012 г.

Вычисления в Java. Math. StrictMath. strictfp.

В стандартной библиотеке языка Java содержатся два класса для произведения базовых математических операций, таких как возведение в степень, взятие логарифма, синуса, косинуса и так далее. Эти классы отличаются между собой различной точностью представления чисел на разных архитектурах процессора. Это классы Math и StrictMath

При использовании StrictMath вы получите абсолютно одинаковый результат вычислений не зависимо от процессора, который установлен у пользователя вашей программы. Прямо говоря, эта функция задумывалась создателями языка как базовая для всех примитивных типов данных и манипуляций с ними. Но как оказалось это не такая простая задача и обеспечение одинаковых результатов на различных платформах значительно уменьшают быстродействие. Большинство задач просто не требуют такой точности в вычислениях, для них гораздо важнее скорость этих вычислений. Это привело к разделению математической части библиотеки на быструю и выдающую одинаковый результат на разных процессорах.

Таким образом если в решении вашей задачи важнее скорость — используйте класс Math, если же вам необходимо одинаковая работа (с точностью до бита) с примитивными типами данных, то ваш выбор — StrictMath. Константы и методы у них одинаковы, и более того некоторые методы класса Math просто вызывают аналогичные методы класса StrictMath.

Рассмотрим не типичные методы этих классов:

  • cbrt — возвращает кубический корень аргумента;
  • ceil — округляет значение в большую сторону;
  • copySign — возвращает первый аргумент со знаком второго;
  • floor — округляет в меньшую сторону;
  • hypot — возвращает квадратный корень из суммы аргументов: sqrt(x2+y2);
  • max — возвращает большее из двух чисел;
  • min — возвращает меньшее из двух чисел;
  • nextAfter — возвращает число с плавающей точкой, максимально приближенной к первому аргументу со стороны второго аргумента;
  • nextUp — то же самое, что и nextAfter, но только всегда в большую сторону;
  • random — возвращает случайное число от 0 до 1;
  • round — обычное округление;
  • signum — возвращает 1.0 если число больше 0 и -1.0 если число меньше нуля и ноль, если аргумент равен нулю;
  • toDegrees — переводит радианы в градусы;
  • toRadians — переводит градусы в радианы;

Пример:
  System.out.println(Math.cbrt(8)); // 2.0
  System.out.println(Math.ceil(2.1)); // 3.0
  System.out.println(Math.copySign(3.4, -11.2)); // -3.4
  System.out.println(Math.floor(9.999)); // 9.0
  System.out.println(Math.hypot(3,4)); // 5.0
  System.out.println(Math.max(3, 2)); // 3
  System.out.println(Math.min(3, 2)); // 2
  System.out.println(Math.nextAfter(4.45634D, -100)); // 4.456339999999999
  System.out.println(Math.nextUp(4.45634D)); // 4.456340000000001
  System.out.println(Math.random()); // 0.45357720245424116
  System.out.println(Math.round(4.5)); // 5
  System.out.println(Math.signum(-34.78)); // -1.0
  System.out.println(Math.toDegrees(Math.PI)); // 180.0
  System.out.println(Math.toRadians(180)); // 3.141592653589793
Остальные методы смотрите в документации: java.lang.Math, jav.lang.StrictMath.

А что нам делать, если мы хотим создать класс, в методах которого все действия с числами будут также гарантированно одинаковые на различных платформах? Или не класс, а метод? В таком случае в языке Java существует ключевое слово strictfp. Его можно добавлять к классу, интерфейсу и методам. Добавив его мы можем быть уверены, что на всех платформах при работе с числами мы получим одинаковый результат.

четверг, 31 мая 2012 г.

Массивы в языке Java, класс Arrays

Массивы являются элементом практически любой программы. Поэтому разработчики языка Java уделили этому вопросу достаточно много времени и проделали большую работу, что бы ими было пользоваться как можно удобней. В этой статье я рассмотрю, как самые известные способы работы, так и то, о чём часто даже не догадываются начинающие программисты, но что значительно упрощает программирование.

Для того чтобы начать работать с массивом какого либо типа нужно объявить переменную типа массив например для типа int это будет выглядеть так:
int[] numbers;
Или так:
int numbers[];
В особых случая можно даже так:
public static void main(String... args){
}
Но первый вариант более предпочтителен, так как в нём явно отделяются тип переменной от её имени и его можно использовать в любом месте кода, где можно объявить переменную. Именно этим вариантом я и буду пользоваться в дальнейшем. 
После этого необходимо выделить память под этот массив. Обычно это происходит вот так:
int[] numbers;
numbers = new int[10];
Можно объявлять переменную и инициализировать её в одной строке:
int[] numbers = new int[10];
Если же элементы будущего массива известны заранее, то можно использовать такой синтаксис:
int[] numbers = {23,43,22,34,14,56,86,77,810,9};
Что бы теперь обратиться к элементам этого массива, мы можем использовать индекс элемента от 0 до 9 (в Java элементы массива нумеруются от 0, поэтому верхняя граница на 1 меньше, чем при инициализации):
int[] numbers = new int[10];
int first = numbers[0];
number[4] = 76;
int last = numbers[9];
for(int i = 0; i < 10; i++){
  System.out.println(number[i]);
}
Рис 1. Ссылка на массив.
Рис 1. Ссылка на массив

При этом следует помнить, что в переменной хранится не весь массив, а только ссылка на этот массив. Это можно увидеть наглядо на рисунке 1. Казалось бы, как нам разница, хранится весь массив или только ссылка на него? В реальности же оказывается если не учитывать этого момента, то можно попасть в достаточно неприятную ситуацию. Рассмотрим простой пример:
int[] numbers = {1,23, 13};
int[] numbersCopy = numbers;
numbersCopy[1] = 43;

for(int i = 0; i < 3; i++){
  System.out.print(numbers[i]);
}
System.out.println();

for(int i = 0; i < 3; i++){
  System.out.print(numbersCopy[i]);
}
Рис 2. Копирование ссылки
Рис 2. Копирование ссылки

Если считать, что в переменных хранятся не ссылки а массивы целиком, то результат должен был бы быть вывод двух строк: "12313" и "14313", но в реальности мы получим две одинаковые строки: "14313". Это происходит из-за того, что мы переменной nubmersCopy присваеваем не копию массива, а копию ссылки. И у нас получаются две переменные с одинаковыми значениями, но значения эти не массивы, как мы хотели, а ссылки на массив. Графически это можно изобразить так, как на рисунке 2. Самый простой способ избежать этого, это использовать метод clone у любого объекта типа массив. Так же объект класса массив имеет константу, в которой хранится количество элементов массива — length. Таким образом код можно переписать следующим образом:
int[] numbers = {1,23, 13};
int[] numbersCopy = numbers.clone();
numbersCopy[1] = 43;

for(int i = 0; i < numbers.length; i++){
  System.out.print(numbers[i]);
}
System.out.println();

for(int i = 0; i < numbersCopy.length; i++){
  System.out.print(numbersCopy[i]);
}
Рис 3. Массив массивов
В языке Java не существует многомерных массивов, но тем не менее у нас есть возможность в элементе массива хранить другой массив, что позволяет имитировать многомерные массивы. Благодаря этому мы можем создавать многомерные массивы с произвольной длинной элементов. Это проиллюстрированно на рисунке 3. В коде это выглядит следующим образом:
/*Простой двумерный массив*/
int[][] twoDimensionsArray = new int[3][5];
/*Массив с разными размерностями, как на рис. 3*/
int[][] twoDimenstionsArray2 = {
  {2,4,223},
  {54,215},
  {25,64,26,67}
};
/*Массив с разными размерностями, как на рис. 3*/
int[][] twoDimenstionsArray3 = new int[3][];
differentDimensions[0] = new int[3];
differentDimensions[1] = new int[2];
differentDimensions[2] = new int[4];
При работе с такими массивами полезно знать один трюк: всегда старайтесь располагать массивы с наименьшем числом элементов ближе к правой стороне, например:
  int[][] twoDimensionsArray = new int[3][500][2000][10000];
Дело в том, что для хранения каждого массива JVM хранит дополнительную информацию, и расположив массивы подобным образом вы уменьшите количество необходимой дополнительной информации до минимума.

Класс java.util.Arrays

Класс Arrays предназначен для работы с массивами. Он содержит методы для работы с целыми массивами, например:
  •  copyOf — предназначен для копирования массива;
  • copyOfRange — копирует часть массива;
  • toString — позволяет получить все элементы в виде одной строки;
  • sort — сортирует массив методом quick sort; 
  • binarySearch — ищет элемент методом бинарного поиска; 
  • fill — заполняет массив переданным значением (удобно использовать, если нам необходимо значение по умалчанию для массива); 
  • equals — проверяет на идентичность массивы; 
  • deepEquals — проверяет на идентичность массивы массивов; 
  • asList — возвращает массив как коллекцию (коллекции будут рассмотрены в следующий раз).
Пример использования класса Arrays:
  int[] first = {31,25,3,324,22,33,643,12};
  int[] second;
  
  second = Arrays.copyOf(first, first.length);
  System.out.println(Arrays.equals(first, second));

  Arrays.sort(first);
  System.out.println(Arrays.equals(first, second)); 
  System.out.println(Arrays.toString(first));
  System.out.println(Arrays.toString(second));
  
  Arrays.fill(second, 100);
  System.out.println(Arrays.toString(second));
Если мы запустим этот код, то получим такой вывод:
true
false
[3, 12, 22, 25, 31, 33, 324, 643]
[31, 25, 3, 324, 22, 33, 643, 12]
[100, 100, 100, 100, 100, 100, 100, 100]