Java 并发编程实战 -- 常见概念

本文详细探讨了线程与进程的区别,包括它们的定义、工作原理和特性。同时,分析了并发与并行的概念,指出并发通过上下文切换实现任务交错,而并行则涉及多个处理器同时处理任务。文章还介绍了Java线程的状态转换,并讨论了线程安全,如内置锁和ThreadLocal的使用,强调了线程同步和资源管理的重要性。
摘要由CSDN通过智能技术生成

  • 但凡做事高效的人,总能在串行性和异步性之间找到合理的平衡,对于程序来说同样如此。

一、线程 vs 进程

Processes and threads are related to each other and very much similar, hence create confusion to understand the differences between both of them.

在这里插入图片描述

1.1 Process

  • What is Process ?
  1. A process is an instance of a program that is being executed;
  2. A process can create other processes to perform multiple tasks at a time;
  3. Each process contains its own memory space and does not share it with the other processes.

在这里插入图片描述

  • Process States
  1. NEW: A new process is being created.
  2. READY: A process is ready and waiting to be allocated to a processor.
  3. RUNNING: The program is being executed.
  4. WAITING: Waiting for some event to happen or occur.
  5. TERMINATED: Execution finished.
  • How it works ?
  1. Firstly, the program is loaded into the computer’s memory in binary code after translation.
  2. A program requires OS resources to run it. The resources such that registers, program counter, and a stack, and these resources are provided by the OS.
  3. A register can have an instruction, a storage address, or other data that is required by the process.
  4. The program counter maintains the track of the program sequence.
  5. The stack has information on the active subroutines of a computer program.
  • Features of Process
  1. Each time we create a process, we need to make a separate system call for each process to the OS. The fork() function creates the process.
  2. Each process exists within its own address or memory space.
  3. Each process is independent and treated as an isolated process by the OS.
  4. Processes need IPC (Inter-process Communication) in order to communicate with each other.
  5. A proper synchronization between processes is not required.

1.2 Thread

  • What is Thread?
  1. A thread is the subset of a process and is also known as the lightweight process.
  2. A process can have more than one thread, and these threads are managed independently by the scheduler.
  3. Threads have some common information, such as data segment, code segment, files, etc., that is shared to their peer threads. But contains its own registers, stack, and counter.

在这里插入图片描述

  • How it works ?
  1. When a process starts, OS assigns the memory and resources to it. Each thread within a process shares the memory and resources of that process only.
  2. Threads are mainly used to improve the processing of an application.
  3. If a single thread executes in a process, it is known as a single-threaded And if multiple threads execute simultaneously, then it is known as multi-threading.
  • Features of Thread
  1. Threads share data, memory, resources, files, etc., with their peer threads within a process.
  2. One system call is capable of creating more than one thread.
  3. Each thread has its own stack and register.
  4. Threads can directly communicate with each other as they share the same address space.
  5. Threads need to be synchronized in order to avoid unexpected scenarios.

1.3 Differences

  • Key Differences
  1. A process is independent and does not contained within another process, whereas all threads are logically contained within a process.
  2. Processes are heavily weighted, whereas threads are light-weighted.
  3. A process can exist individually as it contains its own memory and other resources, whereas a thread cannot have its individual existence.
  4. A proper synchronization between processes is not required. In contrast, threads need to be synchronized in order to avoid unexpected scenarios.
  5. Processes can communicate with each other using inter-process communication only; in contrast, threads can directly communicate with each other as they share the same address space.
  • Difference Table
ProcessThread
A process is an instance of a program that is being executed or processed.Thread is a segment of a process or a lightweight process that is managed by the scheduler independently.
Processes are independent of each other and hence don’t share a memory or other resources.Threads are interdependent and share memory.
Each process is treated as a new process by the operating system.The operating system takes all the user-level threads as a single process.
If one process gets blocked by the operating system, then the other process can continue the execution.If any user-level thread gets blocked, all of its peer threads also get blocked because OS takes all of them as a single process.
Context switching between two processes takes much time as they are heavy compared to thread.Context switching between the threads is fast because they are very lightweight.
The data segment and code segment of each process are independent of the other.Threads share data segment and code segment with their peer threads; hence are the same for other threads also.
The operating system takes more time to terminate a process.Threads can be terminated in very little time.
New process creation is more time taking as each new process takes all the resources.A thread needs less time for creation.

二、并发 vs 并行

2.1 Concurrency

  1. Concurrency relates to an application that is processing more than one task at the same time. Concurrency is an approach that is used for decreasing the response time of the system by using the single processing unit.
  2. Concurrency is creates the illusion of parallelism, however actually the chunks of a task aren’t parallelly processed, but inside the application, there are more than one task is being processed at a time. It doesn’t fully end one task before it begins ensuing.
  3. Concurrency is achieved through the interleaving operation of processes on the central processing unit(CPU) or in other words by the context switching. that’s rationale it’s like parallel processing. It increases the amount of work finished at a time.

在这里插入图片描述

  1. In the above figure, we can see that there is multiple tasks making progress at the same time. This figure shows the concurrency because concurrency is the technique that deals with the lot of things at a time.

2.2 Parallelism

  1. Parallelism is related to an application where tasks are divided into smaller sub-tasks that are processed seemingly simultaneously or parallel. It is used to increase the throughput and computational speed of the system by using multiple processors. It enables single sequential CPUs to do lot of things “seemingly” simultaneously.
  2. Parallelism leads to overlapping of central processing units and input-output tasks in one process with the central processing unit and input-output tasks of another process. Whereas in concurrency the speed is increased by overlapping the input-output activities of one process with CPU process of another process.

在这里插入图片描述

  1. In the above figure, we can see that the tasks are divided into smaller sub-tasks that are processing simultaneously or parallel. This figure shows the parallelism, the technique that runs threads simultaneously.

2.3 Differences

ConcurrencyParallelism
Concurrency is the task of running and managing the multiple computations at the same time.While parallelism is the task of running multiple computations simultaneously.
Concurrency is achieved through the interleaving operation of processes on the central processing unit(CPU) or in other words by the context switching.While it is achieved by through multiple central processing units(CPUs).
Concurrency can be done by using a single processing unit.While this can’t be done by using a single processing unit. it needs multiple processing units.
Concurrency increases the amount of work finished at a time.While it improves the throughput and computational speed of the system.
Concurrency deals lot of things simultaneously.While it do lot of things simultaneously.
Concurrency is the non-deterministic control flow approach.While it is deterministic control flow approach.
In concurrency debugging is very hard.While in this debugging is also hard but simple than concurrency.

三、线程的状态及转换

3.1 线程的状态

StateDescription
NEWA thread that has not yet started is in this state.
RUNNABLEA thread executing in the Java virtual machine is in this state.
BLOCKEDA thread that is blocked waiting for a monitor lock is in this state.
WAITINGA thread that is waiting indefinitely for another thread to perform a particular action is in this state.
TIMED_WAITINGA thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
TERMINATEDA thread that has exited is in this state.

3.2 状态间转换

在这里插入图片描述

3.3 相关类的方法

MethodDescription
Object.wait()Causes the current thread to wait until it is awakened, typically by being notified or interrupted, and the lock will be released.
Thread.sleep()Causes the currently executing thread to sleep (temporarily cease execution) and the lock will not be released.
Thread.yield()A hint to the scheduler that the current thread is willing to yield its current use of a processor.
Thread.join()Caller (main) thread will wait for this callee thread to die (run out).
Thread.interrupt()Instead of terminating the thread, it sends an interrupt notification to the thread so that it can decide to responde to the interrupt notification at an appropriate time (the response can be to terminate the thread or not to respond).
Thread.stop()Deprecated
Thread.suspend()Deprecated
Thread.resume()Deprecated

四、内置锁

4.1 锁的谁?

// Fruit fruit = new Fruit();

public class Fruit {
    private final String lock = "I am a lock";

    // locked 'fruit' instance
    public synchronized void sprout() {
        System.out.println("i am sprouting");
    }

    // locked 'fruit' instance
    public void grow() {
        synchronized (this) {
            System.out.println("i am growing");
        }
    }

    // locked 'lock' instance
    public void grew() {
        synchronized (lock) {
            System.out.println("i have been grown");
        }
    }

    // locked 'Fruit.class'
    public void bloom() {
        synchronized (Fruit.class) {
            System.out.println("i am blooming");
        }
    }

    // locked 'Fruit.class'
    public synchronized static void bearFruit() {
        System.out.println("i am bearing fruit");
    }

}

4.2 覆盖 synchronized 方法

  1. Bad way
public class Fruit {

    public synchronized void doSomething() {
        // ...
    }

}

public class Apple extends Fruit {

    @Override
    public void doSomething() {
        // ...
    }

}
  1. Correct way
public class Fruit {

    public synchronized void doSomething() {
        // ...
    }

}

public class Apple extends Fruit {

    @Override
    public synchronized void doSomething() {
        // ...
    }

}
  1. Better way

Better off with delegation rather than subclassing.

public class Fruit {

    public final void doSomething() {
        synchronized (this) {
            codeNeedsToGuardAndImplement();
        }
    }

    protected void codeNeedsToGuardAndImplement() {
        // ...
    }

}

public class Apple extends Fruit {

    @Override
    protected void codeNeedsToGuardAndImplement() {
        // ...
    }

}

五、ThreadLocal

5.1 释义

  1. This class provides thread-local variables.
  2. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable.
  3. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID).

5.2 实例

  • 代码
package com.simwor.simpletest;

import java.util.concurrent.atomic.AtomicInteger;

public class ThreadLocalTest {

    public static void main(String[] args) {
        new ThreadLocalTest().run();
    }

    /**
     * 3. Mock modifying data in parallel
     */
    private void run() {
        Number no = new Number();
        for(int i = 0; i < 3; i++)
            new Thread(new Runner(no)).start();
    }

    /**
     * 2. Mock modifying data
     */
    private class Runner implements Runnable {
        private Number no;

        public Runner(Number no) { this.no = no; }

        @Override
        public void run() {
            for (int i = 0; i < 10000; i++)
                no.increment();
            // print result after invoking
            System.out.println(no);
            no.destroy();
        }

    }

    /**
     * 1. Mock different ways of keeping data
     */
    private class Number {
        private int publicNo;
        private AtomicInteger atomicNo = new AtomicInteger(0);
        private ThreadLocal<Integer> localNo = ThreadLocal.withInitial(()->0);

        public void increment() {
            publicNo++;
            atomicNo.incrementAndGet();
            localNo.set(localNo.get() + 1);
        }

        public void destroy() {
            localNo.remove();
        }

        @Override
        public String toString() {
            return "Number{" +
                    "publicNo=" + publicNo +
                    ", atomicNo=" + atomicNo.get() +
                    ", localNo=" + localNo.get() +
                    '}';
        }
    }
}
  • 运行结果
// 1st round
Number{publicNo=19983, atomicNo=20000, localNo=10000}
Number{publicNo=29983, atomicNo=30000, localNo=10000}
Number{publicNo=11419, atomicNo=11436, localNo=10000}

// 2nd round
Number{publicNo=29994, atomicNo=30000, localNo=10000}
Number{publicNo=19994, atomicNo=20000, localNo=10000}
Number{publicNo=10193, atomicNo=10199, localNo=10000}

// 3rd round
Number{publicNo=17311, atomicNo=17448, localNo=10000}
Number{publicNo=19950, atomicNo=20067, localNo=10000}
Number{publicNo=29883, atomicNo=30000, localNo=10000}
  • 分析
  1. localNo : 每次均能保证是 10000,因为在每个线程保存有一份本地变量;
  2. atomicNo : 仅能保证最终结果为 30000,因为它是线程安全的对象;
  3. publicNo : 完全随机,没有任何线程安全保障。

5.3 源码

  1. Thread 中保存线程级数据的变量;
	/* 
	 * ThreadLocal values pertaining to this thread. This map is maintained
     * by the ThreadLocal class. 
     */
    ThreadLocal.ThreadLocalMap threadLocals = null;
  1. 看一下 ThreadLocal 中 ThreadLocalMap 的构造,简单的可以看成一个数组用来保存变量;
    /**
     * ThreadLocalMap is a customized hash map suitable only for
     * maintaining thread local values. No operations are exported
     * outside of the ThreadLocal class. The class is package private to
     * allow declaration of fields in class Thread.  To help deal with
     * very large and long-lived usages, the hash table entries use
     * WeakReferences for keys. However, since reference queues are not
     * used, stale entries are guaranteed to be removed only when
     * the table starts running out of space.
     */
    static class ThreadLocalMap {
        private ThreadLocal.ThreadLocalMap.Entry[] table;

        /**
         * Construct a new map initially containing (firstKey, firstValue).
         * ThreadLocalMaps are constructed lazily, so we only create
         * one when we have at least one entry to put in it.
         */
        ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {
            table = new ThreadLocal.ThreadLocalMap.Entry[INITIAL_CAPACITY];
            int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
            table[i] = new ThreadLocal.ThreadLocalMap.Entry(firstKey, firstValue);
            size = 1;
            setThreshold(INITIAL_CAPACITY);
        }

    }
  1. ThreadLocal.set() 方法,修改的 map 变量是栈封闭的;
    /**
     * Sets the current thread's copy of this thread-local variable
     * to the specified value.  Most subclasses will have no need to
     * override this method, relying solely on the {@link #initialValue}
     * method to set the values of thread-locals.
     *
     * @param value the value to be stored in the current thread's copy of
     *        this thread-local.
     */
    public void set(T value) {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            map.set(this, value);
        } else {
            createMap(t, value);
        }
    }
  1. ThreadLocal.get() 方法,查看的 map 变量是栈封闭的;
    /**
     * Returns the value in the current thread's copy of this
     * thread-local variable.  If the variable has no value for the
     * current thread, it is first initialized to the value returned
     * by an invocation of the {@link #initialValue} method.
     *
     * @return the current thread's value of this thread-local
     */
    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

5.4 内存泄露问题

static class ThreadLocalMap {
	
	static class Entry extends WeakReference<ThreadLocal<?>> {
		Object value;

		Entry(ThreadLocal<?> k, Object v) {
			super(k);
			value = v;
		}
	}

	...
}
  1. 实例化时会产生一条 private ThreadLocal<Integer> localNo = ThreadLocal.withInitial(()->0) 强引用。
  2. 变量的值 ThreadLocalMap.Entry e = map.getEntry(this) 也是以 ThreadLocal 为 key (this 即是指 ThreadLocal)存储在 Map 中的。
  3. 不过 ThreadLocalMap.Entry 的 key设置的为虚引用,即是防止 ThreadLocal 引存在强引用关系而回收不掉。
  4. 但特别注意的是:ThreadLocal 变量一定要通过 threadLocal.remove() 手动删除,否则会出现 Entry<null, value> 内存泄漏。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值