У меня есть класс, в котором я определяю список с помощью метода добавления и удаления, как здесь:
public class listClass{
private List<T> someList = new ArrayList<>();
public void add(){
//adds to list
}
public void delete(){
//deletes from list
}
}
У меня есть еще один класс, в котором я определяю два потока и объект, из которого я могу получить доступ к своему списку. Один поток должен использовать метод добавления для непрерывного добавления в список, а другой поток должен использовать метод удаления для непрерывного удаления из списка в соответствии с номерами потоков:
public class threadClass extends Thread{
private int threadNumber;
public ThreadClass(int threadNumber){
this.threadNumber = threadNumber;
}
listClass listClassObject = new listClass();
public void run(){
if (threadNumber == 1){
while(true){/*add*/}
}
else if (threadNumber ==2){
while(true){/*delete*/}
}
}
}
Как мне сделать так, чтобы оба потока манипулировали одним и тем же списком?
Моя текущая реализация имеет другой класс с основным методом, где я вызываю потоки, как здесь:
public static void main(String[] args){
threadClass threadForAddingMediaFiles = new threadClass(1);
threadClass threadForDeletionMediaFiles = new threadClass(2);
threadForAddingMediaFiles.start();
threadForDeletionMediaFiles.start();
Проблема с текущей реализацией заключается в том, что каждый поток создает свой собственный экземпляр списка (что мне ясно, потому что список находится в одном и том же классе), и поэтому каждый поток манипулирует своим собственным списком, а не одним универсальным списком.
Что значит иметь один экземпляр ListClass
? Как мне это сделать? @Слав
В ThreadClass
у вас есть ListClass listClassObject = new ListClass()
. Это создает экземпляр ListClass
одновременно с созданием экземпляра ThreadClass
. Но вы создаете экземпляры дваThreadClass
(в методе main
), что означает, что в конечном итоге этот код приводит к созданию экземпляров дваListClass
. Не делай этого. Создайте один экземпляр ListClass
и передайте его вызовам конструктора ThreadClass
(вам нужно будет добавить параметр). См. первый и второй кодовые блоки в ответ Паоло.
В современной Java мы редко обращаемся к классу Thread
напрямую. Вместо этого используйте инфраструктуру Executors, добавленную в Java 5. Это было рассмотрено во многих существующих вопросах и ответах, поэтому выполните поиск, чтобы узнать больше.
Определите свои задачи как Runnable
, а не Thread
.
Что касается совместного использования вашего менеджера списков между потоками, создайте один экземпляр. Убедитесь, что ваш менеджер списков потокобезопасен. Ваша текущая реализация не является потокобезопасной. Одним из решений было бы пометить методы добавления и удаления как synchronized
.
Передайте один экземпляр вашего объекта диспетчера списков конструктору ваших Runnable
объектов задач. Каждый Runnable
хранит ссылку на этот менеджер списков как на личное поле участника. Затем все ваши исполняемые файлы используют один и тот же менеджер списков.
Отправьте все свои Runnable
объекты в ExecutorService
.
Опять же, все это уже много раз освещалось. Выполните поиск, чтобы узнать больше.
Я думаю, это происходит потому, что вы создаете экземпляр нового listClass внутри каждого объекта threadClass, поэтому, когда вы изменяете список, вы фактически изменяете в каждом потоке свою собственную «локальную» копию. Попробуйте создать экземпляр списка в своей основной функции, а затем передать его своим потокам, чтобы у вас был один экземпляр вашего класса списка, управляемый обоими вашими потоками.
public static void main(String[] args){
listClass myList = new listClass();
threadClass threadForAddingMediaFiles = new threadClass(1, myList);
threadClass threadForDeletionMediaFiles = new threadClass(2, myList);
threadForAddingMediaFiles.start();
threadForDeletionMediaFiles.start();
Затем измените реализацию потока:
public class threadClass extends Thread{
private int threadNumber;
private listClass listToHandle;
public ThreadClass(int threadNumber, listClass listToHandle){
this.threadNumber = threadNumber;
this.listToHandle = listToHandle;
}
public void run(){
if (threadNumber == 1){
while(true){/*add*/}
}
else if (threadNumber ==2){
while(true){/*delete*/}
}
}
}
Наконец, не забудьте поместить оба метода добавления и удаления в свой класс списка как синхронизированные, иначе у вас возникнут проблемы с одновременным доступом.
public class listClass{
private List<T> someList = new ArrayList<>();
public synchronized void add(){
//adds to list
}
public synchronized void delete(){
//deletes from list
}
}
Вы также можете взглянуть на КопиОнВритеАррайлист, который является потокобезопасной реализацией ArrayList. Однако, как сказано в документации, обычно это дорогостоящая реализация, которая может оказаться неподходящим решением для вашей проблемы.
Что означает потокобезопасность?
Будучи потокобезопасным, это свойство гарантирует, что ваш объект ведет себя правильно в случае многократного выполнения несколькими потоками. Если объект не является потокобезопасным и несколько потоков обращаются к общему ресурсу, это может выявить ошибочное поведение или привести к непредсказуемым результатам.
@rhymes Это сложная тема. Но по существу, если вы явно не напишете код иначе, спецификация Java не гарантирует, что изменения, сделанные одним потоком, будут «увидены» другим потоком. Чтобы гарантировать, что такие изменения находятся будут видны другим потокам, вам необходимо создать связь бывает-прежде. Это описано более подробно в пакетная документацияjava.util.concurrent
. Чтобы быть "потокобезопасным", необходимо иметь упорядоченные отношения бывает-прежде.
Вы создаете эти отношения с помощью механизмов, таких как synchronized
, volatile
, классы в java.util.concurrent.locks
и так далее. Некоторые классы, такие как CopyOnWriteArrayList
, написаны потокобезопасным способом, поэтому вам не нужно много делать со своей стороны (если только вы не выполняете более сложные операции, такие как «проверить-затем-действовать»).
Вы можете использовать Collections.synchronizedList(), тогда вам не нужно добавлять синхронизацию в метод добавления/удаления. Создайте экземпляр ListClass, а затем передайте его обоим конструкторам двух потоков.
class listClass
{
private List<T> someList = Collections.synchronizedList(new ArrayList<>());
}
Во-первых, создайте один экземпляр
ListClass
и передайте его каждому экземпляруThreadClass
(кстати, обратите внимание, что я используюCamelCase
, как принято для имен классов в Java). Затем убедитесь, что вы синхронизируете объект (это может бытьsomeList
или экземпляр самогоListClass
), чтобы были созданы необходимые отношения бывает-прежде.