java并发编程知识体系详解篇一(并发编程理论基础篇一)

并发与并行

在这里插入图片描述

何为并发?

并发,在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行,但任一个时刻点上只有一个程序在处理机上运行。
在这里插入图片描述

特点

1、程序与计算不再一一对应,一个程序副本可以有多个计算。
2、并发程序之间有相互制约关系,直接制约体现为一个程序需要另一个程序的计算结果,间接制约体现为多个程序竞争某一资源,如处理机、缓冲等。
3、并发程序在执行中是走走停停,断续推进的。

程序并发的过程,当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。

何为并行?

在操作系统中,若干个程序段同时在系统中运行,这些程序的执行在时间上是重叠的,一个程序段的执行尚未结束,另一个程序段的执行已经开始,无论从微观还是宏观,程序都是一起执行的。是顺序执行)。对比地,并发是指:在同一个时间段内,两个或多个程序执行,有时间上的重叠(宏观上是同时,微观上仍是顺序执行)。
在这里插入图片描述
当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,

进程与线程

1、线程是进程中的一个实体,线程本身是不会独立存在的 。
2、进程是代码在数据集合上的一次运行活动 , 是系统进行资源分配和调度的基本单位 , 线程则是进程的一个执行路径, 一个进程中至少有一个线程,进程中的多个线程共享进程的资源。
3、系统运行一个程序即是一个进程从创建,运行到消亡的过程。
4、在 Java 中,当我们启动 main 函数时其实就启动了一个 jvm 的进程, 而main 函数所在的线程是这个进程中的一个线程,也称主线程 。

实例代码:

import java.lang.management.ManagementFactory;
import java.lang.management.ThreadInfo;
import java.lang.management.ThreadMXBean;

/**
 * @author: 随风飘的云
 * @describe:
 * @date 2022/03/22 15:20
 */
public class MultithThreadTest {
    
    
    public static void main(String[] args) {
    
    
        ThreadMXBean bean = ManagementFactory.getThreadMXBean();
        ThreadInfo[] infos = bean.dumpAllThreads(false, false);
        for (ThreadInfo threadInfo: infos){
    
    
            System.out.println("[" + threadInfo.getThreadId() + "]" + threadInfo.getThreadName());
        }
    }
}

结果(不同的电脑可能有不同的输出):
在这里插入图片描述

线程的几种创建方式

在这里插入图片描述

1、继承Thread类,作为线程对象存在(继承Thread对象)

	/**
 * @author: 随风飘的云
 * @describe:
 * @date 2022/03/25 20:50
 */
public class CreateThreadTest01 extends Thread{
    
    
    public CreateThreadTest01(String CreateName){
    
    
        super(CreateName);
    }
    @Override
    public void run(){
    
    
        while (!interrupted()){
    
    
            System.out.println(getName()+"线程执行了......");
            try{
    
    
                Thread.sleep(2000);
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
    
    
        CreateThreadTest01 test01 = new CreateThreadTest01("Name1");
        CreateThreadTest01 test02 = new CreateThreadTest01("Name2");
        test01.start();
        test02.start();
        //test01.interrupt();//中断线程1
    }
}

结果:可以看出线程1和线程2不间断地交替打印。
在这里插入图片描述
interrupted方法,可以用来判断该线程是否被中断。(终止线程不允许用stop方法,该方法不会施放占用的资源)。当使用了中断线程时,结果如下:可以看出,线程1已经不能运行了,只能打印线程2.
在这里插入图片描述

匿名内部类创建线程对象

/**
 * @author: 随风飘的云
 * @describe:
 * @date 2022/03/25 20:59
 */
public class CreateThreadTest03 extends Thread{
    
    
    public static void main(String[] args) {
    
    
        new Thread(){
    
    
            @Override
            public void run(){
    
    
                System.out.println("无参数线程创建成功!");
            }
        }.start();
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("带线程任务的线程对象执行了");
            }
        }).start();
        // 创建带线程任务并且重写的run方法的线程对象
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("runnable run 线程执行了。。。。");
            }
        }){
    
    
            @Override
            public void run(){
    
    
                System.out.println("Override run 执行了");
            }
        }.start();
        // 调用的重写的方法应该是Thread类的run方法。而不是Runnable接口的run方法。
    }
}

结果:
在这里插入图片描述

2、实现runnable接口,作为线程任务存在

Runnable 接口只是来修饰线程所执行的任务,它不是一个线程对象。想要启动Runnable对象,必须将它放到一个线程对象里。

实例代码:

import static java.lang.Thread.interrupted;

/**
 * @author: 随风飘的云
 * @describe:
 * @date 2022/03/25 20:55
 */
public class CreateThreadTest02 implements Runnable{
    
    
    @Override
    public void run() {
    
    
        while (!interrupted()){
    
    
            System.out.println("线程执行了......");
            try{
    
    
                Thread.sleep(2000);
            }catch (InterruptedException e){
    
    
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
    
    
        // 将线程任务传递给线程对象
        Thread thread = new Thread(new CreateThreadTest02());
        //启动线程
        thread.start();
    }
}

结果:
在这里插入图片描述

3、利用接口Callable创建带返回值的线程

实例代码:

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;

/**
 * @author: 随风飘的云
 * @describe:
 * @date 2022/03/25 21:01
 */

public class CreateThreadTest04 implements Callable {
    
    
    public static void main(String[] args) throws ExecutionException, InterruptedException {
    
    
        CreateThreadTest04 teat04 = new CreateThreadTest04();
        // 最终实现的是Runnable的接口
        FutureTask<String> task = new FutureTask<String>(teat04);
        Thread thread = new Thread(task);
        thread.start();
        System.out.println("哈哈哈哈哈");
        String str = task.get();
        System.out.println("在线程的结果是:"+str);
    }
    @Override
    public Object call() throws Exception {
    
    
        Thread.sleep(2000);
        return "ABCD";
    }
}

结果:
在这里插入图片描述
Callable的接口的代码如下:

@FunctionalInterface
public interface Callable<V> {
    
    
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

定时器Timer

import java.util.Timer;
import java.util.TimerTask;

/**
 * @author: 随风飘的云
 * @describe:
 * @date 2022/03/25 21:04
 */
public class CreateThreadTest05 {
    
    
    public static void main(String[] args) {
    
    
        Timer timer = new Timer();
        timer.schedule(new TimerTask() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("定时器的线程执行了......");
            }
        },0,1000);
    }
}

结果:
在这里插入图片描述

4、线程池创建线程

线程池顾名思义就是事先创建若干个可执行的线程放入一个池(容器)中,需要的时候从池中获取线程不用自行创建,使用完毕不需要销毁线程而是放回池中,从而减少创建和销毁线程对象的开销。

使用newSingleThreadExecutor创建线程
创建一个单线程的线程池。这个线程池只有一个线程在工作,也就是相当于单线程串行执行所有任务。如果这个唯一的线程因为异常结束,那么会有一个新的线程来替代它。此线程池保证所有任务的执行顺序按照任务的提交顺序执行。

/**
 * @description 线程池创建:newSingleThreadExecutor()单一线程池
 *                        跟newFixedThreadPool()原理一样,只是把线程数和最大线程数设置为1
 */
public class SingleThreadExecutor {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newSingleThreadExecutor();
        for (int i = 0; i < 10; i++) {
    
    
            executorService.execute(new FixedThreadPoolExecutor.Task());
        }
    }
}

使用newFixedThreadPool创建线程
创建固定大小的线程池。每次提交一个任务就创建一个线程,直到线程达到线程池的最大大小。线程池的大小一旦达到最大值就会保持不变,如果某个线程因为执行异常而结束,那么线程池会补充一个新线程。

/**
 * @description 线程池创建:newFixedThreadPool()自定义线程数线程池
 *              由于传进去的LinkedBlockingQueue无界队列是没有队列上限的,所以当请求越来越多,并且无法及时处理完毕的时候,
 *              也就是请求堆积的时候,会容易造成占用大量的内存,可能会导致OOM。
 */
public class FixedThreadPoolExecutor {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newFixedThreadPool(10);
        for (int i = 0; i < 10; i++) {
    
    
            executorService.execute(new Task());
        }
        // 手动关闭线程
        executorService.shutdown();
    }
    static class Task implements Runnable{
    
    
        @Override
        public void run() {
    
    
            System.out.println(Thread.currentThread().getName());
        }
    }
}

使用newCachedThreadPool创建线程
创建一个可缓存的线程池。如果线程池的大小超过了处理任务所需要的线程,那么就会回收部分空闲(60秒不执行任务)的线程,当任务数增加时,此线程池又可以智能的添加新线程来处理任务。此线程池不会对线程池大小做限制,线程池大小完全依赖于操作系统(或者说JVM)能够创建的最大线程大小。

/**
 * @description 线程池创建:newCachedThreadPool()可缓存线程池
 *                        特点:SynchronousQueue无界线程池,具有自动回收多余线程的功能
 *                        缺点:maxPoolSize是Integer.MAX_VALUE,可能会创建数量非常多线程,甚至导致OOM。
 */
public class CachedThreadPoolExecutor {
    
    
    public static void main(String[] args) {
    
    
        ExecutorService executorService = Executors.newCachedThreadPool();
        for (int i = 0; i < 10; i++) {
    
    
            executorService.execute(new FixedThreadPoolExecutor.Task());
        }
    }
}

newScheduledThreadPool创建线程
创建一个大小无限的线程池。此线程池支持定时以及周期性执行任务的需求。

/**
 * @description 线程池创建:newScheduledThreadPool() 支持定时及周期性任务执行的线程池
 */
public class ScheduledThreadPoolExecutor {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(10);
        //scheduledExecutorService.schedule(new FixedThreadPoolExecutor.Task(), 1, TimeUnit.SECONDS);
        // 每隔一秒一执行
        scheduledExecutorService.scheduleAtFixedRate(new FixedThreadPoolExecutor.Task(), 1 , 1, TimeUnit.SECONDS);
        Thread.sleep(5000);
        scheduledExecutorService.shutdown();
    }
}

使用newSingleThreadExecutor创建线程
创建一个单线程的线程池。此线程池支持定时以及周期性执行任务的需求。

import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CreateThreadTest06 {
    
    
    public static void main(String[] args) {
    
    
        // 创建具有5个线程的线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(5);
        long threadPoolStart= System.currentTimeMillis();
        for (int i = 0; i < 5; i++) {
    
    
            threadPool.execute(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    System.out.println(Thread.currentThread().getName()+"线程执行了......");
                }
            });
        }
        long threadPoolEnd = System.currentTimeMillis();
        System.out.println("创建5个线程用时:"+(threadPoolEnd-threadPoolStart));
        // 销毁线程池
        threadPool.shutdown();
    }
}

停止线程池

shutdown
isShutdown:是否停止的状态,开始运行就是true
isTerminated:线程是否完全停止
awaitTerminated:在一定时间内线程是否停止 返回boolean
stutdownNow:立即停止,返回被中断的列表。

线程池的状态

RUNNING:接受新任务并处理排第任务。
SHUTDOWN:不接受新的任务,但处理排队任务。
STOP:不接受新任务,也不处理排队任务,并中断正在进行的任务。
TIDYING:所有任务都已终止,workCont为零时,线程会转换到TIDTYING状态,并将运行terminate()的钩子方法。
TERMINATED:terminate()运行完成。

ThreadLocal

从名字可以看出这叫线程变量,意思是ThreadLocal中填充的的变量输入当前线程,该变量对其他线程而言是隔离的,ThreadLocal为变量在每个线程中都创建了一个副本,那么每个线程可以访问自己内部的副本变量。
作用: 让某个需要用到的对象在线程间隔离(每个线程都有自己独立的对象)在任何方法中都可以轻松获取到该对象。
使用场景:

1、在进行对象跨层传递的时候,使用ThreadLocal可以避免多次传递,打破层次间的约束。
2、线程间数据隔离
3、进行事务操作,用于存储线程事务信息。
4、数据库连接,Session会话管理。

在这里插入图片描述

场景举例1:每个线程需要一个独享的对象(通常是工具类如:SimpleDateFormat和Random)

/**
 * @description threadLocal用法1:SimpleDateFormat
 *              利用ThreadLocal,给每个线程分配自己的SimpleDateFormat对象,保证线程安全,高效率利用内存
 *
 *              使用场景:
 *              1.在ThreadLocal第一次get的时候把对象给初始化,重写initialValue()方法和返回值, 对象初始化时间可以由我们控制
 *              2.如果需要保存到ThreadLocal里的对象的生成时机不由我们随意控制,用ThreadLocal.set直接放到我们ThreadLocal中,以便后续使用
 *
 *              ThreadLocal好处:
 *              1.达到线程安全
 *              2.不需要加锁,提高执行效率
 *              3.更高效的利用内存、节省创建对象的开销
 *              4.免去传参的繁琐
 */
public class ThreadLocal01 {
    
    
    private static ExecutorService executorService = Executors.newFixedThreadPool(10);
    public static void main(String[] args) {
    
    
        for (int i = 0; i < 1000; i++) {
    
    
            int index = i;
            executorService.submit(new Runnable() {
    
    
                @Override
                public void run() {
    
    
                    String result = new ThreadLocal01().data(index);
                    System.out.println(result);
                }
            });
        }
        executorService.shutdown();
    }
    public String data(int index){
    
    
        Date date = new Date(1000 * index);
        SimpleDateFormat simpleDateFormat = ThreadSafeSimpleDateFormat.threadLocal2.get();
        return simpleDateFormat.format(date);
    }
}
class ThreadSafeSimpleDateFormat {
    
    
    public static ThreadLocal<SimpleDateFormat> threadLocal = new ThreadLocal<SimpleDateFormat>(){
    
    
        @Override
        protected SimpleDateFormat initialValue() {
    
    
            return new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
        }
    };
    public static ThreadLocal<SimpleDateFormat> threadLocal2 = ThreadLocal.withInitial(()-> new SimpleDateFormat("yyyy-MM-dd hh:mm:ss"));
}

场景举例2:每个线程内需要保持全局变量(如:拦截器中获取用户信息)可以让不同方法直接使用,避免参数传递的麻烦

/**
 * @description threadLocal用法2:每个线程内需要保持全局变量,可以让不同方法直接使用,避免参数传递的麻烦
 *                               防止内存泄露,用完里ThreadLocal里面值,要进行remove
 */
public class ThreadLocal02 {
    
    
    public static void main(String[] args) {
    
    
        Server1 server1 = new Server1();
        server1.process();
    }
}
class Server1{
    
    
    public void process(){
    
    
        User user = new User("张三");
        UserContextHolder.userThreadLocal.set(user);
        Server2 server2 = new Server2();
        server2.process();
    }
}
class Server2{
    
    
    public void process(){
    
    
        User user = UserContextHolder.userThreadLocal.get();
        System.out.println("Server2 获取用户名字:" + user.getName());
        UserContextHolder.userThreadLocal.remove();
        User newUser = new User("李四");
        UserContextHolder.userThreadLocal.set(newUser);
        Server3 server3 = new Server3();
        server3.process();
    }
}
class Server3{
    
    
    public void process(){
    
    
        User user = UserContextHolder.userThreadLocal.get();
        System.out.println("Server3 获取新用户名字:" + user.getName());
        // 用完后一定要remove
        UserContextHolder.userThreadLocal.remove();
    }
}
class User{
    
    
    private String name;
    public String getName() {
    
    
        return name;
    }
    public User(String name) {
    
    
        this.name = name;
    }
}
class UserContextHolder{
    
    
    public static ThreadLocal<User> userThreadLocal = new ThreadLocal<>();
}

线程的优先级

现代操作系统基本采用时分的形式调度运行的线程,操作系统会分出一个个时间片,线程会分配到若干时间片,当线程的时间片用完了就会发生线程调度,并等待着下次分配。线程分配到的时间片多少也就决定了线程使用处理器资源的多少,而线程优先级就是决定线程需要多或者少分配一些处理器资源的线程属性。
Java线程中,通过一个整型成员变量priority来控制优先级,优先级的范围从1~10,在线程构建的时候可以通过setPriority(int)方法来修改优先级,默认优先级是5,优先级高的线程分配时间片的数量要多于优先级低的线程。设置线程优先级时,针对频繁阻塞(休眠或者I/O操作)的线程需要设置较高优先级,而偏重计算(需要较多CPU时间或者偏运算)的线程则设置较低的优先级,确保处理器不会被独占。

public class Priority {
    
    
	private static volatile boolean notStart = true;
	private static volatile boolean notEnd = true;
public static void main(String[] args) throws Exception {
    
    
	List<Job> jobs = new ArrayList<Job>();
	for (int i = 0; i < 10; i++) {
    
    
		int priority = i < 5 Thread.MIN_PRIORITY : Thread.MAX_PRIORITY;
		Job job = new Job(priority);
		jobs.add(job);
		Thread thread = new Thread(job, "Thread:" + i);
		thread.setPriority(priority);
		thread.start();
	}
	notStart = false;
	TimeUnit.SECONDS.sleep(10);
	notEnd = false;
	for (Job job : jobs) {
    
    
		System.out.println("Job Priority : " + job.priority + ",
			Count : " + job.jobCount);
	}
}
static class Job implements Runnable {
    
    
	private int priority;
	private long jobCount;
	public Job(int priority) {
    
    
		this.priority = priority;
	}
	public void run() {
    
    
		while (notStart) {
    
    
			Thread.yield();
		}
		while (notEnd) {
    
    
			Thread.yield();
			jobCount++;
		}
	}
}
}

线程的基本状态

Java线程运行的生命周期中只可能是6种不同的状态之一,在给定的一个时刻,线程只能处于其中的一个状态。

1、New状态:初始状态,线程被构建出来,但是还没有调用start()方法
2、 Runnable状态:运行状态,java线程把操作系统的就绪和运行两种状态统称为“运行中”。
3、 Blocked状态:阻塞状态,表示线程阻塞与锁。
4、 Waiting状态:等待状态,表示线程进入等待状态,进入该状态表示线程需要等待其他线程做出一些特定的动作或通知。
5、TIME WAITING状态:超时等待状态,该状态不同于WAITING,它是可以在指定的时间自行返回的
6、 TIME WAITING状态:终止状态,表示当前线程已经执行完毕

Java线程在自身的生命周期中,并不是固定地处于某个状态,而是随着代码的执行在不同的状态之间进行切换,Java线程状态变迁如下图所示:
在这里插入图片描述

线程创建之后,调用start()方法开始运行。当线程执行wait()方法之后,线程进入等待状态。进入等待状态的线程需要依靠其他线程的通知才能够返回到运行状态,而超时等待状态相当于在等待状态的基础上增加了超时限制,也就是超时时间到达时将会返回到运行状态。当线程调用同步方法时,在没有获取到锁的情况下,线程将会进入到阻塞状态。线程在执行Runnable的run()方法之后将会进入到终止状态。

实例测试:

public class ThreadState {
    
    
    public static void main(String[] args) {
    
    
        new Thread(new TimeWaiting(),"TimeWaitingThread").start();
        new Thread(new Waiting(),"WaitingThread").start();

        new Thread(new Blocked(),"Blocked-1").start();
        new Thread(new Blocked(),"Blocked-2").start();
    }
    // 这个线程不断地休眠
    static class TimeWaiting implements Runnable{
    
    
        @Override
        public void run() {
    
    
            while (true){
    
    
                SleepUtils.second(2000);
            }
        }
    }
    // 这个线程在Waiting.Class上等待
    static class Waiting implements Runnable{
    
    
        @Override
        public void run() {
    
    
            while (true){
    
    
                synchronized (Waiting.class){
    
    
                    try {
    
    
                        Waiting.class.wait();
                    }catch (InterruptedException e){
    
    
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    // 该线程在Blocked.class实例上加锁后,不会释放该锁
    static class Blocked implements Runnable{
    
    
        @Override
        public void run() {
    
    
            synchronized (Blocked.class){
    
    
                while (true){
    
    
                    SleepUtils.second(1000);
                }
            }
        }
    }
}

代码2(放在同一个文件夹下运行)

import java.util.concurrent.TimeUnit;

public class SleepUtils {
    
    
    public static final void second(long second){
    
    
        try {
    
    
            TimeUnit.SECONDS.sleep(second);
        } catch (InterruptedException e){
    
    
            e.printStackTrace();
        }
    }
}

运行代码,打开终端或者命令提示符,键入“jps”,输出如下:
在这里插入图片描述
然后就可以看到运行实例对应的进程ID是177428,然后在命令行终端输入jstack 17748,部分终端输出如下:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

停止运行的线程

线程在运行的过程中退出有如下几种方法:

1、使用退出标志,使得线程正常退出,就是说当run方法执行完毕后线程终止。
2、使用stop方法强制退出,但是不推荐这个方法
3、使用interrupt方法中断线程
4、程序运行结束,线程自动结束。

public class ThreadTest extends Thread{
    
    
    volatile boolean stop = false;
    public void run(){
    
    
        while (!stop){
    
    
            System.out.println(getName()+"线程正在运行中......");
            try{
    
    
                sleep(1000);
            }catch (InterruptedException e){
    
    
                System.out.println("wake up from block ......");
                stop = true;
                e.printStackTrace();
            }
        }
        System.out.println(getName()+"线程已经退出了......");
    }
}
class InterruptThreadDemo {
    
    
    public static void main(String[] args) throws InterruptedException {
    
    
        ThreadTest threadTest = new ThreadTest();
        System.out.println("线程开始运行......");
        threadTest.start();
        Thread.sleep(3000);
        System.out.println("出现Interrupt的线程为:"+threadTest.getName());
        threadTest.stop = true;
        threadTest.interrupt();
        Thread.sleep(3000);
        System.out.println("主线程已经停止了......");
    }
}

运行结果为:
在这里插入图片描述

线程安全

什么是线程安全?当多线程运行了同一代码的时候,如果产生了不同的结果会怎么样?就好比如家里养的鸡下的蛋结果却孵出来一个老鹰,这怎么也显得不合适了,所以线程安全说白了就一句话,当多线程运行同一代码,不会产生不一样的结果。即代码所在的进程中有多个线程在同时运行,而这些线程可能会同时运行这段代码。如果每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。

在多线程环境中,当各线程不共享数据的时候,即都是私有(private)成员,那么一定是线程安全的。因为共享数据没有,那线程之间就互不影响。但这种情况并不多见,在多数情况下需要共享数据,这时就需要进行适当的同步控制了。线程安全一般都涉及到synchronized, 就是一段代码同时只能有一个线程来操作 不然中间过程可能会产生不可预制的结果。

举一个简单的例子,本来你在银行存有1000块钱,你要取1500块钱,你去银行办理两个业务,取钱和存钱,假如你的两个业务是同步进行的,这个时候是不是你的钱就取不出来了?因为你的银行了就只有1000块钱,你取不了1500块钱,但是你的钱却是可以存进去。钱取不了,是不是你去银行办理业务的这个进程就废掉了?因为没有完全运行成功嘛。

确保线程安全

通过合理的时间调度,避开共享资源的存取冲突。另外,在并行任务设计上可以通过适当的策略,保证任务与任务之间不存在共享资源,设计一个规则来保证一个客户的计算工作和数据访问只会被一个线程或一台工作机完成,而不是把一个客户的计算工作分配给多个线程去完成。
确保多线程安全的方法:

1、对非安全的代码进行加锁控制
2、使用线程安全的类
3、多线程并发情况下,线程共享的变量改为方法级的局部变量

线程安全在三个方面体现:

原子性: 提供互斥访问,同一时刻只能有一个线程对数据进行操作(atomic, synchronized);
可见性: 一个线程对主内存的修改可以及时地被其他线程看到,(synchronized、 volatile);
有序性: 一个线程观察其他线程中的指令执行顺序,由于指令重排序,该观察结果一般杂乱⽆无序,(happensbefore 原则)

什么是共享数据?

第一种:将共享数据封装到一个对象中,把这个共享数据所在的对象传递给不同的Runnable。

第二种:将这些Runnable对象作为某一个类的内部类,共享的数据作为外部类的成员变量,对共享数据的操作分配给外部类的方法来完成,以此实现对操作共享数据的互斥和通信,作为内部类的Runnable来操作外部类的方法,实现对数据的操作。

class shareData {
    
    
    private int x = 0;
    public synchronized void addX(){
    
    
        x++;
        System.out.println("X++:"+x);
    }
    public synchronized void subX(){
    
    
        x--;
        System.out.println("X--:"+x);
    }
}
public class ThreadsVisitData{
    
    
    public static shareData share = new shareData();
    public static void main(String[] args) {
    
    
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 5; i++) {
    
    
                    share.addX();
                }
            }
        }).start();
        new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                for (int i = 0; i < 5; i++) {
    
    
                    share.subX();
                }
            }
        }).start();
    }
}

在这里插入图片描述

面试题总结

Runnable接口和Callable接口的区别

首先查看一下Runnable接口的源代码,反正Runnable接口中只有一个方法,Runnable接口中的run()方法的返回值是void,它做的事情只是纯粹地去执行run()方法中的代码而已;
在这里插入图片描述
其次再查看一下Callable接口中的源码,Callable接口中的call()方法是有返回值的,是一个泛型,和Future、FutureTask配合可以用来获取异步执行的结果。
在这里插入图片描述
这其实是很有意义的,首先多线程的运行和单线程运行相比有很多复杂困难的问题,因为多线程运行充满了未知性,比如说某条线程是否运行,运行了多少时间,线程所期望的数据是否已经赋值完毕,Callable接口的泛型的Future/FutureTask却可以获取多线程运行的结果,可以在等待时间太长没获取到需要的数据的情况下取消该线程的任务。这是一个优势。

start()方法和run()方法的区别

每个线程都是通过某个特定 Thread 对象所对应的方法 run() 来完成其操作的,方法 run() 称为线程体。通过调用 Thread 类的 start() 方法来启动⼀一个线程;

start() 方法来启动一个线程,真正实现了多线程运行。这时无需等待 run() 方法体代码执行完毕,可以直接继续执行下面的代码;这时此线程是处于就绪状态,并没有运行。然后通过此 Thread 类调用方法 run() 来完成其运行状态;

run()方法称为线程体,它包含了要执行的这个线程的内容,线程就进入了运行状态,开始运行 run 函数当中的代码。 Run 方法运行结束, 此线程终止。然后 CPU 再调度其它线程。run() 方法是在本线程里的,只是线程里的一个函数,而不是多线程的。如果直接调用 run(),其实就相当于是调用了一个普通函数而已,直接用 run() 方法必须等待 run() 方法执行完毕才能执行下面的代码,所以执行路径还是只有一条,根本就没有线程的特征,所以在多线程执行时要使用 start() 方法而不是 run() 方法。

sleep()方法和wait()方法的区别

首先sleep()方法可以在任何地方使用,而wait()方法只能在同步方法或者同步块中运行。对于 sleep()方法,我们首先要知道该方法是属于 Thread 类中的。而 wait()方法,则是属于Object 类中的。sleep()方法导致了程序暂停执行指定的时间,让出 cpu 给其他线程(其他CPU可以执行其他任务),但是他的监控状态依然保持者,当指定的时间到了又会自动恢复运行状态。注意在调用 sleep()方法的过程中, 线程不会释放对象锁。当调用 wait()方法的时候,当前线程会暂时退出同步资源锁,同时会放弃对象锁,进入等待此对象的等待锁定池,只有针对此对象调用 notify()方法后本线程才进入对象锁定池准备获取对象锁进入运行状态。

猜你喜欢

转载自blog.csdn.net/m0_46198325/article/details/122273085