Threads


Threads create the impression that several things are happening at the same time, although a processor does not really allow this. Instead, it quickly switches between threads to execute them one after the other.



Overall, a process consists of several threads, which are coordinated in time sequence by a thread scheduler. This passes the threads in a specific order to the processor for execution.


Multithreading in Java



Multithreading is the ability of a program or operating system to serve more than one user at a time without having to run multiple copies of the program on the computer.



Java has the great advantage that this function is built directly into the programming language:



Thread = t = new Thread( );


t.start( );



If a thread object is created in this way to start the execution thread, it will directly experience a virtual death and thus the stack will also die. To prevent this from happening, a call to action is needed.



In java it is not enough to create a thread, but there must also be a task for the thread first.



The time and duration of the execution of a thread is determined by the scheduler. A thread can therefore assume different states. The change of these states is called the thread life cycle.




At the beginning, it is in the "new" state if it has been created but not yet started. As soon as the thread is started, it changes to the "runnable" state, ready for execution. If the thread is selected by the scheduler and executed on the processor, it is in the "running" state. If the thread is waiting for a resource or condition, it can change to the "non-runnable" or "blocked" state. Finally, a thread is considered finished when all its instructions have been processed. The states "new", "runnable", "running", "non-runnable" and "terminated" describe the life cycle of a thread and make it possible to process tasks in parallel and efficiently in programmes.


Programm Threads


A selection of commands from the lecture to program with threads:




Bringing a thread to execution



The runnable interface is used to assign tasks to the threads. The threads are constantly assigned tasks, which are instructed by Runnable.



To implement this, the thread is first created, whereby the runnable is passed as a parameter. The thread is then started.



Runnable threadJob = new MyRunnable();


Thread meinThread = new Thread(threadJob);


myThread.start();



by myThread.start(); the thread becomes runnable and waits for use cases. A call stack is created for the thread.



Through the various states, which are described in "Multithreading in java", the thread can be set and used in the states.



It is important to note that if a thread is executed the first time, it dies after the execution is finished. it cannot be revived with start().


Thread scheduler


Ein Thread-Scheduler kann entscheiden, welcher Thread zur Ausführung ausgewählt wird, wenn mehrere Threads im Status "runnable" sind. Es gibt keine Garantie für die Reihenfolge, in der Threads ausgeführt werden, da der Scheduler verschiedene Faktoren berücksichtigt, wie die Priorität und die Wartezeit der Threads. Dadurch können unterschiedliche Ergebnisse bei der Ausführung auftreten. Entwickler können die Thread-Prioritäten festlegen, um das Verhalten des Schedulers zu beeinflussen.



The different results can look as shown in the following examples:




Put threads to sleep


There is the possibility to put threads into an inactive state. In colloquial language, this is also called putting a thread to sleep. This is achieved by denying execution access. The time of the inactive state is measured in milliseconds, an example looks like this:



Thread.sleep(500);



To prevent the method from triggering the InterruptedExeption, it must be placed in a try/catch block.



This .sleep() method is often used for the following reasons:




Concurrency problems


Since threads work independently of each other and only know their own actions, they have no information about other threads.



This can lead to data corruption and unexpected results when accessing the same heap object. Also, the modification of the data submitted to the thread as a task is not reliable, as the data may change during processing or may be modified by other threads accessing the same data set. The thread itself cannot judge this.



The following example from the lecture demonstrates that threads can interfere with each other:




To avoid such problems, the keyword "synchronised" can be used. This keyword can be included directly in the method. It ensures that only one thread can access a synchronised method. This is done at the object level and synchronises the methods that access the object. Synchronisation allows critical sections of code to be completed by one thread before another can access it, to avoid data inconsistencies.



Example:



public synchronised void methodName(int param) {


...}



The problem occurs when changes to objects are not atomic, but happen in stages.



In code, the cause of the problem is that more than one thread needs to access a local variable. As mentioned before, the thread that is being executed cannot check to what extent the data is reset before the method is completed. In this case, "synchronised" also provides the solution.


Thread Deadlock




When using threads, another problem can occur called a deadlock. A deadlock occurs when synchronised methods block each other. This can happen when two threads each hold a key that the other thread needs, and both wait for the desired key to be released without releasing their own key. Unlike databases, Java has no built-in handling of deadlocks such as transaction rollbacks. In such cases, the affected methods simply wait endlessly. Moreover, Java often does not even automatically recognise that a deadlock has occurred. Therefore, it is the responsibility of the developers to take appropriate measures to avoid or fix deadlocks.


The creator/consumer problem


Synchronisation can solve the lost update problem, but not the dependency problem in concurrent access to objects. The dependency problem occurs when threads exchange data and can lead to inconsistent or incomplete data. The producer/consumer problem is a special case of the dependency problem. One possible solution is thread communication, which allows threads to coordinate, wait for each other or notify each other.



Further information and a list of other solutions can be found under this link:



https://www.herr-rau.de/wordpress/2015/12/threads-iii-erzeuger-verbraucher-und-ein-neuer-zustand.htm