一、多线程是什么?
介绍多线程之前要介绍线程,介绍线程则离不开进程。
进程:是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元,一个进程一直运行,直到所有的非守护线程都结束运行后才能jie。
线程:就是进程中的一个独立控制单元,线程在控制这进程的执行。一个进程中至少有一个线程,线程不能独立存在,必须是进程的一部分。
多线程:一个进程中有多个线程并发执行,每条线程并行执行不同的任务。
二、为什么要用多线程?
①、为了更好地利用CPU资源,如果只有一个线程,则第二个任务必须等待第一个任务结束后才能进行;如果使用多线程,则在主线程执行任务的同时可以执行其他任务,不需要等待。
②、进程之间不能共享数据,线程可以。
③、系统创建进程需要为该进程重新分配资源,创建线程代价比较小。
④、Java语言内置了多线程功能支持,件货了Java多线程编程。
三、线程的生命周期介绍
线程生命周期:新建-->就绪-->运行-->等待、阻塞、睡眠-->终止
新建状态:从新建一个线程对象到程序Start()这个线程之间的状态都是新建状态。
就绪状态:线程对象调用了Start()方法后,就处于就绪状态,等到JVM里的线程调度器的调度。
运行状态:就绪状态下的线程在获取CPU资源后就可以执行方法run(),此时的线程便处于运行状态,运行状态的线程可变为就绪、阻塞、死亡三种状态。
等待、阻塞、睡眠状态:在一个线程执行了睡眠方法sleep()、挂起方法suspend()等方法后会失去所占有的资源,从而进入阻塞状态;在睡眠结束后或获得设备资源后可重新进入就绪状态。
①等待阻塞:运行状态中的线程执行wait()方法,是现成进入到等待阻塞状态
②同步阻塞:线程在获取synchronized同步锁失败(因为同步锁被其他线程占用)
③其他阻塞:通过调用线程的sleep()或者join()发出了I/O请求时,线程就会进入到阻塞状态。当sleep()状态超时,join()等待线程终止或超时,或者I/O处理完毕,线程重新进入就绪状态。
终止状态:run()方法执行完成后或者发生其他终止条件时就会切换到终止状态。
线程生命周期图:
四、线程优先级
每一个Java线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
Java线程的优先级是一个整数,取值范围是1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
默认情况下,每一个线程都会分配一个优先级NORM_PRIORITY(5)。
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程的执行顺序,而且非常依赖于平台。
五、线程的创建
Java提供三种创建线程的方法:通过继承Thread类本身、通过实现Runnable接口、通过实现Callable接口创建Future创建线程
①通过继承Thread类本身
定义一个类继承Thread、重写Thread类中的run()方法(将自定义代码存储在run()方法,让线程运行;该方法是新线程的入口)、调用线程的start()方法(该方法有两步:启动线程、调用run()方法)。
该方法实现多线程的本质上是实现了Runnable接口。
/**Thread创建线程代码实现*/
/**主类*/
package com.marshal.org.main;
import com.marshal.org.threadutils.thread.MyThread;
public class ThreadMain {
public static void main(String[] args) {
//创建两个线程
MyThread myThreadOne = new MyThread("线程一");
MyThread myThreadTwo = new MyThread("线程二");
//执行start()方法启动线程,调用run()方法也会执行,但是会以单线程方式执行
myThreadOne.start();
myThreadTwo.start();
//主线程
for (int i = 0; i < 5; i++) {
System.out.println("主线程main" + ":run " + i);
}
}
}
/**继承Thread的子类*/
package com.marshal.org.threadutils.thread;
public class MyThread extends Thread {
//设置线程名称
public MyThread(String name) {
super(name);
}
//重写run()方法
@Override
public void run() {
for (int j = 0; j < 4; j++) {
//获取当前线程的静态对象
Thread thread = currentThread();
System.out.println("当前线程的静态对象" + thread);
//获取当前线程的ID、线程名、线程状态、以及堆栈追踪信息
System.out.println("当前线程ID: " + j + "--" + this.getId() + ", 当前线程名字: " + this.getName() + ", 当前线程状态: " + this.getState() + ", 当前线程堆栈追踪信息: " + this.getStackTrace());
}
}
}
运行结果:
public void start() | 使该线程开始执行;Java虚拟机调用该线程的run()方法 |
---|---|
public void run() |
如果使用该线程是使用独立的Runnable运行对象构造的, 则调用该Runnable对象的run()方法; 否则,该方法不执行任何操作并返回 |
public final void setName(String name) | 修改线程名称,使之与参数name相同 |
public final void setPriority(int priority) | 修改线程优先级 |
public final void setDaemon(boolean on) | 将该线程标记为守护线程或用户线程 |
public final void join(long millisec) | 等待该线程终止的时间最长为millis毫秒 |
public void interrupt() | 中断线程 |
public final boolean isAlive() | 判断线程是否处于活跃状态 |
public static void yield() | 暂停当前正在执行的线程对象,并执行其他线程 |
---|---|
public static void sleep(long millisec) |
在指定的毫秒数内让当前正在执行的线程休眠(暂停执行), 此操作受到操作系统计时器和调度程序精度和准确性影响 |
public static boolean holdsLock(Object obj) | 当且仅当当前线程在指定的对象上保持监视器琐时,才返回true |
public static Thread currentThread() | 返回对当前正在执行的线程对象的引用 |
public static void dumpStack() | 将当前线程的堆栈跟踪打印至标准错误流 |
②通过实现Runnable接口
接口应该由那些打算通过某一线程执行其实例的类来实现(implements)。类必须定义一个成为run()的无参方法。
定义类实现Runnable接口、实现Runnable接口中的run()方法,将线程要运行的代码放在该run()方法中、通过Thread类创建线程对象、将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。自定义的run()方法所属的对象是Runnable接口的子类对象。所以要让线程执行指定对象的run()方法就要先明确run()方法所属对象、调用Thread类的start方法开启线程并调用Runnable接口子类的run()方法。
/**代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;
public class RunnableMain {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//创建线程对象
Thread threadOne = new Thread(myRunnable);
Thread threadTwo = new Thread(myRunnable);
Thread threadThree = new Thread(myRunnable);
threadOne.start();
threadTwo.start();
threadThree.start();
}
}
package com.marshal.org.threadutils.runnable;
//定义类实现Runnable接口
public class MyRunnable implements Runnable{
//实现Runnable接口的抽象方法run()方法, 在内部完成逻辑执行
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
}
运行结果:
③通过实现Callable接口创建FutureTask创建线程
创建Callable接口的实现类,并实现call()方法,该方法将作为线程执行体,且具有返回值。
创建Callable接口实现类的实例,使用FutureTask类进行包装Callable对象,FutureTask对象封装了Callable对象的call()方法的返回值。
使用FutureTask对象作为Thread对象启动线程。
调用FutureTask对象的get()方法获取子线程执行结束后的返回值。
/**代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.callable.MyCallableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableFutureMain {
public static void main(String[] args) {
MyCallableFuture myCallableFuture = new MyCallableFuture();
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(myCallableFuture);
for (int i = 0; i < 100; i++) {
if (i==30){
Thread thread = new Thread(integerFutureTask, "子线程");
thread.start();
}
}
try {
System.out.println("子线程的返回值为:" + integerFutureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
package com.marshal.org.threadutils.callable;
import java.util.concurrent.Callable;
public class MyCallableFuture implements Callable {
@Override
public Object call() throws Exception {
int i = 0;
for (; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+ "==" + Thread.currentThread().getState());
}
return i;
}
}
运行结果:
六、继承Thread类、实现Runnable接口、实现Callable接口的区别。
①继承Thread类:线程代码存放在Thread子类run()方法中。
优势:
编写简单,可以直接使用this.getName()获取当前线程,不必使用Thread.currentThread()方法。
劣势:
已经继承了Thread类,无法在继承其他类。
②实现Runnable接口:线程代码存放在接口的子类的run()方法中。
优势:
避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:
比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
③实现Callable接口,创建Future对象
优势:
有返回值、避免了单继承的局限性、多个线程可以共享一个target对象,非常适合多线程处理同一份资源的情形。
劣势:
比较复杂、访问线程必须使用Thread.currentThread()方法、无返回值。
建议:使用实现接口的方式创建多线程。
七、线程状态管理
①线程睡眠--sleep
线程的原因:线程执行的太快,需要强制执行到下一个线程。
线程睡眠的方法:
sleep(long millis)在指定的毫秒数内,让正在执行的线程休眠。
sleep(long millis, int nanos)在指定的毫秒数加指定的纳秒数内让正在执行的线程休眠。
/**线程睡眠代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;
public class RunnableMain {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//创建线程对象
Thread threadOne = new Thread(myRunnable);
threadOne.start();
}
}
package com.marshal.org.threadutils.runnable;
//定义类实现Runnable接口
public class MyRunnable implements Runnable{
//实现Runnable接口的抽象方法run()方法, 在内部完成逻辑执行
private int time =10;
@Override
public void run() {
while (true){
if (time>=0){
System.out.println(Thread.currentThread().getName() + ":" + time--);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
运行结果:
扩展:
Java线程调度是多线程的核心,只有良好的调度,才能充分发挥系统的性能,提高程序的执行效率。但是不管怎样编写调度,只能最大限度的影响线程执行的次序,而不能做到精准控制。因为使用sleep方法后,线程是进入阻塞状态的,只有当睡眠的时间结束,才会重新进入到就绪状态,而就绪状态进入到运行状态,是由系统控制的,我们不能靳准的去控制,所以如果调用Thread.sleep(1000)是线程睡眠一秒,可能结果会大于一秒。
②线程让步--yield
该方法和sleep()方法类似,也是Thread类提供的一个静态方法,可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。相当于只是当前线程暂停一下,然后重新进入就绪的线程池中,让线程调度器重新调度一次。也会出现某个线程调用yield方法之后暂停,但之后调度器又将其调度出来重新进入到运行状态。
/**线程让步代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;
public class RunnableMain {
public static void main(String[] args) {
MyRunnable myRunnable = new MyRunnable();
//创建线程对象
Thread threadOne = new Thread(myRunnable, "threadOne吃完还剩:");
Thread threadTwo = new Thread(myRunnable, "threadTwo吃完还剩:");
Thread threadThree = new Thread(myRunnable, "threadThree吃完还剩:");
threadOne.start();
threadTwo.start();
threadThree.start();
}
}
package com.marshal.org.threadutils.runnable;
//定义类实现Runnable接口
public class MyRunnable implements Runnable{
//实现Runnable接口的抽象方法run()方法, 在内部完成逻辑执行
private int time =10;
@Override
public void run() {
while (true){
if (time>=0){
System.out.println(Thread.currentThread().getName() + ":" + time-- + "个瓜");
if (time % 2 ==0){
System.out.println("线程:" + Thread.currentThread()+ "让步");
Thread.yield();
}
}
}
}
}
运行结果:
线程睡眠sleep和线程让步yield的区别
sleep方法声明InterruptedException,调用该方法需要捕获该异常。yield没有声明异常,也无需捕获。
sleep方法暂停当前线程后,会进入阻塞状态,只有当睡眠时间到了,才会转入就绪状态。而yield方法调用后,是直接进入就绪状态。
③线程合并--join
当B线程执行到A线程的join()方法时,B线程就会等待,等A线程都执行完毕,B线程才会执行。
join可以用来临时加入线程执行。
/**线程未合并代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;
public class RunnableMain {
public static void main(String[] args) {
MyRunnable myRunnableOne = new MyRunnable("A");
MyRunnable myRunnableTwo = new MyRunnable("B");
MyRunnable myRunnableThree = new MyRunnable("C");
//创建线程对象
Thread threadOne = new Thread(myRunnableOne);
Thread threadTwo = new Thread(myRunnableTwo);
Thread threadThree = new Thread(myRunnableThree);
threadOne.start();
threadTwo.start();
threadThree.start();
}
}
package com.marshal.org.threadutils.runnable;
//定义类实现Runnable接口
public class MyRunnable implements Runnable{
private String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(name + "--" + i);
}
}
}
运行结果:
/**线程合并代码实现*/
package com.marshal.org.main;
import com.marshal.org.threadutils.runnable.MyRunnable;
public class RunnableMain {
public static void main(String[] args) {
MyRunnable myRunnableOne = new MyRunnable("A");
MyRunnable myRunnableTwo = new MyRunnable("B");
MyRunnable myRunnableThree = new MyRunnable("C");
//创建线程对象
Thread threadOne = new Thread(myRunnableOne);
Thread threadTwo = new Thread(myRunnableTwo);
Thread threadThree = new Thread(myRunnableThree);
threadOne.start();
try {
threadOne .join();
} catch (InterruptedException e) {
e.printStackTrace();
}
threadTwo.start();
threadThree.start();
}
}
package com.marshal.org.threadutils.runnable;
//定义类实现Runnable接口
public class MyRunnable implements Runnable{
private String name;
public MyRunnable(String name){
this.name = name;
}
@Override
public void run() {
for (int i = 0; i < 4; i++) {
System.out.println(name + "--" + i);
}
}
}
运行结果:
补充.:
线程threadOne.join()需要等待threadOne .start()执行之后才有效果,如果threadOne .join()放在threadTwo.start()之后的话,threadOne 和threadTwo仍然会交替执行,threadThree会在前两个线程交替执行完成之后再执行。
④停止线程
原stop方法因为有缺陷已经停用了,那么现在该如何停止线程?现在分享一种让run()方法结束。
开启多线程运行,运行的代码通常时循环结构,只要控制住循环,就可以让run方法结束,也就是线程结束。
/**线程停止代码实现*/
⑤特殊情况
当线程处于了冻结状态,就不会读取到标记,也就不会结束。当没有指定方法让冻结的线程恢复到运行状态时,我们需要对冻结的状态进行清除,也就是强制让线程恢复到运行状态中来,这样就可以操作标记让线程结束。
Thread类提供该方法: interrupt()
如果线程在调用Object类的wait()、wait(long)、wait(long,int)方法,或者该类的join()、join(long)、join(long、int)、sleep(long)或sleep(long、int)方法过程中受阻,则其中断状态将被清除,还将收到一个InterruptedException。
八、线程优先级设置
每个线程执行时都有一个优先级的属性,优先级搞得线程可以获得较多的执行机会,而优先级低的线程则获得较少的执行机会。与线程休眠类似,线程的优先级仍然无法保障线程的执行次序。只不过,优先级高的线程获取CPU资源的概率较大,优先级低的也并非没机会执行。
优先级常量:MAX_PRIORITY、MIN_PRIORITY、NORM_PRIORITY
/**线程优先级代码实现*/
九、线程同步与锁
①为什么要进行线程同步
Java允许多线程并发控制,当多个线程同时操作一个可共享资源变量时(如对其进行增删该查操作),会导致数据不准确,而且相互之间产生冲突。所以加入同步锁以避免该线程在没有完成操作前被其他线程调用,从而保证该变量的唯一性和准确性。
②线程不同步会发生什么问题
不进行线程同步,会造成多个线程去操作同一个资源变量。
/**代码实现*/
③线程同步方法
同步方法一:
使用synchronize关键字修饰方法。因为每个Java对象都有一个内置锁,当用synchronize关键字修饰方法时内置锁会保护整个方法,而在调用该方法之前,要先获得内置锁,否则就会处于阻塞状态。
同步方法二:
同步代码块:就是拥有synchronize关键字修饰的语句块,被该关键字修饰的语句块会自动被加上内置锁,从而实现同步。
如果同步函数被静态修饰后,使用的锁是什么锁?静态方法中不能定义this!
静态内存:内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class 该对象类型是Class.
所以静态的同步方法使用的锁是该方法所在类的字节码文件对象,类名.class。
同步的前提:
必须有两个或者两个以上的线程。
必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
只能同步方法,不能同步变量和类。
不必同步类中所有方法,类可以拥有同步和非同步的方法。
如果一个线程在对象上获得一个锁,就没有任何其他线程可以进入(该对象)类中的任何一个同步方法。
线程睡眠时,他所持有的任何锁都不会释放。
同步锁的优缺点:
优势:解决多线程安全问题。
劣势:多线程需要判断,消耗资源降低效率。
如何找问题?
1、明确哪些代码是多线程运行代码。
2、明确共享数据。
3、明确多线程运行代码中哪些语句是操作共享数据的。
十、死锁
进程A中包含资源A,进程B中包含资源B,A的下一步需要资源B,B的下一步需要资源A,所以他们就相互等待对方占有的资源释放,所以也就产生了一个循环等待死锁。
/**死锁代码实现*/
死锁形成的必要条件:
互斥条件:资源不能被共享,只能被同一个进程使用。
请求与保持条件:已经得到资源的进程可以申请新的资源。
非剥夺条件:已经分配的资源不能从相应的进程中强制剥夺
循环等待条件:系统中若干进程形成环路,该环路中每个进程都在等待相邻进程占用的资源。
十一、线程间的通讯、等待唤醒机制
十二、线程池