1.基础概念:程序、进程、线程
- 程序(Program):是为完成特定任务、用某种语言编程的一组指令的集合。即指一段静态的代码,静态的对象
- 进程(Process):是程序的一次执行过程,或者正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。—生命周期
- 程序是静态的,进程是动态的
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
- 多线程(thread):进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(PC),线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患
举个栗子:
- 一栋楼是一台超级计算机(多台电脑)
- 一层楼就是一个服务器(一台电脑,多个房间(多个进程))
- 一个房间(进程)里可以同时合租几个人(同时运行多个线程)
- 当有个人占用厕所时(线程资源占用),其他线程就无法所用厕所(线程堵塞),就会带来安全隐患
- 单核CPU和多核CPU的理解
- 单核CPU其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程任务。
- 多核CPU才能更好的发挥出多线程的效率
- 一个java应用程序java.exe,其实至少有三个线程:
- main()主线程、gc()垃圾回收线程、异常处理线程。如果发生异常会影响主线程。
- 并行和并发
- 并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
- 并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事
多线程的优点
- 提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
- 提高计算机系统CPU的利用率
- 改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利用理解和修改
2.线程的创建和使用
运行匿名线程class MyThread extends Thread{
public void run(){
System.out.println("线程指令集");
}
}
public class ThreadTest{
public static void main(String[] args){
MyThread t1 = new MyThread();
t1.start();
// 1. 调用start(),启动新线程run()
// 2. run()只会运行,不会启动新线程
// t1.run();
// 3.一个对象一个线程无法再使用start(),启动新的线程
MyThread t1 = new MyThread();
t2.start();
}
}
常用方法new Thread(){
@Override
public void run(){
System.out.println("线程指令集");
}
}
start()
启动当前线程run()
需要重写Thread类中的此方法,将创建的线程写入此方法中currentThread()
静态方法,返回执行当前代码的线程Thread.currentThread().getName()
显示线程名setName
设置线程名yield()
释放当前CPU的执行权join()
继续执行阻塞线程,直到join()线程加载完位置sleep(loog millitime)
指定睡眠millitime毫秒stop()
强制线程生命期结束(不推荐使用)isAlive()
返回Boolean,判断线程是否还活着setDaemon(true)
线程设置成守护线程,主线程结束跟着关闭。在start前面
线程优先级
- 优先级常量
MAX_PRIORITY
最高优先级MIN_PRIORITY
最低优先级NORM_PRIORITY
默认优先级
优先级操作
getPriority()
获取优先级setPriority()
设置优先级第二种创建线程的方法和使用
class MThread implements Runnable{
@Override
public void run(){
System.out.println("线程指令集");
}
}
public class ThreadTest{
public static void main(String[] args){
MThread mThread = new MThread();
Thread t1 = new Thread(mThread);
t1.start();
Thread t2 = new Thread(mThread);
t2.start();
}
}
3.线程的生命周期
4.线程同步和线程安全
1. 同步代码块
synchronized(同步监视器){
// 需要监视的代码
}
操作共享的代码,即为需要被同步的代码
- 共享数据:多线程共同操作的变量
- 同步监视器(锁)。任何一个类的对象,都可以充当锁
- 要求:多个线程必须要共用同一把锁
- 在实现runnable接口创建多线程的方法中,可以用this当同步监视器(this要唯一性)
- 当有多个线程通过
- 只允许一个线程通过,然后一个对象(obj)将其堵塞
- 只有一个线程调用tick时
- 安全输出数据,不会对数据照成错乱
- 退出后将对象(obj)停止堵塞
2. 同步方法
class MThread implements Runnable{
@Override
public void run(){
System.out.println("线程指令集");
}
public synchronized void show(){
// 需要
}
}
3. Lock锁
import java.util.concurrent.locks.ReentrantLock;
class win_port4 implements Runnable{
private int ticket = 100;
// 是否公平?意思是均匀分配资源
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run(){
while(true){ // 无法退出,暂不解决
try{
lock.lock(); //调用lock方法
if(ticket>0){
System.out.println(Thread.currentThread().getName() + ":卖了一张票,剩余" + ticket + "张票");
ticket--;
}else break;
}finally{
lock.unlock();
}
}
}
}
Lock和synchroonized的区别
- 相同点:都能解决安全问题
- 不同点:
- synchronized机制在执行完相当的同步代码以后,自动的释放同步监视器
- Lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
建议使用优先级
- lock
- 同步代码块(已经进入了方法体,分配了相应资源)
- 同步方法(在方法体之外)
懒汉式线程安全
// 线程安全的单列式之懒汉式
class Bank{
private Bank(){};
private static Bank instance = null;
// 方法一:效率稍差
// public static Bank getInstance(){
// synchronized (Bank.class){
// if(instance == null)
// instance = new Bank();
// return instance;
// }
// }
// 方法二:效率高
public static Bank getInstance(){
if(instance == null){
synchronized (Bank.class){
if(instance == null)
instance = new Bank();
}
}
return instance;
}
}
死锁
定义
不同的线程分别占用对方需要的同步资源不放弃,都等待对方放弃自己需要的同步资源,就形成了死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
5.线程的通信
wait()
当前线程进入阻塞状态,并释放同步监视器notify()
唤醒一个被wait的一个线程,如果有多个就唤醒优先级高的-
sleep()
和wait()
的异同相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
不同点: 两个方法的声明位置不同:
- Thread类中声明sleep()
- Object类中声明wait()
- 调用的要求不同:
- sleep()可以在任何需要的场景下调用
- wait()必须在同步代码块中
关于是否释放同步监视器:如果两个方法都使用在同步代码块或同步方法中
Callable
相比run()方法,可以有返回值- 方法可以抛出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
Future接口
- 可以对具体Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等。
- FutrueTask是Futrue接口的唯一的实现类
- FutureTask 同时实现了Runnable, Future接口。它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
新增方式二:使用线程池
好处:
- 提高响应速度(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理
JDK 5.0起提供了线程池相关API:ExecutorService 和 Executors
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- void execute(Runnable command) :执行任务/命令,没有返回值,一般用来执行Runnable
Future submit(Callable task):执行任务,有返回值,一般又来执行Callable - void shutdown() :关闭连接池
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n); 创建一个可重用固定线程数的线程池
- Executors.newSingleThreadExecutor() :创建一个只有一个线程的线程池
- Executors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行
深入理解线程操作
wait:等待,释放资源等待
notify
唤醒 notify:唤醒,随机唤醒一个被wait
唤醒的线程