多线程学习(一)

(一)多线程API

1. 进程和线程

进程:操作系统管理的基本运行单位。也是资源分配的最小单位。进程内的线程共享这部分资源。

线程:进程中独立运行的子任务。

2. 常用的方法

(1) CurrentThread()

返回正在调用代码的那个线程。(需要注意:自定义线程中,只有run方法中执行的时候,currentThread 返回的才是自定义线程)

(2) isLive()方法

判断当前线程是否处在活动状态。

(3) Sleep()方法

正在执行的线程暂时休眠(暂停执行)

(4) GetID()方法

获取线程的唯一标识

(5) Interrupted()

测试当前线程是否已经是中断状态。执行后将状态清除,重新置为false

(6) isInterrupted()

测试当前线程是否已经是终端状态。执行后,不清楚状态标志。

(7) Suspend ()暂停、挂起

方法的功能是暂停线程。后面可以和resume()配合使用重新启动。但是不建议使用。主要的弊端如下:

资源独占。正在使用资源的线程被挂起,它加锁使用的资源正在独自占用。不会释放。容易造成资源独占。

不同步:正在使用的对象变量可能被修改到一半。造成数据不同步。

(8) Resume()恢复

被暂停的线程恢复正常使用。

(9) Yeid

放弃当前CPU资源,让给其它线程使用。

       

3. 线程的创建,启动方式

线程创建有两种方式。

(1) 继承Thread类。

ThreadA Extends Thread类,复写Run 方法。

启动:ThreadA.start() 直接使用。这种是每个ThreadA都是单独的一个线程。其中的变量都各自不同。

启动Thread(ThreadA,”name”).start()。这样的方式,ThreadA 被公用,里面的变量也是公用的。存在共享对象变量的情况

(2) 实现Runnable 接口。

继承MyRunnAble Imlpements Runnable 接口,实现run方法

启动Thread(MyRunnable).start()

       

    问题点:

为什么也要类,也要接口。因为JAVA是单继承。如果一个类已经有一个父类,就不能继承Thread来创建线程。所以,设计了Runable接口。

4. 停止线程

线程停止有三种方式:

  1. 线程执行完毕,正常退出。
  2. 使用stop方法强行退出。不建议使用(强行退出会导致依赖对象数据不一致-中途停止,可能修改到一半)。

(1) Stop 暴力停止:

会导致数据不一致。本质上也是抛出异常。ThreadDeath异常。

  1. 使用interrupt()方法退出【建议使用】调用一个线程的interrupt() 方法中断一个线程,并不是强行关闭这个线程,只是跟这个线程打个招呼,将线程的中断标志位置为true,线程是否中断,由线程本身决定。

(1) 异常法:

外部通过调用thread.interrupt

Run 方法中使用 interrupted 或者 isInterrupted 进行判断。抛出异常。可以中断线程。

(2) 沉睡中停止:

线程在sleep过程中,如果被调用interrupt(),会自动跑异常。然后停止线程。

(3) Return 方法:使用interruptreturn配合使用。

外部调用Thread.interrupt

Run方法中判断interrupted或者isInterrupted 进行判断。然后return。即可退出线程。

5. 暂停线程

Suspend ()暂停、挂起

方法的功能是暂停线程。后面可以和resume()配合使用重新启动。但是不建议使用。主要的弊端如下:

资源独占。正在使用资源的线程被挂起,它加锁使用的资源正在独自占用。不会释放。容易造成资源独占。

不同步:正在使用的对象变量可能被修改到一半。造成数据不同步。

Resume()恢复

被暂停的线程恢复正常运行。

6. 优先级

优先级越高,得到的CPU资源就越多。CPU优先执行优先级高的线程对象的任务。

线程的优先级继承性:线程的优先级具备继承性。没有特殊的指定,线程的优先级和创建它的线程的优先级一致。

7. 守护线程

和主线程共死,finally不能保证一定执行

start之前setDaemon(true);设置守护线程,主线程运行完,子线程也结束。常见的守护线程为:垃圾回收器。

(二)对象及变量并发访问

1. 相关定义

(1) 线程安全同步

进程的内存空间中都会有一块特殊的公共区域,通常称为堆(内存)。进程内的所有线程都可以访问到该区域。也称之为主工作内存。

线程安全:堆内存中的数据,由于可以被任意线程访问到,在没有限制的情况下会存在意外被修改的风险。即队内存的数据在没有被保护机制的情况下,对多线程而言是不安全的。

(2) 死锁

不同的线程分别占用对方需要的同步资源不释放,都等对方放弃自己需要的资源。

(3) 线程同步

多个线程访问同一资源。后一个线程需要等待前一个访问结束才能访问。同一个时刻只有一个线程访问。必须执行到底之后才能执行其它线程。

(4) 互斥锁

限定同一时刻, 只有一个线程能对共享数据进行操作, 其他线程需要等到该线程处理完之后再进行操作。

互斥锁有以下特征:

  1. 线程在进入同步代码块之前会自动获取锁,并且在退出同步代码块时会自动释放锁。
  2. 在同一时刻, 只有一个线程能持有这个锁。当线程 A 尝试获取一个由线程 B 持有的锁时, 线程 A 必须等待或阻塞, 直到线程 B 释放了这个锁。 如果 B 不释放这个锁, 则 A 就需要一直等待。
  3. 可重入性: 指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响
(5) 后续

2. 关键字

(1) synchronized互斥锁

    JAVA提供的内置互斥锁。synchronized的使用有三种方式:

① 普通同步方法,锁是当前实例对象(this)。多个线程,一旦有一个线程正在执行,其它线程就进不去【是对象锁,锁的是当前对象】。

② 同步代码块,锁是括号里面的对象。【也是对象锁,可以是任意对象】

③ 静态同步方法,锁是当前类的 Class 对象。【调用类的静态方法,通过对象锁是锁不住的。这个时候需要通过锁类】

 

其中 12是对象锁。理解为对象锁。就是一旦有线程得到这个对象。就不会有其它的线程访问这个对象。

(2) Volatile

JAVA提供的关键字,确保线程之间可见。线程通过排它锁确保只有一个线程获取Volatile 修饰的常量。

  1. 可见性:确保读取得时候是最后一次写的数据。
  2. 原子性:对volatile修饰对常量的读写是具备原子性的。
  3. 有序性:

3. 方法内变量和实例变量

方法内的变量线程安全。方法内的变量是私有的。

实例变量是非线程安全的。

(三)线程间通信

线程之间的通信,协作实现更复杂的工作。

1. 使用wait/notify实现线程间通信

Waitnotify函数都必须在同步代码块中被调用。否则会报错。

Wait()方法调用后,当前线程会释放已经占有的资源。并在线程阻塞队列中,等待被通知。

Notify()方法会唤醒阻塞队列中的一个线程。不过是随机的。Notify方法执行后并不立即释放锁。会执行当前线程内容后释放锁。

NotifyAll()方法会唤醒阻塞队列中的所有线程进行抢夺锁。

wait/notify方式外,还有其它的线程之间的通信。

  1. 管道进行通信:字节流
  2. 管道进行通信:字符流

2. 生产者、消费者模式

假死:所有线程都进入等待的状态。程序不再执行任何业务。产生假死的原因是:唤醒的是同类

当前模式常被称为 生产者、消费者、仓库模式。

生产者:一批线程负责生产数据,放入仓库。

消费者:一批线程负责消费数据。从仓库中取出。

仓库:生产者和消费者所共享的对象常量。

两类线程交叉操作是 生产者、消费者、仓库的一种简化。仓库为一个状态。0/1。根据不同的状态,确保哪类线程开始工作。其中,状态需要使用volatile进行修饰。

3. join方法

主线程和创建的子线程之间本来是两个线程。有各自的执行时间。不会相互等待。而在主线程中执行 子线程.join() ,之后的方法需要等子线程完成之后再执行。

Joinsleep之间的区别:join的本质是调用wait方法。所以,主线程当前锁被释放了的。而sleep是休眠。不会释放锁。

4. ThreadLocal类的使用

主要的目的是做数据的线程隔离。

变量的共享值 需要使用public static修饰。主要解决的是每个线程都可以保存自己的变量。不同线程都可以调用,存放自己的内容。

方法:对应的两个方法:Get() set(“”)

不同线程之间有隔离:多个线程同时向threadLocal类中写数据。各自读取的都是自己线程写入的数据。

InheritableThreadLocal 的使用。特点:子线程可以继承主线程的值。

(四)Lock的使用

1. ReentrantLock

(1) 同步实现

reentrantlock实现同步,是通过lock() 方法获取锁 和 UnLock()方法释放锁 的方式来实现的。

Lockunlock之间的内容。相当于synchronized的花括号之间的内容。

(2) 等待、通知实现

通过lock.newCondition() conditionawait() 等待方法和 signal()唤醒 方法。来实现等待、通知机制的。

同样有signalAll方法。唤醒condition所有的等待线程。

(3) 调用代码必须有同步监视器

synchronizedwait(), notify()必须在同步锁内一样。Conditionawaitsignal 也必须在lockunlock之间一样。

(4) 多个condition实现部分通知

通过不同的condition 可以实现部分通知。

(5) 公平锁和非公平锁

公平锁:线程获取锁的顺序是按照加锁的顺序。FIFO 队列。先进先出的顺序。

2. reentrantReadWriteLock

读写锁有两个锁

读操作的锁:也称之为共享锁。读锁和读锁的线程是可以共享的,也是异步执行。

Lock.readLock

写操作的锁:也称之为互斥锁。

Lock.writeLock

其中读读共享、读写互斥、写读互斥、写写互斥。

(五)线程池

1. 定义/作用

线程池:管理线程的池子。

作用:

(1) 帮助我们管理线程,避免增加创建线程和撤销线程的资源消耗。

(2) 提高响应效率。任务到来直接拿线程开始使用。

(3) 重复使用。

2. 建线程池的核心参数和作用

corePoolSize:线程池 核心线程数的最大值。

maximumPoolSize线程池的 最大线程数。

原文地址:https://www.cnblogs.com/maopneo/p/13948118.html