详解Java多线程
文章目录
1.基本概念:程序、进程、线程
- 程序 : 是为完成特定任务、用某种语言编写 的一 组指令的集合 。即指 一
段静态的代码 ,静态对象。 - 进程: 是程序的一次执行过程,或是正在运行的一个程序 。是 一个 动态
的过程 :有 它自身的产生、存在和消亡的 过程 。------ 生命周期
例如:正在运行的QQ,正在播放的音乐播放器
程序是静态的,进程是 动态的
进程作为资源分配的单位, 系统在运行时会为每个进程分配不同的内存区域 - 线程 :进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间〉它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
比如:360安全卫士,没运行就是一个程序;运行了,后台分配资源,是一个进程;当它打开清理垃圾,木马扫描,全面检查等线程时,这是可以同时进行的,所以360安全卫士支持多线陈
2.线程的创建和使用
Java 中的线程是通过java.lang.Thread来体现的
Thread构造器
线程创建的4种方法
方式一:继承Thread类
一般步骤
1)定义子类继承Thread类。
2)子类中重写Thread类中的run方法。
3)创建Thread子类对象,即创建了线程对象。
4)调用线程对象start方法:启动线程,调用run方法。
例子:这里我们写一个输出100内的奇偶数的线程,一个输出奇数,一个输出偶数
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
//创建线程
Count_even count1 = new Count_even();
//start()的两个作用: 1.启动线程;2.执行run()方法
count1.start();
//匿名对象执行start()
new Count_odds().start();
}
}
class Count_even extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
class Count_odds extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 1) {
System.out.println(i);
}
}
}
}
其中一段输出结果如下:
这里就可以说明:两个线程(其实这里有三个线程,main()也算一个)是同时进行的,相当于有两条执行路径,CPU在这两个线程中不断地切换
这幅图,成功解释了这段代码的运行过程
注意点:
1.如果自己手动调用run()方法,那么就只是普通方法,类似于调用对象中的普通方法,并没有开辟一个新的线程,没有启动多线程模式。
2.run()方法由JVM调用,什么时候调用,执行的过程控制都有操作系统的CPU调度决定。
3.想要启动多线程,必须调用start方法。
4.一个线程对象只能调用一次start()方法启动,如果重复调用了,则将抛出以上的异常“llegalThreadStateException”。
方式二:实现Runnable接口
1)定义子类,实现Runnable接口。
2)子类中重写Runnable接口中的run方法。
3)通过Thread类含参构造器创建线程对象。
4)将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
5)调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
package com.CharlesLC_Test;
public class Thread_Test2 {
public static void main(String[] args) {
Count_Even demo = new Count_Even();
//通过Thread类含参构造器创建线程对象。将Runnable接口的子类对象作为实际参数传递给Thread类的构造器中。
Thread count_num = new Thread(demo);
//调用Thread类的start方法:开启线程,调用Runnable子类接口的run方法。
count_num.start();
}
}
//定义子类,实现Runnable接口
class Count_Even implements Runnable{
@Override
//子类中重写Runnable接口中的run方法。
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(i);
}
}
}
}
较方法的好处:避免了Thread单继承的缺点;另外,可以多个线程共享一个Runable接口。
方式三:实现callable接口
- 创建一个callable的实现类
- 实现calL方法,将此线程需要执行的操作声明在call()中
- 创建callable实现类的对象
- 将callable实现类的对象传递到FutureTask构造器中,创建Futuretask的对象
- 将创建的Futuretask对象传到Thread类的构造器中,并调用start()方法
package com.charlesLC_thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class newThread {
public static void main(String[] args) {
//创建callable实现类的对象
Count_odd demo = new Count_odd();
//将callable实现类的对象传递到FutureTask构造器中,创建Futuretask的对象
FutureTask futureTask = new FutureTask(demo);
//将创建的Futuretask对象传到Thread类的构造器中,并调用start()方法
new Thread(futureTask).start();
try {
//若要将返回值的结果输出,则用get()方法
Object sum = futureTask.get();
System.out.println("总合为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
//创建一个callable的实现类
class Count_odd implements Callable {
@Override
//实现calL方法,将此线程需要执行的操作声明在call()中
public Object call() throws Exception {
int num = 0;
for (int i =0;i<100;i++){
if (i%2==0){
num +=i;
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
return num ;
}
}
方式三与方式二对比::
- 与使用Runnable相比,Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以抛出异常>支持泛型的返回值
- 需要借助Future Task类,比如获取返回结果
方式四:使用线程池来创建
- ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor
- Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
- Executors.newCachedThreadPool():创建一个可根据需要创建新线程的线程池
- Executors.newFixedThreadPool(n);创建一个可重用固定线程数的线程池
- Executors.newSingle ThreadExecutor():创建一个只有一个线程的线程池
- Executors.newScheduled ThreadPool(n):创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。
package com.charlesLC_thread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class PoolThread {
public static void main(String[] args) {
//1.提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(15);
//2.执行指定的线程的操作。需要提供实现Runnable接口或CalLable接口实现类的对象
service.execute(new num_count());//这里是runable
service.submit(new num_count2());//这里是callable
service.shutdown();
}
}
class num_count implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}}
class num_count2 implements Callable{
@Override
public Object call() throws Exception {
for (int i=0;i<100;i++){
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
return null;
}
}
这里创建了一个指定提供数量的线程池,定义了两个线程对象,然后分别在线程池中执行。
Thread类的方法
-
start(): 启动此线程,并调用run()方法;
-
run(): 线程在被调度时执行的 操作,在调用start()会自动执行。
-
Getname():返回此线程的名字。
在上面的例子中的System.out.println(count_num.getName());
输出结果:
为什么是后面有个数字零呢?
在构造Thread的时候,会自动加上后面的数字,这是递增的。 -
currentThread (): 静态方法,返回当前线程 。
-
setName():修改线程的名字
count_num.setName("我是线程1"); System.out.println(count_num.getName());
(利用上面的例子)
成功把线程名改了
- yiled():线程让步,释放当前线程的Cpu
暂停当前正在执行的线程,把执行机会让给优先级相同或更高的线程
- yiled():线程让步,释放当前线程的Cpu
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
Count_even count1 = new Count_even();
count1.start();
for (int i =0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
class Count_even extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(getName()+":"+i);
}
if (i == 20){
yield();
}
}
}
}
但是,虽然这里让步了,当时这不说明就把该线程给阻塞了,只是失去了cpu的执行权。有可能cpu在这两个线程(main和Count_even)的选择中,还会选择Count_even
- join():当某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到join()方法加入的join线程执行完为止
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
Count_even count1 = new Count_even();
count1.start();
for (int i =0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
if (i == 20){
try {
count1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
class Count_even extends Thread{
@Override
public void run() {
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(getName()+":"+i);
}
}
}
}
这里main()中的线程被阻塞,等count1执行完,main()才可以继续执行。
- sleep(long millis):令当前活动线程在指定时间段内放弃对CPU控制,使其他线程有机会被执行,时间到后重排队。
- isAlive():返回 boolean,判断线程是否还活着
线程的调度
- 线程的优先级等级
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5 - 涉及的方法
getPriority():返回线程优先值
setPriority(int newPriority):改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才执行
例子:
package com.CharlesLC_Test;
public class Thread_test1 {
public static void main(String[] args) {
Thread.currentThread().setPriority(Thread.MIN_PRIORITY);
Count_even count1 = new Count_even();
Count_odds count2 = new Count_odds();
count1.start();
count2.start();
for (int i =0;i<100;i++){
System.out.println(Thread.currentThread().getName()+":"+i);
}
System.out.println(Thread.currentThread().isAlive());
}
}
class Count_even extends Thread{
@Override
public void run() {
setPriority(NORM_PRIORITY);
for (int i = 0;i<100;i++){
if (i % 2 == 0) {
System.out.println(getName()+":"+i);
}
}
}
}
class Count_odds extends Thread{
@Override
public void run() {
setPriority(MAX_PRIORITY);
for (int i = 0;i<100;i++){
if (i % 2 == 1) {
System.out.println(i);
}
}
}
}
这里我给main中的线程设置优先级为最小,但是输出结果,还是main中的数据还是比MAX_PRIORITY等级的count2先输出;
3.线程的生命周期
新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run()方法定义了线程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
4.线程的同步
由于内容过多,放在新的博客中
链接:
5.线程的通信
由于内容过多,放在新的博客中
链接: