Главная страница
Навигация по странице:

  • Thread.currentThread().isInterrupted()

  • InterruptedException

  • Object

  • многопоточность джава. многопоточность. Класс Thread в java функциональность отдельного потока заключается в классе Thread


    Скачать 340.19 Kb.
    НазваниеКласс Thread в java функциональность отдельного потока заключается в классе Thread
    Анкормногопоточность джава
    Дата17.12.2021
    Размер340.19 Kb.
    Формат файлаdocx
    Имя файламногопоточность.docx
    ТипДокументы
    #306823
    страница4 из 6
    1   2   3   4   5   6

    Завершение потока


    Распространенный способ завершения потока представляет опрос логической переменной. И если она равна, например, false, то поток завершает бесконечный цикл и заканчивает свое выполнение.

    Определим следующий класс потока:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    class MyThread implements Runnable {

          

        private boolean isActive;

          

        void disable(){

            isActive=false;

        }

          

        MyThread(){

           isActive = true;

        }

          

        public void run(){

              

            System.out.printf("%s started... \n", Thread.currentThread().getName());

            int counter=1; // счетчик циклов

            while(isActive){

                System.out.println("Loop " + counter++);

                try{

                    Thread.sleep(400);

                }

                catch(InterruptedException e){

                    System.out.println("Thread has been interrupted");

                }

            }

            System.out.printf("%s finished... \n", Thread.currentThread().getName());

        }

    }

    Переменная isActive указывает на активность потока. С помощью метода disable() мы можем сбросить состояние этой переменной.

    Теперь используем этот класс:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    public static void main(String[] args) {

             

        System.out.println("Main thread started...");

        MyThread myThread = new MyThread();

        new Thread(myThread,"MyThread").start();

             

        try{

            Thread.sleep(1100);

                 

            myThread.disable();

             

            Thread.sleep(1000);

        }

        catch(InterruptedException e){

            System.out.println("Thread has been interrupted");

        }

        System.out.println("Main thread finished...");

    }

    Итак, вначале запускается дочерний поток: new Thread(myThread,"MyThread").start(). Затем на 1100 миллисекунд останавливаем Main thread и потом вызываем метод myThread.disable(), который переключает в потоке флаг isActive. И дочерний поток завершается.

    Метод interrupt


    Еще один способ вызова завершения или прерывания потока представляет метод interrupt(). Вызов этого метода устанавливает у потока статус, что он прерван. Сам метод возвращает true, если поток может быть прерван, в ином случае возвращается false.

    При этом сам вызов этого метода НЕ завершает поток, он только устанавливает статус: в частности, метод isInterrupted() класса Thread будет возвращать значение true. Мы можем проверить значение возвращаемое данным методом и прозвести некоторые действия. Например:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    class JThread extends Thread {

          

        JThread(String name){

            super(name);

        }

        public void run(){

              

            System.out.printf("%s started... \n", Thread.currentThread().getName());

            int counter=1; // счетчик циклов

            while(!isInterrupted()){

                 

                System.out.println("Loop " + counter++);

            }

            System.out.printf("%s finished... \n", Thread.currentThread().getName());

        }

    }

    public class Program {

      

        public static void main(String[] args) {

              

            System.out.println("Main thread started...");

            JThread t = new JThread("JThread");

            t.start();

            try{

                Thread.sleep(150);

                t.interrupt();

                  

                Thread.sleep(150);

            }

            catch(InterruptedException e){

                System.out.println("Thread has been interrupted");

            }

            System.out.println("Main thread finished...");

        }

    }

    В классе, который унаследован от Thread, мы можем получить статус текущего потока с помощью метода isInterrupted(). И пока этот метод возвращает false, мы можем выполнять цикл. А после того, как будет вызван метод interrupt, isInterrupted() возвратит true, и соответственно произойдет выход из цикла.

    Возможный консольный вывод:

    Main thread started...

    JThread started...

    Loop 1

    Loop 2

    Loop 3

    Loop 4

    JThread finished...

    Main thread finished...

    Если основная функциональность заключена в классе, который реализует интерфейс Runnable, то там можно проверять статус потока с помощью метода Thread.currentThread().isInterrupted():

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    class MyThread implements Runnable {

          

        public void run(){

              

            System.out.printf("%s started... \n", Thread.currentThread().getName());

            int counter=1; // счетчик циклов

            while(!Thread.currentThread().isInterrupted()){

                 

                System.out.println("Loop " + counter++);

            }

            System.out.printf("%s finished... \n", Thread.currentThread().getName());

        }

    }

    public class Program {

      

        public static void main(String[] args) {

              

            System.out.println("Main thread started...");

            MyThread myThread = new MyThread();

            Thread t = new Thread(myThread,"MyThread");

            t.start();

            try{

                Thread.sleep(150);

                t.interrupt();

                  

                Thread.sleep(150);

            }

            catch(InterruptedException e){

                System.out.println("Thread has been interrupted");

            }

            System.out.println("Main thread finished...");

        }

    }

    Однако при получении статуса потока с помощью метода isInterrupted() следует учитывать, что если мы обрабатываем в цикле исключение InterruptedException в блоке catch, то при перехвате исключения статус потока автоматически сбрасывается, и после этого isInterrupted будет возвращать false.

    Например, добавим в цикл потока задержку с помощью метода sleep:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    public void run(){

              

        System.out.printf("%s started... \n", Thread.currentThread().getName());

        int counter=1; // счетчик циклов

        while(!isInterrupted()){

             

            System.out.println("Loop " + counter++);

            try{

                Thread.sleep(100);

            }

            catch(InterruptedException e){

                System.out.println(getName() + " has been interrupted");

                System.out.println(isInterrupted());    // false

                interrupt();    // повторно сбрасываем состояние

            }

        }

        System.out.printf("%s finished... \n", Thread.currentThread().getName());

    }

    Когда поток вызовет метод interrupt, метод sleep сгенерирует исключение InterruptedException, и управление перейдет к блоку catch. Но если мы проверим статус потока, то увидим, что метод isInterrupted возвращает false. Как вариант, в этом случае мы можем повторно прервать текущий поток, опять же вызвав метод interrupt(). Тогда при новой итерации цикла while метода isInterrupted возвратит true, и поизойдет выход из цикла.

    Либо мы можем сразу же в блоке catch выйти из цикла с помощью break:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    while(!isInterrupted()){

             

        System.out.println("Loop " + counter++);

        try{

            Thread.sleep(100);

        }

        catch(InterruptedException e){

            System.out.println(getName() + " has been interrupted");

                 

            break;  // выход из цикла

        }

    }

    Если бесконечный цикл помещен в конструкцию try...catch, то достаточно обработать InterruptedException:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    public void run(){

              

        System.out.printf("%s started... \n", Thread.currentThread().getName());

        int counter=1; // счетчик циклов

        try{

            while(!isInterrupted()){

                System.out.println("Loop " + counter++);

                Thread.sleep(100);

            }

        }

        catch(InterruptedException e){

            System.out.println(getName() + " has been interrupted");

        }

             

        System.out.printf("%s finished... \n", Thread.currentThread().getName());

    }

    При работе потоки нередко обращаются к каким-то общим ресурсам, которые определены вне потока, например, обращение к какому-то файлу. Если одновременно несколько потоков обратятся к общему ресурсу, то результаты выполнения программы могут быть неожиданными и даже непредсказуемыми. Например, определим следующий код:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    public class Program {

     

        public static void main(String[] args) {

             

            CommonResource commonResource= new CommonResource();

            for (int i = 1; i < 6; i++){

                 

                Thread t = new Thread(new CountThread(commonResource));

                t.setName("Thread "+ i);

                t.start();

            }

        }

    }

     

    class CommonResource{

         

        int x=0;

    }

     

    class CountThread implements Runnable{

     

        CommonResource res;

        CountThread(CommonResource res){

            this.res=res;

        }

        public void run(){

            res.x=1;

            for (int i = 1; i < 5; i++){

                System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x);

                res.x++;

                try{

                    Thread.sleep(100);

                }

                catch(InterruptedException e){}

            }

        }

    }

    Здесь определен класс CommonResource, который представляет общий ресурс и в котором определено одно целочисленное поле x.

    Этот ресурс используется классом потока CountThread. Этот класс просто увеличивает в цикле значение x на единицу. Причем при входе в поток значение x=1:

    1

    res.x=1;

    То есть в итоге мы ожидаем, что после выполнения цикла res.x будет равно 4.

    В главном классе программы запускается пять потоков. То есть мы ожидаем, что каждый поток будет увеличивать res.x с 1 до 4 и так пять раз. Но если мы посмотрим на результат работы программы, то он будет иным:

    Thread 1 1

    Thread 2 1

    Thread 3 1

    Thread 5 1

    Thread 4 1

    Thread 5 6

    Thread 2 6

    Thread 1 6

    Thread 3 6

    Thread 4 6

    Thread 4 11

    Thread 2 11

    Thread 5 11

    Thread 3 11

    Thread 1 11

    Thread 4 16

    Thread 1 16

    Thread 3 16

    Thread 5 16

    Thread 2 16

    То есть пока один поток не окончил работу с полем res.x, с ним начинает работать другой поток.

    Чтобы избежать подобной ситуации, надо синхронизировать потоки. Одним из способов синхронизации является использование ключевого слова synchronized. Этот оператор предваряет блок кода или метод, который подлежит синхронизации. Для его применения изменим класс CountThread:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    class CountThread implements Runnable{

     

        CommonResource res;

        CountThread(CommonResource res){

            this.res=res;

        }

        public void run(){

            synchronized(res){

                res.x=1;

                for (int i = 1; i < 5; i++){

                    System.out.printf("%s %d \n", Thread.currentThread().getName(), res.x);

                    res.x++;

                    try{

                        Thread.sleep(100);

                    }

                    catch(InterruptedException e){}

                }

            }

        }

    }

    При создании синхронизированного блока кода после оператора synchronized идет объект-заглушка: synchronized(res). Причем в качестве объекта может использоваться только объект какого-нибудь класса, но не примитивного типа.

    Каждый объект в Java имеет ассоциированный с ним монитор. Монитор представляет своего рода инструмент для управления доступа к объекту. Когда выполнение кода доходит до оператора synchronized, монитор объекта res блокируется, и на время его блокировки монопольный доступ к блоку кода имеет только один поток, который и произвел блокировку. После окончания работы блока кода, монитор объекта res освобождается и становится доступным для других потоков.

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

    В итоге консольный вывод изменится:

    Thread 1 1

    Thread 1 2

    Thread 1 3

    Thread 1 4

    Thread 3 1

    Thread 3 2

    Thread 3 3

    Thread 3 4

    Thread 5 1

    Thread 5 2

    Thread 5 3

    Thread 5 4

    Thread 4 1

    Thread 4 2

    Thread 4 3

    Thread 4 4

    Thread 2 1

    Thread 2 2

    Thread 2 3

    Thread 2 4

    При применении оператора synchronized к методу пока этот метод не завершит выполнение, монопольный доступ имеет только один поток - первый, который начал его выполнение. Для применения synchronized к методу, изменим классы программы:

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    public class Program {

     

        public static void main(String[] args) {

             

            CommonResource commonResource= new CommonResource();

            for (int i = 1; i < 6; i++){

                 

                Thread t = new Thread(new CountThread(commonResource));

                t.setName("Thread "+ i);

                t.start();

            }

        }

    }

     

    class CommonResource{

         

        int x;

        synchronized void increment(){

            x=1;

            for (int i = 1; i < 5; i++){

                System.out.printf("%s %d \n", Thread.currentThread().getName(), x);

                x++;

                try{

                    Thread.sleep(100);

                }

                catch(InterruptedException e){}

            }

        }

    }

     

    class CountThread implements Runnable{

     

        CommonResource res;

        CountThread(CommonResource res){

            this.res=res;

        }

         

        public void run(){

            res.increment();

        }

    }

    Результат работы в данном случае будет аналогичен примеру выше с блоком synchronized. Здесь опять в дело вступает монитор объекта CommonResource - общего объекта для всех потоков. Поэтому синхронизированным объявляется не метод run() в классе CountThread, а метод increment класса CommonResource. Когда первый поток начинает выполнение метода increment, он захватывает монитор объекта CommonResource. А все потоки также продолжают ожидать его освобождения.

    Иногда при взаимодействии потоков встает вопрос о извещении одних потоков о действиях других. Например, действия одного потока зависят от результата действий другого потока, и надо как-то известить один поток, что второй поток произвел некую работу. И для подобных ситуаций у класса Object определено ряд методов:

    • wait(): освобождает монитор и переводит вызывающий поток в состояние ожидания до тех пор, пока другой поток не вызовет метод notify()

    • notify(): продолжает работу потока, у которого ранее был вызван метод wait()

    • notifyAll(): возобновляет работу всех потоков, у которых ранее был вызван метод wait()

    Все эти методы вызываются только из синхронизированного контекста - синхронизированного блока или метода.

    Рассмотрим, как мы можем использовать эти методы. Возьмем стандартную задачу из прошлой темы - "Производитель-Потребитель" ("Producer-Consumer"): пока производитель не произвел продукт, потребитель не может его купить. Пусть производитель должен произвести 5 товаров, соответственно потребитель должен их все купить. Но при этом одновременно на складе может находиться не более 3 товаров. Для решения этой задачи задействуем методы wait() и notify():

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    26

    27

    28

    29

    30

    31

    32

    33

    34

    35

    36

    37

    38

    39

    40

    41

    42

    43

    44

    45

    46

    47

    48

    49

    50

    51

    52

    53

    54

    55

    56

    57

    58

    59

    60

    61

    62

    63

    64

    65

    66

    67

    public class Program {

      

        public static void main(String[] args) {

              

            Store store=new Store();

            Producer producer = new Producer(store);

            Consumer consumer = new Consumer(store);

            new Thread(producer).start();

            new Thread(consumer).start();

        }

    }

    // Класс Магазин, хранящий произведенные товары

    class Store{

       private int product=0;

       public synchronized void get() {

          while (product<1) {

             try {

                wait();

             }

             catch (InterruptedException e) {

             }

          }

          product--;

          System.out.println("Покупатель купил 1 товар");

          System.out.println("Товаров на складе: " + product);

          notify();

       }

       public synchronized void put() {

           while (product>=3) {

             try {

                wait();

             }

             catch (InterruptedException e) {

             }

          }

          product++;

          System.out.println("Производитель добавил 1 товар");

          System.out.println("Товаров на складе: " + product);

          notify();

       }

    }

    // класс Производитель

    class Producer implements Runnable{

      

        Store store;

        Producer(Store store){

           this.store=store;

        }

        public void run(){

            for (int i = 1; i < 6; i++) {

                store.put();

            }

        }

    }

    // Класс Потребитель

    class Consumer implements Runnable{

          

         Store store;

        Consumer(Store store){

           this.store=store;

        }

        public void run(){

            for (int i = 1; i < 6; i++) {

                store.get();

            }

        }

    }

    Итак, здесь определен класс магазина, потребителя и покупателя. Производитель в методе run() добавляет в объект Store с помощью его метода put() 6 товаров. Потребитель в методе run() в цикле обращается к методу get объекта Store для получения этих товаров. Оба метода Store - put и get являются синхронизированными.

    Для отслеживания наличия товаров в классе Store проверяем значение переменной product. По умолчанию товара нет, поэтому переменная равна 0. Метод get() - получение товара должен срабатывать только при наличии хотя бы одного товара. Поэтому в методе get проверяем, отсутствует ли товар:

    1

    while (product<1)

    Если товар отсутсвует, вызывается метод wait(). Этот метод освобождает монитор объекта Store и блокирует выполнение метода get, пока для этого же монитора не будет вызван метод notify().

    Когда в методе put() добавляется товар и вызывается notify(), то метод get() получает монитор и выходит из конструкции while (product<1), так как товар добавлен. Затем имитируется получение покупателем товара. Для этого выводится сообщение, и уменьшается значение product: product--. И в конце вызов метода notify() дает сигнал методу put() продолжить работу.

    В методе put() работает похожая логика, только теперь метод put() должен срабатывать, если в магазине не более трех товаров. Поэтому в цикле проверяется наличие товара, и если товар уже есть, то освобождаем монитор с помощью wait() и ждем вызова notify() в методе get().

    И теперь программа покажет нам другие результаты:

    Производитель добавил 1 товар

    Товаров на складе: 1

    Производитель добавил 1 товар

    Товаров на складе: 2

    Производитель добавил 1 товар

    Товаров на складе: 3

    Покупатель купил 1 товар

    Товаров на складе: 2

    Покупатель купил 1 товар

    Товаров на складе: 1

    Покупатель купил 1 товар

    Товаров на складе: 0

    Производитель добавил 1 товар

    Товаров на складе: 1

    Производитель добавил 1 товар

    Товаров на складе: 2

    Покупатель купил 1 товар

    Товаров на складе: 1

    Покупатель купил 1 товар

    Товаров на складе: 0

    Таким образом, с помощью wait() в методе get() мы ожидаем, когда производитель добавит новый продукт. А после добавления вызываем notify(), как бы говоря, что на складе освободилось одно место, и можно еще добавлять.

    А в методе put() с помощью wait() мы ожидаем освобождения места на складе. После того, как место освободится, добавляем товар и через notify() уведомляем покупателя о том, что он может забирать товар.

    Здравствуйте! В этой статье я вкратце расскажу вам о процессах, потоках, и об основах многопоточного программирования на языке Java.
    Наиболее очевидная область применения многопоточности – это программирование интерфейсов. Многопоточность незаменима тогда, когда необходимо, чтобы графический интерфейс продолжал отзываться на действия пользователя во время выполнения некоторой обработки информации. Например, поток, отвечающий за интерфейс, может ждать завершения другого потока, загружающего файл из интернета, и в это время выводить некоторую анимацию или обновлять прогресс-бар. Кроме того он может остановить поток загружающий файл, если была нажата кнопка «отмена».

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

    Давайте начнем. Сначала о процессах.

    1   2   3   4   5   6


    написать администратору сайта