多线程(基础)
线程介绍
- 进程
进程是指运行中的程序,启动一个进程,操作系统就会为该进程分配内存空间。
进程是程序的一次执行过程,或是正在运行的一个程序。是动态过程:有它自身的产生、存在和消亡的过程。
- 线程
线程由进程创建,是进程的一个实体。
一个进程可以有多个线程。
- 并发与并行
- 并发:同一时刻,多个任务交替执行,造成一种“貌似同时”的错觉,简单的说,单核cpu实现的多任务就是并发
- 并行:同一时刻,多个任务同时执行,多核cpu可以事项并行。
线程使用
-
创建线程的两种方法
- 继承Thread类,重写run方法。
- 实现Runnable接口,重写run方法。(主要是因为java一个类只能继承一个父类,但是可以继承多个接口)
- 实现callable接口,重写run方法。(具有返回值)
-
为什么启动多线程调用的是start方法,不是run方法
继承后实现的run方法,如果调用的是它,那就相当没有开启多线程,此时的run方法相当一个普通方法。
具体可以去看start方法源码;
(1) public synchronized void start(){ start0(); } (2) //srarr0是本地方法,是JVM调用,底层是C/C++实现 //真正实现多线程的效果,是start0(),而不是run private native void start0();
-
关于实现runnable接口的类需要实例Thread对象,传入继承runnable的类对象,再调用start方法开启线程,这种方式使用了设计模式—静态代理(因为runnable中只有run方法,没有start方法)。
class Dog implements Runnable{ @Override public void run() { // TODO Auto-generated method stub } } public static void main(String[] args) { Dog dog=new Dog(); Thread thread=new Thread(dog); thread.start(); }
-
实现Runnable接口方式更加适合多个线程共享一个资源的情况,且避免了单继承的限制(若用继承Thread类多个线程共享一个资源则需要将共享的资源定义为static)。
//假设T3继承Runnable接口,并实现了run方法 T3 t3=new T3("hello"); Thread thread01=new Thread(t3); Thread thread02=new Thread(t3); thread01.start(); thread02.start();
线程方法
-
线程终止
- 基本说明
- 当线程完成任务后,会自动退出。
- 还可以通过使用变量来控制run方法退出的方式停止线程,即通知方式。
- 例:需求是启动一个线程 t,要求再main线程中去停止线程t,请编程实现。
- 实现:设置一个boolean类型的变量去控制while循环。
- 基本说明
-
常用方法
- setName 设置线程名称,使之与参数name相同
- getName 返回该线程的名称
- start 是该线程开始执行
- run 调用线程对象run方法
- setPriority 更改线程的优先级
- getPriority 获取线程的优先级
- sleep 在指定的毫秒数内让当前正在执行的线程休眠
- interrupt 中断线程,并没有真正的结束线程,所以一般用于中断正在休眠的线程
-
线程插队
-
yield:线程的礼让。让出cpu,让其他线程执行,但礼让的时间不确定,所以也不一定礼让成功
-
join:线程的插队。插队的线程一旦插队成功,则肯定先执行完插入的线程所有的任务
-
案例:创建一个子线程,每隔1s输出hello,输出20次,主线程每隔1s,输出hi,输出20次,要求:两个线程同时执行,当线程输出5次后,就让子线程执行完毕,主线程再继续。
public class demo2 { public static void main(String[] args) throws Exception { Thread thread = new Thread(new T()); thread.start(); for (int i = 0; i < 20; i++) { Thread.sleep(1000); System.out.println("主线程吃了" + i + "包子了"); if (i == 5) { System.out.println("主线程先让子线程吃完"); thread.join(); } } } } class T implements Runnable { @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 20; i++) { try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("子线程吃了" + i + "包子了"); } } }
-
-
-
守护线程
- 用户线程和守护线程
- 用户线程:也叫工作线程,当线程的任务执行完或通知方式结束
- 守护线程:一般是为工作线程服务的,当所有的用户线程结束,守护线程自动结束。
- 常用的守护线程:垃圾回收机制
- 用户线程和守护线程
线程生命周期
-
线程状态。
线程可以处于以下状态之一:
-
NEW
尚未启动的线程处于此状态。 -
RUNNABLE
在Java虚拟机中执行的线程处于此状态。 -
被阻塞等待监视器锁定的线程处于此状态。
-
WAITING
正在等待另一个线程执行特定动作的线程处于此状态。 -
正在等待另一个线程执行动作达到指定等待时间的线程处于此状态。
-
已退出的线程处于此状态。
一个线程可以在给定时间点处于一个状态。 这些状态是不反映任何操作系统线程状态的虚拟机状态。
-
Synchronized
-
线程同步机制
- 在多线程编程,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何同一时刻,最多有一个线程访问,以保证数据的完整性。
- 也可以这样理解:线程同步,即当有一个线程在对内存进行操作时,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程都不可以对这个内存地址进行操作,直到该线程完成操作,其他线程才能对该内存地址进行操作
-
同步具体方法-Synchronized
-
同步代码块
sychronized(对象){ //得到对象的锁,才能操作同步代码 // 需要被同步代码; }
-
synchronized还可以放在方法声明中,表示整个方法为同步方法
public synchronized void m(String name){ //需要被同步的代码 }
-
如何理解:就好像某位小伙伴上厕所前先把门关上(上锁),完事后再出来(解锁),那么其它小伙伴就可再使用厕所了。
-
使用synchronized解决售票问题。
-
互斥锁
- 基本介绍
- Java语言中,引入了对象互斥锁的概念,来保证共享数据操作的完整性。
- 每个对象都对应于一个可称为“互斥锁”的标记,这个标记用来保证再任一时刻,只能有一个线程访问对象。
- 关键字synchronized来与对象的互斥锁联系。当某个对象用synchronized修饰时,表明该对象再任一时刻只能由一个线程访问。
- 同步的局限性:导致程序的执行效率要降低
- 同步方法(非静态的)的锁可以是this,也可以是其他对象(要求是通一个对象)
- 同步方法(静态的)的锁为当前类本身。
- 注意事项和细节
- 同步方法如果没有使用static修饰:默认锁对象为this
- 如果方法使用static修饰,默认锁对象:当前类.class
- 实现的落地步骤:
- 需要先分析上锁的代码
- 选择同步代码块或同步方法
- 要求多个线程的锁对象为同一个即可
死锁
-
基本介绍
多个线程都占用了对方的锁资源,但都不肯相让,导致了死锁,在编程是一定要避免死锁的发生。