用多线程只有一个目的,那就是更好的利用cpu的资源,因为所有的多线程代码都可以用单线程来实现。
程序:是一段静态的代码,是应用软件执行的蓝本
进程:是程序的一次动态执行过程,它对应了从代码加载、执行至执行完毕的一个完整过程,这个过程也是进程本身从产生、发展至消亡的过程
线程:是比进程更小的执行单位。进程在其执行过程中,可以产生多个线程,形成多条执行线索,每条线索,即每个线程也有它自身的产生、存在和消亡的过程,也是一个动态的概念
主线程:(每个Java程序都有一个默认的主线程)
当JVM加载代码发现main方法之后,就会立即启动一个线程,这个线程称为主线程
注意:主线程不一定是最后完成执行的线程,各个线程运行时完全独立,争夺cpu,很可能主线程执行完了,子进程没有。
单线程:如果main方法中没有创建其他的线程,那么当main方法执行完最后一个语句,JVM就会结束Java应用程序
多线程:如果main方法中又创建了其他线程,那么JVM就要在主线程和其他线程之间轮流切换,JVM要等程序中所有线程都结束之后才结束程序。
多线程的优势:
减轻编写交互频繁、涉及面多的程序的困难
程序的吞吐量会得到改善
由多个处理器的系统,可以并发运行不同的线程
“同时”执行是人的感觉,在线程之间实际上轮换执行
线程生命周期(五个状态):新建、就绪、运行、阻塞、死亡
新建状态:线程对象已经创建,还没有在其上调用start()方法
就绪状态:当线程调用start方法,但调度程序还没有把它选定为运行线程时线程
运行状态:线程调度程序从可运行池中选择一个线程作为当前线程时线程所处的状态。(是线程进入运行状态的唯一方式)
阻塞(等待/睡眠)状态:线程仍旧是活的,但是当前没有条件运行。当某件事件出现,他可能返回到可运行状态
死亡状态:当线程的run()方法完成时就认为它死去。线程一旦死亡,就不能复生。 一个死去的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常
Java中两种创建线程的方式:
1.继承Thread类
重写run() 方法
new一个线程对象
调用对象的 start() 启动线程
优点:编写简单,如果需要访问当前线程直接使用this即可获得当前线程.
缺点:因为线程类已经继承了Thread类,不能再继承其他的父类
2.实现Runnable接口
实现run() 方法
创建一个Runnable类的对象r,new MyRunnable()
创建Thread类对象并将Runnable对象作为参数,new Thread(r)
调用Thread对象的start()启动线程
优点:线程类只实现了Runable接口,还可以继承其他的类.
缺点:编程稍微复杂,需要访问当前线程,必须使用Thread.currentThread()方法
多线程同步模型(生产者——消费者示例)
多线程问题——死锁
(两个或两个以上的线程在执行过程中,因争夺资源而造成了互相等待)
产生死锁的必要条件
互斥条件:指线程对所分配到的资源进行排它性使用
请求和保持条件:指线程已经保持至少一个资源,但又提出了新的资源请求
不可剥夺条件:进程已获得的资源,在未使用完之前,不能被剥夺,只能在使用完时由自己释放
环路等待条件:指在发生死锁时,必然存在一个线程—资源的环形链
出现死锁的情况
相互排斥:一个线程永远占用某一共享资源
循环等待:线程A在等待线程B,线程B在等待线程C,线程C在等待线程A
部分分配:线程A得到了资源1,线程B得到了资源2,两个线程都不能得到全部的资源
缺少优先权:一个线程访问了某资源,但一直不释放该资源,即使该线程处于阻塞状态
Java线程与线程的区别
所有与进程相关的资源,都被记录在PCB(进程控制模块)中。
进程是抢占处理机的调度单位。
线程属于某个进程,共享进程的资源。
线程由堆栈寄存器、程序计数器和TCB(线程控制模块)组成。
线程是CPU调度的最小单位,进程是资源分配的最小单位。
线程不能看做独立应用,而进程可看做独立应用
进程有独立的地址空间,相互不影响,线程只是进程的不同执行路径
线程没有独立的地址空间,多进程的程序比多线程程序健壮
进程的切换比线程的切换开销大
多线程的实现方法
继承Thread类,重写run方法
实现Runnable接口,重写run方法
通过Callable和FutureTask创建线程
通过线程池创建线程
Thread中start和run方法的区别
run方法只是thread的一个普通方法调用,还是在主线程里执行,是不会开启多线程的
直接调用Run方法,程序中只有主线程这一个线程,执行路径只有一条,还是要顺序执行,需要run方法体执行完毕,才可执行下面的代码。(相当与普通的方法)
start方法可启动多线程
start方法启动线程,无需等待run方法体代码执行完毕,可以直接继续执行下面的代码。(真正的实现多线程)
代码示例
创建一个MyThread方法继承Thread
public class MyThread extends Thread {
private String name;
public MyThread(String name) {
this.name = name;
}
public void run(){
for (int i = 0 ; i < 100 ; i++){
System.out.println(this.name +"--"+ i);
}
}
}
使用run方法:结果四个方法按照顺序运行,得出结论(直接使用run方法不是多线程)
public class demo {
public static void main(String[] args) {
MyThread my1 = new MyThread("第1个线程");
MyThread my2 = new MyThread("第2个线程");
MyThread my3 = new MyThread("第3个线程");
MyThread my4 = new MyThread("第4个线程");
my1.run();
my2.run();
my3.run();
my4.run();
}
}
使用start方法:结果发现四个方法交替输出,得出结论(start真正的实现多线程)
public class demo {
public static void main(String[] args) {
MyThread my1 = new MyThread("第1个线程");
MyThread my2 = new MyThread("第2个线程");
MyThread my3 = new MyThread("第3个线程");
MyThread my4 = new MyThread("第4个线程");
my1.start();
my2.start();
my3.start();
my4.start();
}
}
Thread和Runnable的关系
Thread是实现了Runnable接口的类,是Runnable的具体实现,使得run支持多线程;
因类的单一继承原则,推荐多使用Runnable接口;
创建一个MyThread方法继承Thread
public class MyRunnable implements Runnable {
private String name;
public MyRunnable(String name) {
this.name = name;
}
public void run(){
for (int i = 0 ; i < 100 ; i++){
System.out.println(this.name +"--"+ i);
}
}
}
创建多线程
四种构造方法:
/**
* corePoolSize 核心线程数
* maximumPoolSize 最大线程数
* keepAliveTime idle线程存活时间
* unit 上个参数的单位
* workQueue 线程对象的缓冲队列
* threadFactory 生成线程的工厂
* handler 达到容量后的回调
*/
//1.
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
//2.
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
//3.
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory)
//4.
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
什么是线程安全
在多条线程访问的时候,我们在主程序中不需要去做任何的同步,的程序还能按照我们预期的行为去执行,那么我们就可以说这个类是线程安全的。
我们什么时候需要考虑线程安全呢?:多个线程访问同一个资源
· 如果是多个线程访问同一个资源,那么就需要上锁,才能保证数据的安全性。
· 如果每个线程访问的是各自的资源,那么就不需要考虑线程安全的问题,所以这个时候,我们可以放心使用非线程安全的对象
如何实现线程安全
一、synchronized
采用synchronized关键字给代码块或方法加锁
二、Lock
在java 5之后,java.util.concurrent.locks包下提供了另外一种方式来实现线程同步,就是Lock。
三、synchronized和Lock的区别:
Lock是接口,synchronized是关键字
Lock可以提高多个线程进行读操作的效率。
Lock可以让等待锁的线程响应中断,使用synchronized时,等待的线程会一直等待下去,不能够响应中断
发生异常时:Lock需要在finally块中释放锁,否则很可能造成死锁现象。synchronized会自动释放线程占有的锁,不会导致死锁现象发生。