1、线程的基本概念
1.线程,又称轻量级进程(Light Weight Process)。是程序中的一个顺序控制流程,同时也是CPU的基本调度单位。由多个线程组成,彼此间完成不同的工作,交替执行,称为多线程。
2.JVM虚拟机是一个进程,当中默认包含主线程(Main),可通过代码创建多个独立线程,与Main并发执行。
2、线程的组成元素
1、CPU时间片:操作系统为每个线程分配的执行时间。
2、运行数据:①堆空间:执行线程的对象,多个线程可以共享堆中的对象。②栈空间:存储线程使用的局部变量,每个线程都拥有独立的栈。
3、线程的逻辑代码。(run()代码)
3、创建线程的四种方式:
1、继承Thread类
2、实现Runnable接口
3、实现Callable接口
4、调用执行器(Executor)创建线程池
代码示例一:
/**
* 单线程示例一
* 1、继承Thread类
* 2、重写run()方法
*/
class Test extends Thread {
@Overried
public void run() {
//线程执行代码
System.out.println("调用start()方法,自动运行当前对象的run()方法");
}
}
/**
* 测试类
* 1、创建单线程对象
* 2、调用start()方法,启用线程
*/
public class Test01 {
public static void main(String[] args) {
Test t = new Test();
t.start();
}
}
代码示例二:
/**
* 单线程示例二
* 1、实现Runnalbe接口
* 2、覆盖run()方法
*/
class Test implements Runnable {
@Overried
public void run() {
//线程执行代码
}
}
/**
* 3、创建实现类对象 task01
* 4、创建线程对象 thread01
* 5、线程对象调用thread01.start(task01)方法
*/
public class Test01 {
public static void main(String[] args) {
Test t1 = new Test();//创建实现类对象
Thread t = new Thread(t1);
t.start();//启动线程
}
}
4、线程的六种状态
存储在packages:java.lang中
Enums:Thread.State
1、New(初始状态)
2、Runnable(运行状态)
3、Blocked(阻塞状态)
4、Waiting(无限期等待状态)
5、Timed waiting(有限期等待状态)
6、Terminated(终止状态)
状态迁移详解图例一:
状态迁移详解图例二:
状态迁移详解图例三:
影响线程运行的方法:
1、休眠 : public static void sleep(long millis)
#当前线程主动放弃时间片,回到就绪状态,竞争下一次时间片
#缺点,占用一个单线程(使当前调用的线程停留)
2、放弃:public static void yiele()
#允许其他线程加入到当前线程中,并优先执行
3、结合:public final void join()
5、线程安全
5.1 导致线程不安全的操作/原因:
情形一、并行与串行会造成线程“数据”的安全问题
需求:A线程将“string1”存入数组的第一个空位;B线程也将“String2”存入数组的第一个空位。
此时会造成线程不安全:
情形二、当线程并发访问临界资源时,如果破坏原子操作,可能会造成数据不一致。
临界资源:共享资源(例如同一个task对象),临界资源一次仅允许一个线程使用时,才能保证它不会重复存入多个数据。
原子操作:不可分割的多步操作,被视为一个整体,其顺序和步骤不可打乱或缺省。
5.2 解决办法:
一、synchronized { //同步代码块 }
1、每个对象都有一个互斥锁标记,用来分配给线程的。
2、只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步代码块。
3、线程退出同步代码块时,会自动释放相应的互斥锁标记。
二、 synchronized 返回值类型 方法名称(形参列表0) {//代码(原子操作)}
1、只有拥有对象互斥锁标记的线程,才能进入该对象加锁的同步方法中。 2、线程退出同步方法时,会释放相应的互斥锁标记。三、同步方法示例
JDK中的同步方式实现的线程安全的类有
1.StringBuffer
2.Vector
3.Hashtalbe
6、死锁
概念:多个并发进程因争夺系统资源而产生相互等待的现象,被称为“死锁”。
根本原因:①资源有限;②进程执行顺序发生错误。
6.1 死锁产生的四个必要条件:
1、互斥:资源在多进程间互斥。
2、占有且等待:进程执行需要多种资源,拿到了一部分,同时还等待另一部分,拿到的那部分资源又不会释放。
3、不可抢占:不能抢占已进程占有的资源。
4、循环等待:
6.2 解决办法:
1、线程通信 --------- wait() and notify()
步骤一:在对obj加锁的同步代码块中调用wait()方法,暂停当前线程。此线程会释放其拥有的所有锁标记。同时此线程阻塞处于无限期等待的状态中。释放锁,进入等待队列。
public final void wait() OR public final void wait(long timeout)
步骤二:在对obj加锁的同步代码块中调用notify()方法,从obj的Waiting中释放一个或全部线程。对自身线程没有任何影响。
public final void notify() OR public final void notifyAll()
经典问题2:
生产者–>存储容器–>消费者
5、线程池
线程池的概念:
1.线程容器,可设定线程分配的数量上限。
2.将预先创建的线程对象存入池中,并重用线程池中的线程对象。
3.避免频繁的创建和销毁。
原理:
将任务提交给线程池,由线程池分配线程、运行任务,并在当前任务结束后复用线程。
获取线程池:
1.常用的线程池接口和类(在包java.util.concurrent;中,并发包)
Executor:线程池的顶级接口。
ExecutorService:线程池接口,可调用**submit(Runnable task)**提交任务代码。
Executors:工厂类,通过此类可以初始化一个线程池。
通过newFixedThreadPool(int nThreads) 获取固定数量的线程池。参数:指定线程池中的线程的数量
通过newCachedThreadPool() 获得动态数量的线程池,如不够则参加新的,没有上限。
ExecutorService esf = Executors.newFixedThreadPool(10); //初始化一个固定数量的线程池对象(静态)
ExecutorService esc = Executors.newCachedThreadPool(); //初始化一个线程缓存池(动态)
1.继承Thread类、实现Runnable接口时,线程执行对象.start(),调用了**run()方法,实现线程。
2.Callable<>,传任务给线程去执行,但是它有返回值,调用的是call()**方法。
public interface Callable {
public V call() throws Exception;
}JDK5加入,与Runnable接口类似,实现之后代表一个线程任务。
Callable具有泛型返回值,可以声明异常。
Future接口
1.需求:使用两个线程,并发计算150,51100的和,再进行汇总统计。
思考:实际应用中,如何接受call方法的返回值?
概念:异步接受ExecutorService.submit()所返回的状态结果,当中包含了call()的返回值。
方法:V get()以阻塞形式(阻塞主线程)等待Future中的异步处理结果(call()的返回值),拿到值后(确保异步线程执行结束),主线程继续执行,直到结束。
线程池 、 call 、Future
6、线程安全的集合
Lock 接口
重入锁 ReentrantLock
Lock接口的实现类,与synchronized一样,具有互斥功能。
使用步骤:
Lock lo = new ReentrantLock(); //创建重入锁对象
try {
lo.lock(); //加锁
// 程序代码
} finally {
lo.unlock();
}
读写锁ReentrantReadWriteLock
一写多读的同步锁,读写分离,可分别分配读锁、写锁。
支持多次分配读锁,使多个读操作可以并发执行。
互斥规则:
写-写:互斥,阻塞。
读-写:互斥,读阻塞写、写阻塞读。
读-读:不互斥、不阻塞。
在读 操作远远高于写操作的环境中,可在保障线程安全的情况下,提高运行效率。
Lock lo = new RentrantReadWriteLock();
ReadLock rl = lo.Readlock();