线程(Thread):程序执行流的最小单位
一个进程里是由一个或多个线程组成的
进程: 系统中正在运的一个应用程序(例如QQ...),由一个或多个线程组成的
同一进程内的多个线程可共享资源
多线程的两种运行方式:
1)并行 (parallel):同一时刻,有不同的线程在同时执行,根据操作系统中的任务调度器,
将CPU分成冉的时间片,分配给各个程序使用(
微观上是串行的(单核CPU),宏观上是并行的(多核CPU)
( 同一时刻有多个线程执行 )
2) 并发(concurrent) : 在同一时刻,只有一个线程在执行, 但多个线程交替执行
宏观上是并行的效果
( 同一时刻,只有一个线程在执行,但是多个线程交替在执行的)
多线程的好处:
(1) 多线程,多进程可以让程序执行不被阻塞
(2) 充分利用多核CPU的优势,提高执行效率
创建线程的两种方式:
1) 通过 继承( extends ) Thread 类,并重写它的 run() 方法(可以通过匿名内部类的方式实现)
格式: Thread t = new Thread ( new Thread () {
public void run(){
//要执行的代码;
}
});
//启动线程
t.start();
2) 通过 实现(implements) Runnable 接口, 并重写它的 run() 方法(可以通过匿名内部类的方式实现)
格式: Thread t = new Thread ( new Runnable(){
public void run(){
//要执行的带代码...
}
});
//启动线程
t.start();
3) 实现 ( implements ) Callable接口。并重写 Object call() 方法
//Callable代表线程要要执行的代码
FutureTask task = new FutureTask(new Callable(){
public Object call(){
//要执行的代码块...
}
});
Thread t = new Thread(task);
//启动线程
t.start();
//获取 call() 方法的返回值
Object o = task.get();
线程中常见的方法:
1) Thread.sleep(long n): 让当前线程 休眠 n毫秒
2) Thread.currentThread(): 找到当前线程, main方法实际是由主线程(main)来调用的
3) .start() : 启动当前线程,只能调用当前线程一次,如果多次调用start(),会出现 IllegalThreadStateException 异常
4) .join() : 等待当前线程执行结束
5) .join(long n): 限时等待当前线程执行结束, 若等待 n 毫秒后,无论该线程是否执行结束,都结束等待
6) .wait(): 让当前线程进入等待结束,只有被notify()唤醒才能重新被执行
7) .wait(long n) 有时限的等待, 到n毫秒后结束等待,或是被notify
8) .getName() : 获取当期线程的名字
9) .yield() : 谦让,当前线程谦让别的线程先执行
10) .interrupt: 打断当前正在执行的线程( 包括 sleep() / join() 的等待 )
守护线程: 默认情况下,java进程需要等待所有线程都运行结束,才会结束.
有一种特殊的线程叫做守护线程(守护的是主线程),只要主线程运行结束, 即使守护线程的代码没有执行完,也会跟着主线程一起结束,
( setDaemon(boolean b) : 设置当前线程为守护线程 )
// 守护线程
Thread t1= new Thread(()->{
System.out.println("守护线程开始执行...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程结束");
});
t1.setDaemon(true); // 设置该线程为守护线程
t1.start();
Thread.sleep(1000);
*****面试题: 直接调用 run() 方法 和 调用 start() 方法???
直接调用 run() 方法时,是主线程调用的 run() 方法,相当于普通方法的调用,没有启动新的线程进行调用
调用 start() 方法时,是先启动了一个新的线程,再由这个新的线程去调用 run() 方法
解决多线程的并发问题: 使用 sunchronized 同步机制
格式:
synchronized ( 对象 ){
// 要同步的代码块
}
每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器
要检查有没有owner,如果没有,此线程成为owner;
但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。
成为owner可以理解为获得了对象的锁, 在竞争的时候,是非公平的(由操作系统决定的)
注意: synchronized必须是进入同一个对象的monitor 才有上述的效果
注意: synchronized 语句块既可以保证代码块的原子性,
也可以保证代码块内变量的可见性。
但缺点是synchronized是属于重量级操作,性能会受到影响。
synchronized 也可以修饰用来方法
//方式一: 修饰 非静态方法
public synchronized void test() {
}
//等价于
public void test() {
synchronized(this) {// 获取的是调用此方法的 对象
}
}
//方式二: 修饰 静态方法
class Test{
public synchronized static void test() {
}
}
//等价于
public static void test() {
synchronized(Test.class) { // 获取的是 类对象
}
}
volatile: 易变的,可用来修饰 成员变量 / 静态成员变量 (不能修饰局部变量)
保证变量在多个线程之间的可见性, 但不能保证原子性
可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值
(volatile)static boolean run = true; // volatile 修饰,保证静态成员变量的可见性
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
Thread.sleep(1000);
run = false; //一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止
}
线程死锁 如果一组进程(或线程)中的每一个进程(或线程)都在等待仅由该组进程中的 其他进程(或线程)才能引发的事件,那么该组进程(或线程)是死锁的(Deadlock)
例: A线程已经获取到了 a 锁, B线程已经获取到了 b 锁, 此时A线程想要获取 b 锁,而B线程也想要获取 a 锁
两个线程都各自占着自己的锁不释放,而又想获取对方的锁,所以就导致了死锁的出现
Object A = new Object();
Object B = new Object();
Thread a = new Thread(()->{
synchronized (A) { // A线程已经获取 A锁
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) { // A线程想要获取 B锁
System.out.println("操作...");
}
}
});
Thread b = new Thread(()->{
synchronized (B) { // B线程已经获取 B锁
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) { // B线程想要获取 A锁
System.out.println("操作...");
}
}
});
a.start();
b.start();
避免死锁出现的方法:
obj.wait(): 让object监视器的线程进入 WaitSet 区域等待
obj.notify(): 让object上正在 WaitSet 区域等待的线程中随机挑一个唤醒
obj.notifyAll(): 让object上正在等待的线程全部唤醒
注意: 1) wait() , notify() , notifyAll() 全部都是 Object 类的方法
2) wait() 和 notify() 方法是多线程之间进行协作的手段,必须获取到该对象的锁( synchronized )才能使用
3) wait() 会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止
Object obj = new Object();
new Thread(()-> {
synchronized (obj) {
System.out.println("thread-0线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-0其它代码....");
}
}).start();
new Thread(()-> {
synchronized (obj) {
System.out.println("thread-1线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-1其它代码....");
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj){
obj.notify(); // 在obj的等待区 WaitSet 随机唤醒一个线程
obj.notifyAll(); //将obj的等待区 WaitSet中的所有线程都唤醒
}
}
*****面试题:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒
1) sleep(long n ):是 Thread类的方法, wait(long n): 是 Object类的方法
2) sleep(long n) 可以不配合 synchronized 使用,可单独使用;
wait(long n) 必须强制与 synchronized 一起使用
3) sleep(long n): 让当前线程休眠时,该线程不会释放已经获得的对象锁
wait(long n): 让当前该线程进入等待区时,该线程会将已经获得的对象锁释放,让别的线程有机会去得到这个锁
线程状态:
1) NEW(新建) : 线程刚被创建,但是还没有调用 start方法
2) RUNNABLE(可运行) : 当调用了start() 方法之后
3) BLOCKED(阻塞): 当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态
4) WAITING(等待): 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态
5) TIMED_WAITING : 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会
6) TERMINATED (终止)当线程代码运行结束
五种状态:
NEW(新建), RUNNABLE(可运行) , RUNNING(正在运行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(终止)