文章目录
Java-多线程
1 程序、进程、线程
名称 | 描述 |
---|---|
程序 | 为完成指定任务,用某种语言编写的一组指令的集合 |
进程 | 程序的一次执行过程,是操作系统分配资源的最小单位 |
线程 | 进程的进一步细化,是程序内部的一条执行路径 |
2 单核CPU和多核CPU
2.1 CPU
单核CPU其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程的任务
多核CPU才能更好的发挥多线程的效率
一个Java应用程序Java.exe其实至少有三个线程:main()主线程,gc()垃圾回收线程,异常处理线程.如果发生异常,会影响主线程
2.2 并行和并发
名称 | 描述 |
---|---|
并行 | 多个CPU同时执行多个任务,比如多个人同时做不同的事 |
并发 | 一个CPU同时执行多个任务,比如秒杀,多个人做同一件事 |
2.3 多线程的优点
1 提高应用程序的响应,对图形化界面更有意义,可增强用户体验
2 提高计算机系统CPU的利用率
3 改善程序结构.将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
2.4 应用场景
1 程序需要同时执行两个或多个任务
2 程序需要实现一些需要等待的任务时,如用户输入,文件读写操作,网络操作,搜索等
3 需要一些后台运行的程序时
3 线程的创建
创建线程有两种方式,但是启动线程只能通过调用Thread类对象中的start方法
3.1 方法一
/*
创建线程的第一种方式
创建类 继承 Thread
复写run()方法,run()方法就等于新线程中的main方法,是程序的起点和终点
启动线程:手动调用当前线程的start()方法,而不是run()方法
*/
public class Test1{
public static void test(){
//创建线程类对象
Thread t =new Test();
//启动线程
t.start();
for(int i =0;i<10;i++){
System.out.println("main->"+i);
}
}
}
class Test extends Thread{
@Override
public void run(){
for(int i = 0;i<10;i++){
System.out.println("run->"+i);
}
}
}
3.2 方法二
/*
创建线程的第二种方式
实现Runnable接口,覆写run()方法
启动线程,调用Thread类对象中的start方法
*/
public class Test1{
public static void test(){
//创建线程类对象
Thread t =new Test();
//启动线程
t.start();
for(int i =0;i<10;i++){
System.out.println("main->"+i);
}
}
}
class Test implements Runnable{
@Override
public void run(){
for(int i = 0;i<10;i++){
System.out.println("run->"+i);
}
}
}
4 线程的常用方法
Start() : 启动线程
setName() : 设置线程的名字,默认是Thread-0 , Thread-1 以此类推
getName() : 获取线程的名字
setPriority() : 设置优先级 , 1-10 , 10个级别,1最小,10最高
getPriority() : 获取优先级
Static currentThread() : 获取当前线程的内存地址(线程对象)
Static sleep() : 让当前线程进入睡眠状态,参数是毫秒数
5 线程的生命周期
一个线程完整的生命周期通常要经历五个阶段
阶段 | 描述 |
---|---|
新建 | 新生的线程属于新建状态 |
就绪 | 新生线程被start()后,没分配到CPU资源时 |
运行 | 分配到CPU资源开始运行 |
阻塞 | 被认为挂起或IO操作时 |
死亡 | 线程完成全部工作或出现异常和强制终止时 |
6 线程控制
方法 | 功能 |
---|---|
isAlive() | 判断线程是否存活 |
getPriority() | 获得线程的优先级数值 |
setPriority() | 设置线程的优先级数值 |
Thread.sleep() | 让当前线程睡眠指定毫秒数 |
join() | 调用某线程的该方法,并与当前线程合并 |
yield() | 让出CPU,当前线程进入就绪队列 |
wait() | 当前线程进入wait pool |
notify() | 唤醒对象的wait pool的一个等待线程 |
notifyAll() | 唤醒对象的wait pool的所有等待线程 |
7 线程同步
多个线程执行的不确定性引起执行结果的不稳定
多个线程对账本的共享,会造成操作的不完整性,会破坏数据
多线程出现了安全问题
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有 执行完,另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中,其他线程不可以 参与执行。
8 Synchronized锁
对于并发工作,你需要某种方式来防 止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法 就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须 锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁 之时,另一个任务就可以锁定并使用它了。
同步方法
Synchronized的使用方法
Java对于多线程的安全问题提供了专业的解决方式:同步机制
同步代码块:
synchronized (对象){
// 需要被同步的代码;
}
synchronized还可以放在方法声明中,表示整个方法为同步方法。
例如:
public synchronized void show (String name){
….
}
注意:
必须确保使用同一个资源的多个线程共用一把锁,这个非常重要,否则就
无法保证共享资源的安全
一个线程类中的所有静态方法共用同一把锁(类名.class),所有非静态方 法共用同一把锁(this),同步代码块(指定需谨慎)
释放锁的操作
当前线程的同步方法、同步代码块执行结束。
当前线程在同步代码块、同步方法中遇到break、return终止了该代码块、 该方法的继续执行。
当前线程在同步代码块、同步方法中出现了未处理的Error或Exception,导
致异常结束。
当前线程在同步代码块、同步方法中执行了线程对象的**wait()**方法,当前线 程暂停,并释放锁。
不会释放锁的操作
线程执行同步代码块或同步方法时,程序调用Thread.sleep()、
Thread.yield()方法暂停当前线程的执行
线程执行同步代码块时,其他线程调用了该线程的suspend()方法将该线程
挂起,该线程不会释放锁(同步监视器)。
应尽量避免使用suspend()和resume()来控制线程
9 Lock锁
Lock(锁)
class A{
private final ReentrantLock lock = new ReenTrantLock(); public void m(){
lock.lock();
try{
//保证线程安全的代码;
}
finally{
lock.unlock();
}
}
}
注意:如果同步代码有异常,要将unlock()写入finally语句块
synchronized 与 Lock 的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是 隐式锁,出了作用域自动释放
synchronized是JVM层面的,属于重量级,Lock是代码层面的,比较轻量级
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)