Avatar billede encorez Juniormester
04. april 2018 - 21:58 Der er 32 kommentarer

Multiple tråde med return

Hej
Jeg vil gerne splitte mit workload ud på flere tråde for at udnyttet CPUen og gøre arbejde hurtigere færdigt.
Jeg havde næste færdiggjort at implementere et eksempel der bruger Runnable, men ser til sidst at den bruge en public void run() til selve workloaded og hvis jeg har forstået rigtig, så kan jeg ikke få den til at returnere en værdi, og det har jeg brug for.
Jeg har så læst at man måske skal bruge Callable istedet, men det forvirrer mig lidt hvordan jeg skal implementere det i min kode.

Så jeg håber hvis jeg lister strukturen i min kode herunder at nogen kan hjælpe med hvordan jeg splitter workloaded ud i tråde og får returneret resultatet.


public class analyse {
public static void main(String[] args){
  while (masse parametre){
   
    String test_kombo = test_kombo.func2(parametre); //Det er denne linie /funktion som skal ud i tråde, og hvor jeg skal have resultatet returneret som String, så jeg kan arbejde videre med det.
}}}

public class test_kombo {
    public static String func2(parametre){
   
    //Arbejdet udføres
    String result;
    return result;
}}
Avatar billede arne_v Ekspert
04. april 2018 - 22:18 #1
Outline:


public class Foobar implements Callable<String> {
    public String call()  {
              // do things
              // return string
        }
}



ExecutorService es = Executors.newFixedThreadPool(POOL_SIZE);
Future<String>[] task = new Future[N];
for(int i = 0; i < task.length; i++) {
      // submit task:
      task[i] = es.submit(new Foobar());
}
for(int i = 0; i < task.length; i++) {
      // this blocks until task is completed:
    String res = task[i].get();
    // use res
}
es.shutdown();
Avatar billede arne_v Ekspert
05. april 2018 - 01:25 #2
Mere komplet eksempel:


import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadFun implements Callable<String> {
    private static final String MSG = "It works!";
    private static final int N = 100;
    @Override
    public String call() throws InterruptedException {
        Thread.sleep(1000);
        return MSG;
    }
    private static void test(int poolsize) throws InterruptedException, ExecutionException {
        long t1 = System.currentTimeMillis();
        ExecutorService es = Executors.newFixedThreadPool(poolsize);
        @SuppressWarnings("unchecked")
        Future<String>[] task = new Future[N];
        for(int i = 0; i < task.length; i++) {
            task[i] = es.submit(new ThreadFun());
        }
        for(int i = 0; i < task.length; i++) {
            String res = task[i].get();
            if(!res.equals(MSG)) throw new RuntimeException("Ooops");
        }
        es.shutdown();       
        long t2 = System.currentTimeMillis();
        System.out.printf("Executing %d tasks in %d threads took %.1f seconds\n", N, poolsize, (t2 - t1) / 1000.0);
    }
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        test(1);
        test(10);
        test(100);
    }
}


Output:

Executing 100 tasks in 1 threads took 100.0 seconds
Executing 100 tasks in 10 threads took 10.0 seconds
Executing 100 tasks in 100 threads took 1.0 seconds
Avatar billede encorez Juniormester
05. april 2018 - 17:24 #3
Det er et super eksempel :)

Og jeg kan egentlig godt følge hvad der sker, men jeg får hovedpine ved at prøve at gennemskue hvordan jeg bruger det i min kode.

Alle de arbejdsopgaver jeg skal have ud i tråde laver jeg jo i min "public static void main", hvor i dit eksempel bliver trådene ikke lavet der, men i en  "private static void test".
Egentlig ville jeg flytte indholdet fra "private static void test(int poolsize)" ned i "public static void main".

Men det forvirrer mig at submit(new ThreadFun()) henviser til classen "public class ThreadFun". Her hedder min jo "public class analyse", og jeg kan ikke se om det virker at jeg omdøber til "submit(new analyse())"??

Og et andet spørgsmål, jeg skal jo have en masse parametre med over til den funktion der skal lave arbejdet i tråden. Det er jo her jeg havde "func2(parametre)".
I dit eksempel gætter jeg på at de parametre skal jeg have med her "new ThreadFun(parametrerne))" og så ved jeg ikke om jeg fanger dem her: "public String call(parametrerne)".
Det er jo i "public String call()" at arbejdet i tråden bliver udført ikke sandt? Altså det arbejde som før blev lavet i min "func2"-funktion. Så her skal jeg jo have de parametre med over, som jo er forskellige fra tråd til tråd.

Beklager forvirringen, jeg forsøger blot at holde tungen lige i munden og indse hvad skal være hvor for at det passer bedst mulig ind :)
Avatar billede arne_v Ekspert
05. april 2018 - 18:44 #4
Andet eksempel:


mport java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThreadFun implements Callable<String> {
    private String sv;
    private int iv;
    public ThreadFun(String sv, int iv) {
        this.sv = sv;
        this.iv = iv;
    }
    @Override
    public String call() throws InterruptedException {
        Thread.sleep(10);
        return sv + iv;
    }
    private static final int POOL_SIZE = 10;
    private static final int N = 100;
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService es = Executors.newFixedThreadPool(POOL_SIZE);
        @SuppressWarnings("unchecked")
        Future<String>[] task = new Future[N];
        for(int i = 0; i < task.length; i++) {
            task[i] = es.submit(new ThreadFun("Test", i));
        }
        for(int i = 0; i < task.length; i++) {
            String res = task[i].get();
            if(!res.equals("Test" + i)) throw new RuntimeException("Ooops");
        }
        es.shutdown();     
    }
}
Avatar billede arne_v Ekspert
05. april 2018 - 18:45 #5
Det viser start kode i main og brug af parametre (via constructor).
Avatar billede encorez Juniormester
11. april 2018 - 09:36 #6
Det virker perfekt :)

Et spørgsmål: Efter loopet med  task[i] = es.submit(new ThreadFun("Test", i)); kan man være sikker på at når den loop er færdig, så bliver koden efterfølgende IKKE eksekveret FØR alle tråde er kørt færdige?

Altså
for(){
task[i] = es.submit(new ThreadFun("Test", i)); kan man
}
System.out.println("Når denne tekst vises, ER alle tråde kørt færdige og resultatet er i task-arrayet");

Jeg spørger fordi hvis ikke man kan være sikker på det, så jeg nødt til at loop arrayet igennem og sikre at der er resultat i alle entries, inden jeg begynder at arbejde videre med det.
Avatar billede arne_v Ekspert
11. april 2018 - 14:08 #7
Nej.

task[i] = es.submit(new ThreadFun("Test", i));

saetter bare arbejde i koe og har en referance til arbejdet.

Med stor sandsynlighed er der stadig arbejde der udfoeres eller maaske endda stadig staar i koe og venter paa at blive udfoert, naar den for loekke er faerdig.

Det er:

String res = task[i].get();

som:
1) venter paa at det paagaeldende arbejde er udfoert
2) henter resultat
Avatar billede arne_v Ekspert
11. april 2018 - 14:10 #8
Den brugte model er lidt primitiv idet den virker bedst hvis de forskellige arbejder tager ca. lige lang tid eller de foerste arbejder er de hurtigste.

Men det er ogsaa en nem model.

Der er mere avancerede men ogsaa vanskelige modeller.
Avatar billede encorez Juniormester
11. april 2018 - 15:32 #9
Men er denne kode "nok" til at sikre at alle tråde er færdig inden jeg fortsætter med at arbejde med resultatet i arrayet?
for(int i = 0; i < task.length; i++) {
            String res = task[i].get();
            if(!res.equals("Test" + i)) throw new RuntimeException("Ooops");
        }

Jeg tænker noget i retning af (syntax er absolut ikke korrekt, men du forstår ideen):
for(int i = 0; i < task.length; i++) {
  if(tas[i] == null) {
      i = 0;
      sleep(2 seconds);
}
//Denne kode eksekveres først når ovenstående loop er "gennemført"
Avatar billede encorez Juniormester
11. april 2018 - 16:28 #10
Lad mig beskrive koden som giver mig problemet og grunden til mit spørgsmål.

Følgende kode virkede fint for mig når POOL_SIZE var 1. Men ved 2 eller mere får jeg en exception og jeg tror det er fordi der går noget galt i SQL resultset referencer(altså rs-referencen), men det er kun et gæt.
Afhængig af hvad jeg prøver får jeg enten en java.lang.NullPointerException eller en SQL exception.
Og jeg ved at du anbefalede at læse data over i et array istedet for resultsættet, men dette var det nemmeste for mig og det virkede i mine tests, så håber det også kan spille med tråde.

for(løber igennem 5 gange, parameter 1-5){
  String query = "udtræk med parameter 1 til 5";
  st = conn.createStatement();
  rs = st.executeQuery(query);
        for(mange andre parametre){
        task[i] = es.submit(new analyse(mange parametre, rs));
        }
  //Her har jeg eksperimenteret med om jeg skal lukke st og rs, før den næste query udtrækkes
  //Og samtidig sikre at alle tråde med "parameter 1" er færdige, før næste query udtrækkes med parameter 2 osv.
  //Dog har jeg her prøvet at lave en sleep på 20 sekunder hvor alle tråde burde være færdige, men stadig før jeg en
}
Avatar billede arne_v Ekspert
11. april 2018 - 16:31 #11
Det er nok.

task[i].get() venter indtil task i er faerdig og returnerer resultatet.

Naar den er kaldt for alle i saa er alle task faerdige.
Avatar billede arne_v Ekspert
11. april 2018 - 17:04 #12
Men bruger du forskellig connection til de forskellige traade?

Det skal du!

Du kan ikke bruge samme connection til at udfoere forskellige queries i forskellige traade samtidigt.

En connection per task.
Avatar billede encorez Juniormester
11. april 2018 - 17:26 #13
Super, jeg arbejder videre med task[i].get() til at vente på at arbejdet bliver færdigt.

Beklager mange gange min langsommelighed, men hvilke connections til de forskellige tråde mener du præcist?
Min "rs" til resultsettet fra min sql query, eller noget andet?
Avatar billede arne_v Ekspert
11. april 2018 - 17:44 #14
st = conn.createStatement();

den her variabel "conn" !
Avatar billede encorez Juniormester
11. april 2018 - 18:23 #15
conn er jo den initielle forbindelse til databsen.
Connection conn = DriverManager.getConnection(myUrl, "root", "password");

Så siger du at når jeg skal lave et nyt udtræk fra databasen så skal jeg lukke conn med conn.close();, og derefter etablere den igen med en ny
Connection conn = DriverManager.getConnection(myUrl, "root", "password");
for at lave det nye udtræk?

Eller hvor er det jeg skal lave en ny conn?

Jeg har allerede eksperimenteret med at lukke både rs og st inden jeg laver det nye udtræk, og det lader til at have virket.
st.close();
rs.close();
Avatar billede arne_v Ekspert
11. april 2018 - 18:58 #16
Mit forslag er:

public String call()  {
      Connection conn = ...;
      ...
      conn.close();
}

eller hvis nyere Java:

public String call()  {
      try(Connection conn = ...) {
            ...
      }
}
Avatar billede encorez Juniormester
11. april 2018 - 19:32 #17
Ok, men min conn og sqp queries foregår i public static void main, ikke i public String call().
Fra public static void main sender jeg en referencen til ResultSet rs med over til tråden.

public String call() kalder så min funktion med "rs"-referencen som parameter, og min function kan derefter arbejde med ResultSet rs.
Men problemet er måske når 2 tråde parallelt sender funktionen i gang med at arbejde på det samme resultSet så går det galt?
Avatar billede arne_v Ekspert
11. april 2018 - 19:51 #18
Problemet er at ResultSet er ikke en data struktur i hukommelsen med resultat af query men er en kontekst med forbindelse til databasen og et antal raekker cached i hukommelsen.

Jeg vil anbefale enten:

connection
statement
resultset
laes fra resultset til en List<X>
luk alt
start tasks (traade) med den List<X>

og task laver:

processing

eller:

start tasks (traade)

og task laver:

connection
statement
resultset
processing
luk alt
Avatar billede arne_v Ekspert
11. april 2018 - 20:01 #19
CachedRowSet goer sikker det som du proever at bruge ResultSet til, men det er en sjaelden brugt mulighed.
Avatar billede encorez Juniormester
11. april 2018 - 20:46 #20
Ang din mulighed nr. 2
og task laver:
connection
statement
resultset
processing

vil det ikke betyde at hver eneste tråd vil lave det samme sql-udtræk?

Bare at lave udtrækket én gang tager måske 10 sekunder eller mere lige nu fordi det er en stor database, så jeg tænker at hvis mange tråde, og parallelt/samtidigt, så vil det lægge en kæmpe load på mysql databasen som jeg bruger.
Med mindre mysql databasen cacher resultatet, så når først den har leveret det én gang, så er det i cachen alle de efterfølgende gange.
Eller hvordan virker det tror du?
Avatar billede arne_v Ekspert
11. april 2018 - 21:01 #21
Jo. Den anden loesning laeser data for hver gang.

Den foerste laeser kun en gang.

Saa List<X> eller CachedRowSet.
Avatar billede encorez Juniormester
12. april 2018 - 12:59 #22
Super, det er nok en List jeg skal i gang med så.

Det der var nemt ved at fortsætte med ResultSet var at kunne kalde attribut navnet fra mysql databasen, og få værdien.
Altså hvis jeg har en tabel over biler, så kan jeg jo lave
while(loop gennem resultset, rs){
    String model = rs.getString(model);
}

Når jeg skal transfomere ResultSet over i en List, kan jeg så lave samme struktur, så jeg kan kalde de samme attribut navne som brugt i tabellen?
Avatar billede arne_v Ekspert
12. april 2018 - 19:45 #23
Normal vil man lave en:

public class X {
    private String model;
    ...
    public String getModel() {
          return model;
    }
    // eventuel setter
    ...
}

while(rs.next()) {
    lst.ad(new X(..., rs.getString("model"), ...));
}

// brug lst.get(i).getModel()
Avatar billede arne_v Ekspert
12. april 2018 - 19:46 #24
Jeg kan godt proeve at lave et komplet eksempel i aften (bemaerk at jeg er 6 timer efter dansk tid).
Avatar billede encorez Juniormester
12. april 2018 - 21:26 #25
Det vil være helt kanon hvis du kan det :)
Avatar billede arne_v Ekspert
13. april 2018 - 02:33 #26

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class DB2List {
    public static class T1 {
        private int f1;
        private String f2;
        public T1(int f1, String f2) {
            this.f1 = f1;
            this.f2 = f2;
        }
        public int getF1() {
            return f1;
        }
        public String getF2() {
            return f2;
        }
    }
    private static List<T1> loadData() throws SQLException {
        List<T1> res = new ArrayList<>();
        try(Connection con = DriverManager.getConnection("jdbc:mysql://localhost/Test", "root", "")) {
            try(Statement stmt = con.createStatement()) {
                try(ResultSet rs = stmt.executeQuery("SELECT f1,f2 FROM t1")) {
                    while(rs.next()) {
                        res.add(new T1(rs.getInt("f1"), rs.getString("f2")));
                    }
                }
            }
        }
        return res;
    }
    public static void m1(List<T1> data) {
        for(T1 o : data) {
            if(o.getF1() < 3) {
                System.out.printf("m1: %d %s\n", o.getF1(), o.getF2());
            }
        }
    }
    public static void m2(List<T1> data) {
        for(T1 o : data) {
            if(o.getF1() >= 3) {
                System.out.printf("m2: %d %s\n", o.getF1(), o.getF2());
            }
        }
    }
    public static void main(String[] args) throws SQLException {
        List<T1> data = loadData();
        m1(data);
        m2(data);
    }
}
Avatar billede encorez Juniormester
13. april 2018 - 10:04 #27
Super, mange tak, den arbejder jeg videre med.

En lille ting ang. at udtrække "model" som attribut.
Som jeg har brug for det så kommer attribut navnet som en variable.

F.eks.
String variabel = "year"
int year = rs.getInt(variabel)

Vil det også virke med objekterne fra dit eksempel?
Avatar billede arne_v Ekspert
13. april 2018 - 19:06 #28
Nej. Det er ikke "the Java way".

Men det kan naturligvis laves.


import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class DB2List {
    public static class T1 {
        private int f1;
        private String f2;
        public T1(int f1, String f2) {
            this.f1 = f1;
            this.f2 = f2;
        }
        public int getF1() {
            return f1;
        }
        public String getF2() {
            return f2;
        }
        public Object get(String fld) {
            switch(fld) {
                case "f1":
                    return getF1();
                case "f2":
                    return getF2();
                default:
                    throw new RuntimeException("Unknown field T1." + fld);
            }
        }
    }
    private static List<T1> loadData() throws SQLException {
        List<T1> res = new ArrayList<>();
        try(Connection con = DriverManager.getConnection("jdbc:mysql://localhost/Test", "root", "")) {
            try(Statement stmt = con.createStatement()) {
                try(ResultSet rs = stmt.executeQuery("SELECT f1,f2 FROM t1")) {
                    while(rs.next()) {
                        res.add(new T1(rs.getInt("f1"), rs.getString("f2")));
                    }
                }
            }
        }
        return res;
    }
    public static void m1(List<T1> data) {
        for(T1 o : data) {
            if(o.getF1() < 3) {
                System.out.printf("m1: %d %s\n", o.get("f1"), o.get("f2"));
            }
        }
    }
    public static void m2(List<T1> data) {
        for(T1 o : data) {
            if(o.getF1() >= 3) {
                System.out.printf("m2: %d %s\n", o.get("f1"), o.get("f2"));
            }
        }
    }
    public static void main(String[] args) throws SQLException {
        List<T1> data = loadData();
        m1(data);
        m2(data);
    }
}
Avatar billede arne_v Ekspert
13. april 2018 - 19:37 #29
Eller:


import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.InvocationTargetException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;

public class DB2ListVar {
    public static class TypeUnsafeBean {
        public Object get(String fld) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {
            return new PropertyDescriptor(fld, this.getClass()).getReadMethod().invoke(this);
        }
    }
    public static class T1 extends TypeUnsafeBean {
        private int f1;
        private String f2;
        public T1(int f1, String f2) {
            this.f1 = f1;
            this.f2 = f2;
        }
        public void setF1(int f1) {
            this.f1 = f1;
        }
        public int getF1() {
            return f1;
        }
        public String getF2() {
            return f2;
        }
        public void setF2(String f2) {
            this.f2 = f2;
        }
    }
    private static List<T1> loadData() throws SQLException {
        List<T1> res = new ArrayList<>();
        try(Connection con = DriverManager.getConnection("jdbc:mysql://localhost/Test", "root", "")) {
            try(Statement stmt = con.createStatement()) {
                try(ResultSet rs = stmt.executeQuery("SELECT f1,f2 FROM t1")) {
                    while(rs.next()) {
                        res.add(new T1(rs.getInt("f1"), rs.getString("f2")));
                    }
                }
            }
        }
        return res;
    }
    public static void m1(List<T1> data) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {
        for(T1 o : data) {
            if(o.getF1() < 3) {
                System.out.printf("m1: %d %s\n", o.get("f1"), o.get("f2"));
            }
        }
    }
    public static void m2(List<T1> data) throws IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {
        for(T1 o : data) {
            if(o.getF1() >= 3) {
                System.out.printf("m2: %d %s\n", o.get("f1"), o.get("f2"));
            }
        }
    }
    public static void main(String[] args) throws SQLException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, IntrospectionException {
        List<T1> data = loadData();
        m1(data);
        m2(data);
    }
}
Avatar billede arne_v Ekspert
13. april 2018 - 19:38 #30
Men jeg ville undgaa det. Det er ikke den maade man arbejder paa i Java.
Avatar billede encorez Juniormester
13. april 2018 - 21:35 #31
Jeg brugte dit første forslag, dog med en int istedet for Object, da den hver gang skal returnere en int.
Den ser ud til at gøre præcist det jeg har brug for, så tusind tak for det :)

public int return_value(String information) {
      switch(information) {
                case "model":
                    return model;
                case "year":
                    return year;
                default:
                    throw new RuntimeException("Unknown field T1." + indicator);
            }
}
Avatar billede arne_v Ekspert
30. maj 2018 - 03:10 #32
En oversigt over forskellige muligheder for database adgang i Java:

http://www.vajhoej.dk/arne/articles/javadb.html
Avatar billede Ny bruger Nybegynder

Din løsning...

Tilladte BB-code-tags: [b]fed[/b] [i]kursiv[/i] [u]understreget[/u] Web- og emailadresser omdannes automatisk til links. Der sættes "nofollow" på alle links.

Loading billede Opret Preview

Log ind eller opret profil

Hov!

For at kunne deltage på Computerworld Eksperten skal du være logget ind.

Det er heldigvis nemt at oprette en bruger: Det tager to minutter og du kan vælge at bruge enten e-mail, Facebook eller Google som login.

Du kan også logge ind via nedenstående tjenester