Многопоточность Основы параллельного программирования

Java база данных Операционная система

Авторские права принадлежат автору Для любой формы перепечатки, пожалуйста, свяжитесь с автором для получения разрешения и укажите источник.

Разница между потоком и процессом

Каждая программа, работающая в системе, является процессом. Каждый процесс содержит один или несколько потоков. Поток — это набор инструкций или специальный сегмент программы, который может выполняться независимо внутри программы. Его также можно понимать как контекст, в котором выполняется код. Таким образом, потоки — это в основном легкие процессы, которые отвечают за выполнение нескольких задач в рамках одной программы. Обычно операционная система отвечает за планирование и выполнение нескольких потоков.

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

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

Суммировать:Процесс — это совокупность всех потоков, и каждый поток — это путь выполнения в процессе.

Сценарии многопоточных приложений?

Ответ: В основном это отражает многопоточность для повышения эффективности программы.
Например: многопоточная загрузка Thunder, пул соединений с базой данных, пакетная отправка SMS и т. д.

Как создать несколько потоков

Первый наследует класс Thread и переопределяет метод запуска.

//1. 继承thread类,重写run方法,run方法中,需要线程执行代码
class ThreadDemo01 extends Thread {

	// run方法中,需要线程需要执行代码
	@Override
	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.print("子id:" + this.getId() + ",");
			System.out.print("name:" + getName());
			System.out.println("..i:" + i);
			System.out.println();
		}
	}

}

// 1.什么是线程 线程是一条执行路径,每个线程都互不影响。
// 2.什么是多线程,多线程在一个进程中,有多条不同的执行路径,并行执行。目的为了提高程序效率。
// 3.在一个进程中,一定会主线程。
// 4.如果连线程主线程都没有,怎么执行程序。
// 线程几种分类 用户线程、守护线程
// 主线程 子线程 GC线程
public class Test001 {

	// 交替執行
	public static void main(String[] args) {
		System.out.println("main... 主线程开始...");
		// 1.创建线程
		ThreadDemo01 threadDemo01 = new ThreadDemo01();
		// 2.启动线程
		threadDemo01.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main..i:" + i);
		}
		System.out.println("main... 主线程结束...");
	}

}

Второй реализует интерфейс Runnable и переписывает метод запуска.

class ThreadDemo02 implements Runnable {

	public void run() {
		for (int i = 0; i < 10; i++) {
			System.out.println(" 子 i:" + i);
		}
	}
}

// 1.实现runable接口,重写run方法
public class Thread002 {
	public static void main(String[] args) {
		System.out.println("main... 主线程开始...");
	
		// 创建线程
		ThreadDemo02 threadDemo02 = new ThreadDemo02();
		Thread t1= new Thread(threadDemo02);
		// 启动线程
		t1.start();
		for (int i = 0; i <10; i++) {
			System.out.println("main..i:"+i);
		}
		System.out.println("main... 主线程结束...");
	}

}

Третий способ использования анонимных внутренних классов


public class Thread003 {

	public static void main(String[] args) {
		System.out.println("main... 主线程开始...");

		Thread t1 = new Thread(new Runnable() {

			public void run() {
				for (int i = 0; i < 10; i++) {
					System.out.println(" 子 i:" + i);
				}
			}
		});
		t1.start();
		for (int i = 0; i < 10; i++) {
			System.out.println("main..i:" + i);
		}
		System.out.println("main... 主线程结束...");
	}
}

Лучше использовать класс, который наследуется от Thread или реализует интерфейс Runnable?

Для реализации интерфейса Runnable лучше использовать реализацию, так как интерфейс может продолжать наследоваться, а унаследованный класс не может быть унаследован.

Начальная нить Используемый метод запуска вызова или метод RUN?

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

Получить объект потока и его имя

Общие методы API потока
start() начать нить
currentThread() Получить текущий объект потока
getID() Получить идентификатор текущего потока. Номер потока, который начинается с 0.
getName() Получить текущее имя потока
sleep(long mill) спящая нить
Останавливаться() остановить нить,
Конструктор общего потока
Нить() Выделить новый объект Thread
Тема (имя строки) Выделяет новый объект Thread с указанным именем, как следует из его имени.
Поток (Runable r) Выделить новый объект Thread
Thread(Runable r, имя строки) Выделить новый объект Thread

Нить демона

В Java есть два типа потоков: один — пользовательский, а другой — поток демона.
Пользовательский поток относится к потоку, созданному пользователем.Основной поток останавливается, а пользовательский поток не останавливается.
Поток демона Когда процесс не существует или основной поток останавливается, поток демона также останавливается.
Используйте метод setDaemon(true) для установки в качестве потока демона

public class Test005 {

	public static void main(String[] args) throws InterruptedException {
		Thread t1 = new Thread(new Runnable() {

			public void run() {
				while (true) {
					try {
						Thread.sleep(1000);

					} catch (Exception e) {
						// TODO: handle exception
					}
					System.out.println("我是子线程(用户线程)");
				}
			}
		});
		// 标识当前方法为守护线程
		t1.setDaemon(true);
		t1.start();
		for (int i = 0; i < 10; i++) {
			Thread.sleep(300);
			System.out.println("main:i:" + i);
		}
		System.out.println("主线程执行完毕...");

	}

}

Многопоточное рабочее состояние

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

новое состояние

Когда поток создается с помощью оператора new, такого как new Thread(r), поток еще не запущен и находится в новом состоянии. Когда поток находится в зарождающемся состоянии, программа еще не начала выполнение кода в потоке.

состояние готовности

Вновь созданный поток не запускается автоматически.Для выполнения потока необходимо вызвать метод start() потока. Когда объект потока вызывает метод start(), поток запускается.Метод start() создает системные ресурсы для запуска потока и планирует поток для запуска метода run(). Когда метод start() возвращается, поток находится в состоянии готовности.

Поток в состоянии готовности не обязательно немедленно запускает метод run(), поток также должен конкурировать с другими потоками за процессорное время, а поток может быть запущен только в том случае, если процессорное время получено. Потому что в однопроцессорной компьютерной системе невозможно одновременное выполнение нескольких потоков, и одновременно выполняется только один поток. Таким образом, в этот момент может быть несколько потоков в состоянии готовности. Потоки, находящиеся в состоянии готовности, планируются планировщиком потоков системы среды выполнения Java.

Рабочий статус

Когда поток получает процессорное время, он переходит в состояние выполнения и фактически начинает выполнение метода run().

состояние блокировки

В процессе работы поток может переходить в состояние блокировки по разным причинам:

  1. Поток входит в состояние сна, вызывая метод сна;
  2. Поток вызывает операцию, которая заблокирована при вводе-выводе, т. е. операция не возвращается к вызывающей стороне до тех пор, пока операция ввода-вывода не будет завершена;
  3. Поток пытается получить блокировку, удерживаемую другим потоком;
  4. Поток ожидает условия триггера;

состояние смерти

Потоки могут умереть по двум причинам:

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

Чтобы определить, активен ли поток в данный момент (т. е. готов ли он к выполнению или заблокирован), необходимо использовать метод isAlive. Этот метод возвращает true, если он может выполняться или заблокирован; если поток все еще новый и не может выполняться, или если поток умирает, он возвращает false.

функция метода join()

Когда метод t1.join() выполняется в основном потоке, считается, что основной поток должен отдать право на выполнение t1.
Пример: создайте поток, основной поток может выполняться только после выполнения дочернего потока.

//join
public class Test006 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println("子线程,i:" + i);
                }
            }
        });
        t1.start();
        // 当在主线程当中执行到t1.join()方法时,就认为主线程应该把执行权让给t1
        t1.join();
        for (int i = 0; i < 10; i++) {
            System.out.println("main线程,i:" + i);
        }
    }
}

приоритет

Современные операционные системы в основном планируют текущие потоки в виде временного подразделения. Количество срезов времени, выделенные поток, определяет, сколько процессорных ресурсов использует нить, а также соответствует концепции приоритета потока. В потоке Java приоритет контролируется приоритетом INT, начиная от 1 до 10, из которых 10 - самое высокое, а значение по умолчанию составляет 5. Ниже приведены некоторые количества и методы приоритета в исходном коде (на основе 1.8).

class PrioritytThread implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}
public class Test008 {
    public static void main(String[] args) {
        PrioritytThread prioritytThread = new PrioritytThread();
        Thread t1 = new Thread(prioritytThread);
        Thread t2 = new Thread(prioritytThread);
        t1.start();
        // 注意设置了优先级, 不代表每次都一定会被执行。 只是CPU调度会有限分配
        t2.start();
        t1.setPriority(10);
    }
}

Метод доходности

Роль метода Thread.yield(): приостановить выполнение текущего потока и выполнить другие потоки. (может не работать) yield() возвращает текущий запущенный поток в работоспособное состояние, чтобы другие потоки с таким же приоритетом могли выполняться. Следовательно, цель использования yield() состоит в том, чтобы обеспечить правильную ротацию между потоками с одинаковым приоритетом. Однако на практике нет гарантии, что yield() будет уступать, потому что уступающие потоки могут быть повторно выбраны планировщиком потоков.
В заключение:В большинстве случаев yield() переводит поток из состояния выполнения в состояние готовности к выполнению, но это может не иметь никакого эффекта.

пример

Теперь... Когда есть три потока T1, T2 и T3, как вы гарантируете, что T2 выполняется после выполнения T1, а T3 выполняется после выполнения T2.

class T1 implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

class T2 implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

class T3 implements Runnable {
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().toString() + "---i:" + i);
        }
    }
}

public class JoinThreadDemo02 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new T1(), "T1");
        Thread t2 = new Thread(new T2(), "T2");
        Thread t3 = new Thread(new T3(), "T3");
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        t3.start();
        t3.join();
    }
}

git статья весь код

вопросы интервью

  1. В чем разница между процессом и потоком?
    Ответ: Процесс — это совокупность всех потоков, каждый поток — это путь выполнения в процессе, а поток — это просто путь выполнения.
  2. Зачем использовать многопоточность?
    Ответ: повысить эффективность программы
  3. Как создать несколько потоков?
    Ответ: Наследовать интерфейс Thread или Runnable.
  4. Что лучше: наследовать класс Thread или реализовать интерфейс Runnable?
    Ответ: Интерфейс Runnable хорош тем, что он может продолжать наследоваться после реализации интерфейса. Наследование класса Thread больше не может быть унаследовано.
  5. Где вы используете многопоточность?
    Ответ: В основном это отражает многопоточность для повышения эффективности программы.
    Например: отправка SMS пакетами, многопоточная загрузка Thunder и т. д.