多线程 -需理解的基础知识点(线程基本使用、同步容器、并发容器、ThreadLock、volatile关键字)

线程理解

浅显直白说:就是每一次客户请求,就会产生一条主线程,真对执行效率,和现在多核CPU的运用,同时用多条线程处理。 ------ 源自个人理解

每个产生的线程都是在CPU上运行,CPU采用时间片的方式进行线程运作,即每个时间段执行线程,执行完线程或者执行的时间到了,当前时间片会被用完,当前线程等待下个时间片分配并继续执行。这里涉及线程执行一半,时间片段用完,下次执行记录问题,在CPU中叫做:上下文切换,需要涉及CPU寄存器,计数器去记录当前线程状态,方便下次获取时间片之后,线程继续运作。
并行:多个cpu实例或者多台机器同时执行一段处理逻辑,是真正的同时。
并发:通过cpu调度算法,让用户看上去同时执行,实际上从cpu操作层面不是真正的同时。

产生线程两种通用方式

继承Thread类、实现Runnable接口、Callable接口

Thread可以直接启动,因为Thread类本身就是实现的Runnable接口,建议直接使用Runnable接口会更加高效

Callable接口:future模式,并发模式的一种,可以有两种形式,即无阻塞和阻塞,分别是isDone和get。其中Future对象用来存放该线程的返回值以及状态

基础方法

start:用来启动线程,会在主线程之外开启子任务,获取时间片,并运行。
run:线程定义对应执行任务
sleep:线程睡眠,以毫秒为单位,形成线程阻塞
yield:让出线程资源给同等级活高等级线程,线程本身状态为就绪状态。存在自己交出CPU资源,然后又被自己运用的情况,有些不可控。
join:当前线程阻塞,等待join线程执行完毕后继续执行,类实际执行wait方法
interrupt: 中断,修改线程状态,若无捕捉状态(interrupted、isInterrupted),无法阻止,可采用interrupt异常捕捉,一般采用内部定义 isStop变量 控制线程终止(interrupted()是静态方法:内部实现是调用的当前线程的isInterrupted(),并且会重置当前线程的中断状态 ; isInterrupted()是实例方法,是调用该方法的对象所表示的那个线程的isInterrupted(),不会重置当前线程的中断状态)

基本属性
getId: 获取线程ID
getName:设置的线程名称
getPriority:获得线程优先级
setDaemon、isDaemon:设置守护线程(守护线程依赖于创建他的线程)

基本方法
sleep 使线程睡眠,若当前对象被上锁,是不会释放
yield 让出CPU资源,但不会释放锁
wait 会释放锁
wait/notify必须存在于synchronized块中(线程wait,资源可以被其他运用执行)
实际运用:生产者消费者(完整,自行调度睡眠时间,查看生产和消费的差异)
notify : 唤醒一条等待线程
notifyAll:唤醒所有等待线程

   package com.ly.thread;

    /**
 * create by lyMaster on 2019/4/22 - 10:51
 **/
public class ProductAndCustomer {
    private static final int Max = 10;

    private int number = 0;

    public synchronized void product() throws InterruptedException {

        if(number > 10){
            System.out.println("商品数量超多:"+number);
            wait();
        }

        this.number ++;
        System.out.println("生产:当前数量:"+number);
        notifyAll();
    }

    public synchronized void customer() throws InterruptedException {

        if(number < 1){
            System.out.println("无商品:"+number);
            wait();
        }

        this.number --;
        System.out.println("消费:当前数量:"+number);
        notifyAll();
    }


    public static void main(String[] args) {
        ProductAndCustomer productAndCustomer = new ProductAndCustomer();

        Thread pro = new Thread(new Product(productAndCustomer));
        Thread cum = new Thread(new Cutomer(productAndCustomer));

        pro.start();
        cum.start();

    }

}

class Product implements Runnable{
    private ProductAndCustomer productAndCustomer ;

    public Product(ProductAndCustomer productAndCustomer) {
        this.productAndCustomer = productAndCustomer;
    }

    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(4000);
                productAndCustomer.product();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
class Cutomer implements Runnable{
    private ProductAndCustomer productAndCustomer ;

    public Cutomer(ProductAndCustomer productAndCustomer) {
        this.productAndCustomer = productAndCustomer;
    }

    @Override
    public void run() {
        try {
            while (true) {
                Thread.sleep(2000);
                productAndCustomer.customer();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

关键字:volatile
CPU的高速缓存(CPU执行速度很快,但读写内存数据很慢,因此需要将要执行数据预先放入高速缓存,在给CPU执行,然后返回缓存刷新到内存中)
多线程访问同一变量,当thread1读取变量a从主存到缓存,其他thread也可以,导致执行结果错误。如:i= i + 1;,假设i = 0;两个线程执行结果为2,最终可能导致为 1。早期采用总线锁,就是读取i时,不允许其他线程从组村中读取i,导致效率低下。后出现协议:MESI - 都可以同时读取,最先获取发出信号提示其他线程当前变量无效,得重新获取。
volatile线程安全问题
这样如果有一个变量i = 0用volatile修饰,两个线程对其进行i++操作,如果线程1从内存中读取i=0进了缓存,然后把数据读入寄存器,之后时间片用完了,然后线程2也从内存中读取i进缓存,因为线程1还未执行写操作,内存屏障是插入在写操作之后的指令,意味着还未触发这个指令,所以缓存行是不会失效的(可以理解为在要写操作前暂停了)。然后线程2执行完毕,(线程2完整的执行了读写操作)内存中i=1,(线程1再完成写的操作)然后线程1又开始执行,然后将数据写回缓存再写回内存,结果还是1。

并发保证:
原子性(交互时,一边加了,另一边必须减) java 对于基础数据赋值有原子性
可见性(有人对变量修改了,其他线程可知)
有序性(编码后,JVM编译不一定保证代码执行顺序,只会保证结果不错)

volatile 保证 可见性(变化给出无效变量重新获取)、有序性,无法保证原子性
运用:读操作

ThreadLocal :
跟随线程的副本变量 - 线程 与 对象之间形成 一对一的关系变量,防止跨线程问题。保证线程安全。例如:建立连接 - 关闭连接 这套流程。单线程无问题,当设计多线程,连接的公用性会很差,一个线程建立连接,另一个可能完成后直接关闭。需要线程独立拥有线程,切克同时进行,不影响效率。

原子类:
保证数据一致
AtomicIntegerFieldUpdater:原子更新整型的字段的更新器
AtomicLongFieldUpdater:原子更新长整型字段的更新器
AtomicStampedReference:原子更新带有版本号的引用类型。该类将整型数值与引用关联起来,可用于原子的更新数据和数据的版本号,可以解决使用CAS进行原子更新时可能出现的ABA问题。
AtomicReference :原子更新引用类型
AtomicReferenceFieldUpdater :原子更新引用类型里的字段
AtomicMarkableReference:原子更新带有标记位的引用类型。可以原子更新一个布尔类型的标记位和应用类型
AtomicIntegerArray :原子更新整型数组里的元素
AtomicLongArray :原子更新长整型数组里的元素
AtomicReferenceArray : 原子更新引用类型数组的元素
AtomicBooleanArray :原子更新布尔类型数组的元素
AtomicBoolean :原子更新布尔类型
AtomicInteger: 原子更新整型
AtomicLong: 原子更新长整型

同步容器:
connection:
list set queue
arrayList linkList
hashSet
map:
hashMap
以上非线程安全
vector stack hashtable
connoctions 注意s 是工具类,提供集合容器的排序查
Collections.synchronizedXxx()同步容器
大部分线程安全,但存在多线程删除数据,另一个线程在删除前获取数据信息,然后对数据操作,当先数据不存在。例:vector 长度10,thread1 获取vector长度,然后删除最后一位,thread2也vector长度,再输出最后一位,若thread1、thread2同时执行,thread2获取thread1一样长度,但Thread1已经删除,thread2不可获取。
并发容器
ConcurrentHashMap,
CopyOnWriteArrayList,
CopyOnWriteHashSet
采用分片处理,只对特定的片段进行加锁以保障线程安全,其他的读线程仍然可以访问map而不用等待正在访问的线程遍历结束

关于List迭代器处理:
1、单线程:iterator 获取、被迭代list本身做删除,或抛出异常:CoucurrentModificationException 改变与期望改变不一致,用迭代本身remove可以解决
2、多线程:用迭代本身就会有问题,如上vector操作一致,一个已删除,一个未删除,期望变化记录的值和实际值不一致,抛出异常。
解决:
线程任务同步,synchronized 和 lock
使用并发容器 CopyOnWriteArrayList 代替 List和Vector

非阻塞队列:PriorityQueue 、 LinkList
add 队尾插入,满异常
remove 队首移除,空异常
offer 对尾插入,满异常
poll 队首获取并移除,无null
peek 队首获取,无null

阻塞队列:ArrayBlockingQueue 、 LinkedBlockingQueue
put 队尾存入,满等待
take 队首获取,空等待
offer 队尾存入,满等待时间,过,false
poll 队首获取,空等待时间,无,null

区别:非阻塞,就是原先默认集合容器,线程一直读取,除非限制条件,不然就一直操作,直到异常。阻塞,当容器空时,或者满时,直接阻塞线程,等待数据。
忙循环
线程进入死循环中,特定条件下继续工作;采用sleep、wait、yield释放CPU控制权;在多核多个CPU运行时,若让出CPU,再次启动可能在另一CPU,需要重新获取缓存。

在Python 2.0中,使用thread模块实现多线程。 1. 创建线程 使用thread模块的start_new_thread()函数来创建新线程。该函数接收两个参数:一个是函数名,另一个是传递给函数的参数。 下面是一个例子: import thread import time # 定义一个函数,用于新线程 def print_time(threadName, delay): count = 0 while count < 5: time.sleep(delay) count += 1 print("%s: %s" % (threadName, time.ctime(time.time()))) # 创建两个新线程 try: thread.start_new_thread(print_time, ("Thread-1", 1)) thread.start_new_thread(print_time, ("Thread-2", 2)) except: print("Error: 无法启动线程") while 1: pass 运行后,会创建两个新线程,分别输出线程名和运行时间,每个线程运行5次。 2. 线程同步 多个线程访问同一资源时,可能会导致数据不一致的情况。因此,使用线程同步来保证数据一致性。 使用thread模块提供的锁(Lock)机制,可以实现线程同步。锁有两种状态:锁定和未锁定。每次只能有一个线程获得锁定状态。如果当前锁定状态为锁定,则无法再获得锁定,线程会被挂起直到锁定状态解除。 下面是一个例子: import thread import time # 创建锁 threadLock = thread.allocate_lock() # 定义一个函数,用于新线程 def print_time(threadName, delay): count = 0 while count < 5: time.sleep(delay) count += 1 # 加锁 threadLock.acquire() print("%s: %s" % (threadName, time.ctime(time.time()))) # 释放锁 threadLock.release() # 创建两个新线程 try: thread.start_new_thread(print_time, ("Thread-1", 1)) thread.start_new_thread(print_time, ("Thread-2", 2)) except: print("Error: 无法启动线程") while 1: pass 在该例子中,使用allocate_lock()函数创建了一个锁,然后在print_time()函数中使用acquire()函数获得锁,使用release()函数释放锁。 这样,每次只有一个线程可以获得锁,其他线程要等待该线程释放锁后才能获得锁,保证了数据的一致性。 3. 线程优先级 在Python 2.0中,可以使用thread模块提供的函数setpriority()和getpriority()来设置和获取线程的优先级。 setpriority()函数接收两个参数:线程ID和优先级。优先级范围是0~100,其中0为最低优先级,100为最高优先级。 getpriority()函数只接收一个参数:线程ID,返回该线程的优先级。 下面是一个例子: import thread import time # 定义一个函数,用于新线程 def print_time(threadName, delay): count = 0 while count < 5: time.sleep(delay) count += 1 print("%s: %s" % (threadName, time.ctime(time.time()))) # 创建两个新线程 try: thread.start_new_thread(print_time, ("Thread-1", 1)) thread.start_new_thread(print_time, ("Thread-2", 2)) except: print("Error: 无法启动线程") # 设置线程优先级 thread.start_new_thread(thread.setpriority, (thread.get_ident(), 50)) while 1: pass 在该例子中,使用get_ident()函数获取当前线程ID,然后使用setpriority()函数设置线程优先级为50。这样,在两个线程运行的同时,线程1的优先级更高,可以更快地运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值