一、基本知识
并发:在一个时间段内,高速度切换同时处理多个任务.
并行:指多个任务同时处理.
进程:一段程序的执行过程,他代表了程序所占用的内存区域,是系统进行资源分配和调度的基本单位,是操作系统结构的基础。
线程(英语:thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。一个程序中至少有一个进程,一个进程中包含多个线程.
线程调度:
①分时调度:所有线程轮流使用cpu的使用权.
②抢占调度:优先级高的线程使用cpu时间相对较多.
分为1到10级,最低1级,最高10级,默认5级
主线程:执行main方法的线程.
二、Thread类
1、线程5种状态:
①新建状态(New):创建了一个线程对象。
②就绪状态(Runnable):调用了该对象的start()方法。
③运行状态(Running):就绪状态的线程获取了CPU执行权
④阻塞状态(Blocked):线程因为某种原因放弃CPU使用权,暂时停止运行
⑤死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
①新建状态(New):创建了一个线程对象。
②就绪状态(Runnable):调用了该对象的start()方法。
③运行状态(Running):就绪状态的线程获取了CPU执行权
④阻塞状态(Blocked):线程因为某种原因放弃CPU使用权,暂时停止运行
⑤死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期
2、阻塞状态:
①等待阻塞:运行状态中的线程wait()方法,JVM会把该线程放入到等待队列中,使本线程进入到等待阻塞状态
②同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),则JVM会把该线程放入锁池中,线程处于同步阻塞状态
③其他阻塞:通过调用线程的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态
①等待阻塞:运行状态中的线程wait()方法,JVM会把该线程放入到等待队列中,使本线程进入到等待阻塞状态
②同步阻塞:线程在获取synchronized同步锁失败(因为锁被其他线程占用),则JVM会把该线程放入锁池中,线程处于同步阻塞状态
③其他阻塞:通过调用线程的sleep()或join()或发出I/O请求时,线程会进入到阻塞状态
3、创建线程的方法一
①创建一个Thread类的子类
②在子类中重写run()方法,在方法内部设置进程任务.
③创建子类对象,调用Thread中start()方法,开启新线程//十大特
缺点:一个类只能继承一个父类,继承了Thread意味着不能继承其他类了,使用接口的方法有效避免了该缺点.
注意事项:结果是两个线程并发运行,多次启动一个线程是违法的,特别是当线程结束执行后,不能在启动.
匿名内部类例子:
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
①创建一个Thread类的子类
②在子类中重写run()方法,在方法内部设置进程任务.
③创建子类对象,调用Thread中start()方法,开启新线程//十大特
缺点:一个类只能继承一个父类,继承了Thread意味着不能继承其他类了,使用接口的方法有效避免了该缺点.
注意事项:结果是两个线程并发运行,多次启动一个线程是违法的,特别是当线程结束执行后,不能在启动.
匿名内部类例子:
new Thread(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}.start();
4、创建线程的方法二
①创建Runnable 接口的实现类.//ruang 呢包
②在实现类中重写run()方法.
③创建实现类的对象.
④创建Thread对象参数传入实现类的对象,并调用start()方法.
注意事项:①Thread中有个构造方法Thread(Runnable target) 传入的参数类型为Runnable类
②以上两个方法可以使用匿名内部类创建.
匿名内部类例子:
new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
①创建Runnable 接口的实现类.//ruang 呢包
②在实现类中重写run()方法.
③创建实现类的对象.
④创建Thread对象参数传入实现类的对象,并调用start()方法.
注意事项:①Thread中有个构造方法Thread(Runnable target) 传入的参数类型为Runnable类
②以上两个方法可以使用匿名内部类创建.
匿名内部类例子:
new Thread(new Runnable(){
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}).start();
5、Thread成员方法
| static Thread | currentThread() //可润特 //返回对当前正在执行的线程的对象 静态方法,使用对象名调用 |
| String | getName() //返回该线程的名称。名称从Thread-0开始 , |
| void | setName(String name) |
| static void | sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行).注意:该方法有异常需要手动处理. |
| void | start() 导致该线程开始执行;java虚拟机调用这个线程的 run方法。 |
三、线程安全问题
如果多个线程访问共享数据,则会出现安全问题(售票案例)
同步机制解决安全问题
1、同步代码块
格式: synchronized(锁对象){ //深口 奶子的
//存放可能会出现线程安全问题的代码(访问了共享数据的代码)
}
格式: synchronized(锁对象){ //深口 奶子的
//存放可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意:①锁对象可以是任意类的对象.
②必须保证多个线程使用的锁对象是同一个.
③锁对象要在run()方法外部声明.
缺点:程序需要频繁的判断锁,获取锁,释放锁,程序效率会低.
2、同步方法
①首先创建一个方法
格式: 修饰符 synchronized 返回值类型 方法名(参数列表){
//存放可能会出现线程安全问题的代码(访问了共享数据的代码)
}
②然后调用该方法
3、静态同步方法
格式: 修饰符 static synchronized 返回值类型 方法名(参数列表){
//存放可能会出现线程安全问题的代码(访问了共享数据的代码)
}
注意事项:锁对象是本类的class属性.
4Lock接口 //拉克
成员方法
void
lock() 获取锁。
void
unlock() 释放锁。
ReentrantLock-->Lock的实现类 //瑞en崔的
使用步骤:
①在成员位置创建一个ReentrantLock对象.
②在可能会出现安全问题之前调用Lock类中的lock方法获得锁.
③在可能会出现安全问题之后调用Lock类中的unlock方法释放锁.
成员方法
| void | lock() 获取锁。 | |
| void | unlock() 释放锁。 | |
ReentrantLock-->Lock的实现类 //瑞en崔的
使用步骤:
①在成员位置创建一个ReentrantLock对象.
②在可能会出现安全问题之前调用Lock类中的lock方法获得锁.
③在可能会出现安全问题之后调用Lock类中的unlock方法释放锁.
四、等待唤醒
Object类中方法
| void | wait() //维特 //使线程进入Waiting状态,无线等待 | |
| void | notify() //闹忒fai //唤醒在此对象监视器上等待的时间最长的单个线程。 | |
| void | wait(long timeout) | |
| void | notifyAll() | |
注意事项:
①wait()和和notify()必须要在同步代码块中使用.
②同步使用的锁对象要唯一.
③只有锁对象才能调用wait()和notify()方法
等待唤醒例子:
public class ClassMain {
public static void main(String[] args) throws InterruptedException {
Object object = new Object();
new Thread() {
@Override
public void run() {
while (true) {
synchronized (object) {
System.out.println("我要买5个包子");
try {
object.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("开始吃包子");
}
}
}
}.start();
new Thread() {
@Override
public void run() {
while (true) {
try {
Thread.sleep(5000); //此代码块不能放入同步代码块,切记
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object) {
System.out.println("老板做好了包子");
object.notify();
}
}
}
}.start();
}
}
知识总结:
①当一个线程执行到Thread类中的sleep()方法,该线程会睡眠,将cpu执行权交给其他线程.
②同步代码块等技术保证了该线程会先执行完该代码块中的内容才将cpu执行权交给其他线程,如果遇到wait()会交出cpu执行权.
③当一个线程执行到Object类中的wait()方法,该线程进入无线睡眠状态,此时cpu执行权交给其他线程,必须等待唤醒才能继续本线程.
④被唤醒或者是自己醒后,要与其他线程争抢cpu,如果抢不到,就进入阻塞状态继续等待下一次抢夺.
五、线程池
概念:一个可以容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程消耗资源.
原理:线程池的底层是一个单列集合,依次取出来使用,使用完毕在添加到末尾.
1、Executors类-->生产线程池的类 //A ke 晒可特日
成员方法:
| static ExecutorService | newFixedThreadPool(int nThreads) //费可死特 |
2、ExecutorService接口
成员方法:
| Future> | submit(Runnable task) //开启线程任务 |
| void | shutdown() |
3线程池使用步骤:
①调用Executors类中的静态方法newFixedThreadPool创建一个指定线程数目的线程池.
②创建一个实现Runnable接口的实现类,重写run()方法,设置线程任务.
③调用ExecutorService接口中的submit()方法,传入Runnable的实现类,开启线程任务.
④调用ExecutorService接口中的shutdowm()方法,关闭线程池.



