1. 进程和线程
1. 1进程和线程的概念
-
进程是资源分配(CPU、内存)的基本单位,它是程序执行的一个实例,在程序运行时创建;程序运行时系统就会创建一个进程,并为它分配资源,然后把它放入线程队列,等待CPU的调度,当调度到它的时候,就会为它分配CPU时间片,程序开始真正运行。
-
线程是程序执行的最小单位,它是进行的一个执行流,是CPU调度和分派的基本单位;一个程序的多个线程之间可以并发执行。
2. 线程的实现
2. 1 多线程的实现
多线程的实现有三种方法:实现Runnable接口,继承Thread类,实现Callable接口
2. 1. 1 实现Runnable接口
- 先来看看Runnable源码:
@FunctionalInterface
public interface Runnable { // Runnable 接口源码
public abstract void run();
}
以下是Runnable接口实现多线程:
public class Test3 {
public static void main(String[] args) {
Thread thread = new Thread(new Test2(),"Test2_Thread");
thread.start(); // 使线程进入线程队列等待调度
}
}
class Test2 implements Runnable {
@Override
public void run() { // 重写run()方法
System.out.println(Thread.currentThread().getName() + " begin executing! ");
}
}
Test2类实现了Runnable接口,然后其对象作为参数传给Thread的构造方法,我们再来看看Thread类部分源码:
public class Thread implements Runnable { // java.lang.Thread
private Runnable target; // 引用构造方法参数中的Runnable
private volatile String name; // 线程名,若不设定则使用默认赋值
public Thread(Runnable target, String name) { // Thraed类的一个构造方法
this(null, target, name, 0);
}
@Override // 重写run()方法
public void run() {
if (target != null) {
target.run();
}
}
}
2. 1. 2 继承Thread类
java 8 新特性 lamda 公式 ,也称为闭包。
public class Test3 {
public static void main(String[] args) {
Thread thread = new Thread(()->{
System.out.println( Thread.currentThread().getName() + " begin executing! "); // 使用了lamda公式,简洁了代码
}, "Test3_thread");
thread.start();
}
}
2. 1. 2 实现Callable接口
//TODO
2. 2 关于run()和start()的区别
- run()方法
方法内是线程要做的事情;
- start()方法
在 start() 方法内部,JVM 将创建一个新的本地线程,并将其与 Thread 对象关联。然后,JVM 调用本地方法,该本地方法会执行新线程的 run() 方法。这样,新线程会在独立的执行流中执行 run() 方法的代码。
2. 3 线程的五种状态
-
新建 : 线程创建后就为新建状态( 例如 调用构造器 new Thread( ) ),此时具有相应的内存空间和其他资源,但还处于不可运行阶段;
-
就绪:通过调用线程对象的 start() 方法可以使线程进入就绪阶段,线程加入等待队列。此时的线程已经万事俱备,就等待CPU从等待队列的调度,为其分配时间片;
-
运行:线程被成功调度并分配处理器资源,线程就进入了运行阶段。此时自动调用线程对象的run()方法;
-
堵塞:线程在某些特殊情况下,暂停自己的运行状态,如 I/O操作等,或者调用线程的sleep()、wait()、join()方法等
都可以使线程进入堵塞阶段;当引起阻塞的原因被消除后,线程就能重新回到就绪阶段,等待CPU调度。
-
终止:线程有三种方式终止自己,终止后不具有运行能力
-
run()方法执行完,正常结束线程;
-
使用stop()方法强行终止线程,此方法直接终止线程(不推荐, Deprecated(since = “1.2”) );
-
2. 4 多线程常用的操作方法
2. 4. 1 线程的命名和取得
-
构造方法传入
public Thread(Runnable target, String name) { }
-
调用setName()方法传入
public final synchronized void setName(String name) { this.name = name; }
-
取得线程名字
public final String getName() { return name; }
线程名若不设定则会自动命名 ,使用默认名 "Thread - XXX "
2. 4. 2 线程休眠
public static void sleep(long millis, int nanos) throws InterruptedException { } // 指定睡眠毫秒数,纳秒数
public static native void sleep(long millis) throws InterruptedException; { } // 指定睡眠毫秒数
线程休眠使线程暂停运行,进入堵塞阶段,经过指定毫秒数后,又回到就绪阶段,等待调度;
睡眠中可能会产生中断异常 InterruptedException ,需对其进行异常捕获或处理。
2. 4. 3 线程中断
private volatile boolean interrupted; // 中断状态位
public void interrupt() // 使interrupted为true,抛出异常后立即又把设置interrupted为false
public boolean isInterrupted() { return interrupted; } // 测试线程是否已经中断。线程的中断状态不受该方法的影响。
public static boolean interrupted() // 测试当前线程是否已经中断。线程的中断状态 由该方法清除。
interrupt() 方法使 interrupted 为 true,也就是设置中断位为true,线程会时不时的检测这个中断位,一旦检测 interrupted 为 false,则 抛出interruptedException,中断的结果使堵塞的线程重新回到就绪状态。
interrupt()方法的作用不是终止线程,不会中断一个正在运行的线程。而是改变中断状态,使处于堵塞状态的线程产生interruptedException异常(需提前进行好异常处理),让线程提前结束堵塞状态,所以使线程堵塞的方法( wait()、join()、sleep() )都会有throws interruptedException。
Thread.interrupt()方法: 作用是中断线程。将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程
interrupt()方法只是改变中断状态,不会中断一个正在运行的线程。需要用户自己去监视线程的状态为并做处理。支持线程中断的方法(也就是线程中断后会抛出interruptedException的方法)就是在监视线程的中断状态,一旦线程的中断状态被置为“中断状态”,就会抛出中断异常。这一方法实际完成的是,给受阻塞的线程发出一个中断信号,这样受阻线程检查到中断标识,就得以退出阻塞的状态。
更确切的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞,此时调用该线程的interrupt()方法,那么该线程将抛出一个 InterruptedException中断异常(该线程必须事先预备好处理此异常),从而提早地终结被阻塞状态。如果线程没有被阻塞,这时调用 interrupt()将不起作用,直到执行到wait(),sleep(),join()时,才马上会抛出 InterruptedException。
2. 4. 4 线程合并
public final void join() throws InterruptedException
调用join()的线程将在所处线程前执行,所在的线程进入堵塞阶段
public class Test3 {
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("thread execute!");
sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
thread.join(); // main线程受阻等待thread执行完再执行
for(int i = 0;i < 10;i++){
System.out.print(i + " ");
}
}
}
2. 4. 5 线程让步
public static native void yield();
yield()做的是让当前运行线程回到就绪状态,给其他线程运行的机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。如果线程数很少,可能会无效果。
2. 4 . 6 线程优先级
线程优先级与线程的调度有关,个人优先级是主观意向,实际被调度的几率还是和性能有关。
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5; // 主线程默认优先级为5
public static final int MAX_PRIORITY = 10;
public final void setPriority(int newPriority) // 获取优先级
public final int getPriority() // 设置优先级
3. 线程同步和死锁问题
3. 1 同步
同步方法和同步代码块
3. 1线程通讯
wait() 和 notifyAll() 、 notifyAll()
3. 2 守护线程,非守护线程(用户线程)
private boolean daemon = false; // 默认线程为非守护线程
通过调用 setDaemon() 方法来设置守护线程
public final void setDaemon(boolean on) {
checkAccess();
if (isAlive())
throw new IllegalThreadStateException(); // 线程死亡后调用此方法抛出异常
}
daemon = on; // 源码通过调用setDaemon()方法来设置 daemon 的值
}
join() 线程对象调用,当前线程等待其执行完在执行
public class Test {
public static void main (String[] args)throws Exception {
thread2();
}
public static void thread1(){
Thread daemonThread = new Thread(()->{
for(int i = 0;i < 10;i++){
System.out.println("非守护线程(用户线程)执行");
}
});
daemonThread.setDaemon(true);
for(int i=0;i<10;i++){
System.out.println("主线程执行");
}
System.out.println(Thread.currentThread().getName() + "执行结束");
}
public static void thread2() throws Exception{
Thread thread1 = new Thread(()->{
for(int i = 0;i < 10;i++){
System.out.println("Thread1 execute!");
}
});
thread1.start();
Thread thread2 = new Thread(()->{
try {
thread1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0;i < 100;i++){
System.out.println("Thread2 execute!");
}
});
thread2.start();
Thread thread3 = new Thread(()->{
try {
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0;i < 100;i++){
System.out.println("Thread3 execute!");
}
});
thread3.start();
}
}
4. 锁
synchronized作用在实例方法上锁对象, synchronized作用在静态方法上锁类
synchronized(){}代码块,锁对象
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-VJ69E0UG-1605695535625)(E:\QQFile\1833475904\FileRecv\MobileFile\CSDN_1603966901791.jpg)]
package multieThread;
class A{
private static int b = 100;
public static void fun(){ // 静态方法
synchronized (A.class){ // 锁类
while(b > 0){
b--;
System.out.println(b);
}
}
}
}
public class Test1 implements Runnable {
private static int a;
@Override
public void run() {
A.fun();
}
public static void main(String[] args) {
Test1 test1 = new Test1();
new Thread(test1).start();
new Thread(test1).start();
new Thread(test1).start();
}
}
同步方法使用this锁 , 静态同步方法使用当前class文件