多线程(基础)
进程:正在运行的一个程序
线程:由进程创建的,是进程的一个实体,线程也可以创建线程
一个进程可以拥有多个线程
一个java程序启动后它就是一个进程,它只提供资源装载的空间,具体的调度是由进程来完成的,一个java程序从main开始之后,进程启动,这个线程就是主线程。
进程仅仅是一个容器,进程不能运行,能运行的是进程中的线程。
并发:同一时间段内,多任务交替执行
并行parallel:同一时刻,多任务同时进行
创建线程的2种方法:
-
继承Thread类,重写run方法,实例化线程对象,调用start()方法,因为java是单继承,这种方法不适合共享资源
-
实现Runnable接口,重写run方法,实例化实现了接口的对象,实例化线程对象,调用start()方法,是一种代理模式
Thread是实现了Runnable接口的类,使用run支持多线程。无论使用Runnable还是Thread,都会new Thread,然后执行run方法。
start0()方法是底层方法,由JVM调用,真正实现多线程。
run方法是一个普通的方法,没有真正启动一个线程
Thread线程常用方法:
-
start启动线程
-
set/getName设置获取名字
-
currentThread获取当前线程对象
-
yield线程让步,当前运行--->就绪
-
join加入线程,当前运行--->阻塞
-
sleep线程休眠,当前运行--->阻塞,如果超:阻塞--->就绪
-
线程优先级setPriority(1~10,10最高,5默认)
-
执行方法run
JVM直接调用的是Thread类中的Run()方法
Thread(Runnable target, String name)
target接口对象,name线程的名字
线程生命周期
-
Thread.currentThread()获取当前线程
-
synchronized关键字,当某个对象被它修饰时,表明该对象在任一时刻只能被一个线程访问
new -→ ready --- running -→teminated
timedwaiting
waiting
blocked
线程通信
wait⽅法与notify⽅法必须要在同步代码块或同步函数中使用,并由同⼀个锁对象调⽤。
wait⽅法与notify⽅法是属于Object类的⽅法的。因为:锁对象可以是任意对象。
-
wait() 让当前线程处于等待状态,并释放锁
-
notify() 唤醒某个等待中的线程
-
notifyAll() 唤醒所有等待中的线程
-
wait 是Object顶级类的方法,只能在同步方法或者同步块中使用。wait会释放锁,要用notify()唤起,时长大于等于sleep。
-
sleep 是Thread线程类的静态方法,可以在任何地方使用,不会释放锁,它也不需要占用锁。
-
BlockingQueue自带阻塞
单线程
同一时刻,只允许执行一个线程
多线程
同一时刻可以执行多个线程
-
⼀个程序运⾏后⾄少有⼀个进程,⼀个进程中可以包含多个线程
-
并发:指两个或多个事件在同⼀个时间段内发⽣,多任务交替执行。
-
并⾏:指两个或多个事件在同⼀时刻发⽣(同时发⽣),需要多核cpu。
-
Runnable的实现方式是实现其接口 implements
-
Thread的实现方式是继承其类 extends
-
run()方法只是一个类中的普通方法,调用run方法跟调用普通方法一样
-
而start()方法是启动线程,它创建线程等一系列工作,然后自己调用run里面的任务内容。start0()真正的多线程
守护线程(Daemon Thread)
比如垃圾回收线程,就是最典型的守护线程。
Java程序入口就是由JVM启动main
线程,main
线程又可以启动其他线程。当所有线程都运行结束时,JVM退出,进程结束。
如果有一个线程没有退出,JVM进程就不会退出。所以,必须保证所有线程都能及时结束。
但是有一种线程的目的就是无限循环,例如,一个定时触发任务的线程:
如果这个线程不结束,JVM进程就无法结束。问题是,由谁负责结束这个线程?
然而这类线程经常没有负责人来负责结束它们。但是,当其他线程结束时,JVM进程又必须要结束,怎么办?
答案是使用守护线程(Daemon Thread)。
守护线程是指为其他线程服务的线程。在JVM中,所有非守护线程都执行完毕后,无论有没有守护线程,虚拟机都会自动退出。
因此,JVM退出时,不必关心守护线程是否已结束。
如何创建守护线程呢?方法和普通线程一样,只是在调用start()
方法前,调用setDaemon(true)
把该线程标记为守护线程:
在守护线程中,编写代码要注意:守护线程不能持有任何需要关闭的资源,例如打开文件等,因为虚拟机退出时,守护线程没有任何机会来关闭文件,这会导致数据丢失。
线程优先级
1 - 10级
1最低,10最高优先级。
线程的优先级越高 执行cpu的执行权越大 并不是一定会优先执行
锁
synchronized重量级
-
同步代码块(性能好)
-
同步方法
同一个锁才会有互斥现象
类锁和静态方法锁是同一把锁
1、修饰普通方法(锁住的是当前实例对象)
-
同一个实例调用会阻塞
-
不同实例调用不会阻塞
2、同步代码块传参this(锁住的是当前实例对象)
-
同一个实例调用会阻塞
-
不同实例调用不会阻塞
3、同步代码块传参变量对象 (锁住的是变量对象)
- 同一个属性对象才会实现同步
4、同步代码块传参class对象(全局锁)
- 所有调用该方法的线程都会实现同步
5、修饰静态方法(全局锁)
- 所有调用该方法的线程都会实现同步
对象锁(this)和类锁(class)区别?
1、对于静态方法,由于此时对象还未生成,所以只能采用类锁;
2、只要采用类锁,就会拦截所有线程,只能让一个线程访问。
3、对于对象锁(this),如果是同一个实例,就会按顺序访问,但是如果是不同实例,就可以同时访问。
4、如果对象锁跟访问的对象没有关系,那么就会都同时访问。
当使用 synchronized 加锁 class 时,无论共享一个对象还是创建多个对象,它们用的都是同一把锁,而使用 synchronized 加锁 this 时,只有同一个对象会使用同一把锁,不同对象之间的锁是不同的。
Lock(更轻量)
1.lock Lock 实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作
2.方法
void lock() 获取锁对象
void unlock() 释放锁对象
死锁
多个线程同时被阻塞,它们中的一个或者全部都在等待某个资源被释放。
由于线程被无限期地阻塞,因此程序不可能正常终止。
线程1
synchronized(obj1){
synchronized(obj2){}
}
线程2
synchronized(obj2){
synchronized(obj1){}
}
线程中常用方法
方法名称 | 方法描述 |
---|---|
public fifinal void stop() | 终止线程 |
public void interrupt() | 中断线程(只是标记改变中断状态而已,它不会中断一个正在运行的线程。) |
|
|public static void yield()
|暂停当前正在执行的线程对象,并执行其他线程(礼让)|
|public fifinal void join()|等待该线程终止(执行当前线程 再执行其它的线程)必须是在开启之后调用
|
Thread.interrupt的作用其实不是中断线程,而是通知线程应该中断了,如果线程处于正常活动状态,线程将继续正常执行,不受影响。
notify方法:
从类 java.lang.Object 继承的方法
用于唤醒在此对象监视器上等待的单个线程。