一、简介
1.进程与线程的区别
进程: 进程是系统进行资源分配和调度的独立单位。每一个进程都有它自己的内存空间和系统资源
线程: 线程作为资源调度的基本单位,是程序的执行单元,执行路径(单线程:一条执行路径,多线程:多条执行路径)。是程序使用CPU的最基本单位。
⼀个程序⾄少有⼀个进程,⼀个进程⾄少有⼀个线程。
并行与并发
并行:
- 并行性是指同一时刻内发生两个或多个事件。
- 并行是在不同实体上的多个事件
并发:
- 并发性是指同一时间间隔内发生两个或多个事件。
- 并发是在同一实体上的多个事件
由此可见:并行是针对进程的,并发是针对线程的
2.线程的属性与类型
包括线程:
- 编号(ID)
- 名称(Name)
- 线程类别(Daemon)
- 优先级(Priority)
线程编号ID:类型long,用于标识不同的线程,编号唯一性只存在java虚拟机的一次运行有效
线程名称Name:类型String,默认Thread-线程编号,设置该属性有助于线程调试和问题定位。
线程类别Daemon:类型boolean,值为true表示该线程为守护线程,否则为用户线程,默认值与相应线程的父线程该属性值相同,该属性必须在线程启动前设置!否则会报错
优先级Priority:类型int,该属性本质上是给线程调度器的提示,用于表示应用程序那个线程优先运行。java定义了1~10的10个优先级别。默认值为5(普通优先级别)。对应一个具体的线程而言,优先级别的默认值与父线程相同。
注:线程并不保证执行顺序按优先级进行!优先级使用不当可能导致某些线程用于无法得到运行!一般情况下不设置即可。
3.线程的两个基本类型(用户线程 (User Thread) 与守护线程 (Daemon) )
用户线程会阻止Java虚拟机的正常停止,即一个Java虚拟机只有在其所有的用户线程运行都结束的情况下才能正常停止。
守护线程不会影响Java虚拟机的正停止,即应用程序中有守护线程在运行也不会影响Java虚拟机的正常停止。因此守护线程通常用于执行一些重要性不是很高的任务,如监视其他线程的运行情况。
二、Java实现多线程
1.创建多线程的方式
- 继承Thread,重写run方法
- 实现Runnable接口,重写run方法
通过继承Thread类创建多线程
/*
* 定义子类,继承Thread
* 重写方法run
*/
public class subThread extends Thread{
@Override
public void run() {//线程要执行的任务
for(int i=0;i<50;i++){
System.out.println("run"+i);
}
}
}
/*
创建和启动一个线程
创建Thread子类对象
子类对象调用方法start() .
让线程程序执行, JVM调用线程中的run
*/
public class test {
public static void main(String[] args) {
subThread mythread1 = new subThread();
for(int i = 0;i<50;i++){
System.out.println("main1"+i);
}
mythread1.start();//只能调用一次
for(int i = 0;i<50;i++){
System.out.println("main2"+i);
}
subThread mythread2 = new subThread();
mythread2.start();
}
}
通过实现Runnable接口创建多线程
public class SubRunnable implements Runnable {
@Override
public void run() {
for(int i = 0; i<50; i++){
System.out.println("Runnable "+i);
}
}
}
public class test {
public static void main(String[] args) {
SubRunnable subRunnable1 = new SubRunnable();
Thread myThreadRun = new Thread(subRunnable1);
myThreadRun.start();
for(int i = 0;i<50;i++){
System.out.println("main2"+i);
}
}
}
匿名内部类创建多线程
public class test {
public static void main(String[] args) {
new Thread(){
@Override
public void run() {
for(int i =0;i<30;i++){
System.out.println("thread"+i);
}
}
}.start();
Runnable runnable = new Runnable() {
@Override
public void run() {
for(int i =0;i<30;i++){
System.out.println("Runnable"+i);
}
}
};
Thread thread2 = new Thread(runnable);
thread2.start();
}
}
通过Callable接口实现多线程
注:建议先看完线程池知识再来学习
实现方式与Runnable基本一致,只是Runnable重写的是run()方法没有返回值;Callable重写的是Call()方法,返回值是泛型。但是由于Thread类构造方法不能添加Callable类型,因此Callable接口只是在线程池中作为有返回值的提交线程使用。
线程池中的实现:
public class ThreadPool {
public static void main(String[] args) throws Exception{
/*ExecutorService ex = Executors.newFixedThreadPool(2);
ex.submit(new SubRunnable());
ex.submit(new SubRunnable());
ex.submit(new SubRunnable());*/
ExecutorService ex = Executors.newFixedThreadPool(2);
//返回Future对象,Callable的返回值在Future对象中
Future<String> f = ex.submit(new SubCollable());
//通过Future对象的get方法获得返回值,会要抛错
String str = f.get();
System.out.println(str);
}
}
/*
*Callable接口的实现类,作为线程提交任务出现
* 使用方法返回值
* */
public class SubCollable implements Callable {
@Override
public String call() throws Exception {//throws Exception可抛可不抛
System.out.println("Callable运行");
return "abdgljgl";
}
}
一般我们使用实现Runnable接口
- 可以避免java中的单继承的限制
- 应该将并发运行任务和运行机制解耦,因此我们选择实现Runnable接口这种方式!
2.多线程的API
2.1设置与获取线程名
//使用父类构造方法设置线程名
public class subThread extends Thread{
public subThread(String setName){
super(setName);
}
@Override
public void run() {//线程要执行的任务
System.out.println(super.getName()+" run");
}
}
public class test {
public static void main(String[] args) {
Thread thread1 = new subThread();
thread1.start();
thread1.setName("thread1");//通过setName()方法设置线程名
System.out.println(thread1.getName()+" thread1+main");//通过调用对象getName()方法获取线程名
System.out.println(Thread.currentThread().getName()+" main");//Thread.currentThread()返回对当前正在执行的线程对象的引用
Thread thread2 = new Thread(new SubRunnable());
thread2.start();
System.out.println(thread2.getName()+" Runnable+main");
Thread thread3 = new Thread(new SubRunnable(),"thread3");//直接创建Thread类时设置线程名
thread3.start();
}
}
//结果
thread1 thread1+main
main main
Thread-1 Runnable+main
thread1 run
Thread-1 Runnable+run
thread3 Runnable+run
2.2守护线程
守护线程是为其他线程服务的
- 垃圾回收线程就是守护线程
守护线程有一个特点:
- 当别的用户线程执行完了,虚拟机就会退出,守护线程也就会被停止掉了。
- 也就是说:守护线程作为一个服务线程,没有服务对象就没有必要继续运行了
使用线程的时候要注意的地方
- 在线程启动前设置为守护线程,方法是
setDaemon(boolean on)
,在调用start()方法后再设置会报错 - 使用守护线程不要访问共享资源(数据库、文件等),因为它可能会在任何时候就挂掉了。
- 守护线程中产生的新线程也是守护线程
2.3优先级线程
每⼀个线程都是有优先级的
⼀般来说,⾼优先级的线程在运⾏时会具有优先权,但这依赖于线程调度的实现,这个实现是和操作系统相关的(OS dependent)。
thread1.getPriority();//获得优先级 thread1.setPriority(2);//设置优先级
我们可以定义线程的优先级,但是这并不能保证⾼优先级的线程会在低优先级的线程前执⾏。线 程优先级是⼀个int变量(从1-10),1代表最低优先级,10代表最⾼优先级。 java的线程优先级调度会委托给操作系统去处理,所以与具体的操作系统优先级有关,如⾮特别需要,⼀般⽆需设置线程优先 级。
2.4线程生命周期
2.4.1线程的五种状态
- 新建状态(new): 线程对象被创建后就进入了新建状态。如:Thread thread = new Thread();
- 就绪状态(Runable): 也被称为“可执行状态”。线程对象被创建后,其他线程调用了该对象的start()方法,从而启动该线程。如:thread.start(); 处于就绪状态的线程随时可能被CPU调度执行。
- 运行状态(Running): 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
- 阻塞状态(Blocked): 阻塞状态是线程因为某种原因放弃CPU使用权限,暂时停止运行。直到线程进入就绪状态,才有机会进入运行状态。阻塞的三种情况:
- 等待阻塞: 通过调用线程的wait()方法,让线程等待某工作的完成。
- 同步阻塞: 线程在获取synchronized同步锁失败(因为锁被其他线程占用),它会进入同步阻塞状态。
- 其他阻塞: 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或超时、或者I/O处理完毕时,线程重新转入就绪状态。
- 死亡状态(Dead): 线程执行完了或因异常退出了run()方法,该线程结束生命周期。
关于线程中的start()和run()
**启动线程使用start(),而不是run()! **
在执行start()方法之前,只是有一个Thread对象,还没一个真正的线程。(分配内存,初始化成员变量)
——>start()之后,线程状态从新状态到可执行状态。(调用栈和计数器,线程没运行,只是可以运行)
——>当线程获得执行机会时,其目标run()方法将运行。
**start():**他的作用是启动一个新线程,新线程会调用相应的run()方法。start()不能被重复调用。
run():和普通成员的方法一样可以被重复调用。单独调用run()会在当前线程中执行run(),而不会启动新的线程。
run()和start()方法区别:
run()
:仅仅是封装被线程执行的代码,直接调用是普通方法start()
:首先启动了线程,然后再由jvm去调用该线程的run()方法。
?关于阻塞
关于死亡
—>调用run或call方法执行完成,正常结束
—>线程抛出一个为捕获的Exception或Error
—>直接调用stop()方法来结束该线程——容易导致死锁,不推荐。
不要试图对死亡的线程使用start()方法,将会抛出异常,并不会重启死亡的异常!
参考资料: https://blog.csdn.net/qq_30604989/article/details/80163366
2.4.2线程状态的切换方法
sleep()方法——线程休眠
调用sleep方法会进入计时等待状态,等时间到了,进入的是就绪状态而并非是运行状态! 要等待CPU调用。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
wait()方法——线程等待
wait()的作用也是能够让线程从“运行状态”进入“休眠(阻塞)状态”,同时释放同步锁。但是sleep不会释放同步锁
notify()方法——唤醒等待线程
join()方法
让“主线程”线程等待“子线程”运行完之后再运行。
在主线程中调用子线程s.start()启动子线程并调用s.join(),在调用s.join()之后主线程会一直等待,直到子线程运行完毕之后才接着运行。
class MyThread1 extends Thread{
public MyThread1(String name) {
super(name);
}
@Override
public void run(){
System.out.println(this.getName()+" start\n");
//延时操作
for (int i = 0; i < 1000; i++) {
}
System.out.println(this.getName()+"finish\n");
}
}
public class Demo extends Thread {
public static void main(String[] args) throws InterruptedException {
MyThread1 t = new MyThread1("myThread");
t.start();
t.join();
System.out.println(currentThread().getName()+" finish");
}
}
//结果
myThread start
myThreadfinish
main finish
yield()方法
和sleep类似,让线程从“运行状态”到“就绪状态”,但是不会阻塞线程,只是让当前线程停一会儿,让同优先级的其他线程获得被执行的机会,但是不保证一定会被执行。
yield&wait
1)wait让线程从“运行状态”到“阻塞状态”,yield让线程从“运行状态”到“就绪状态”
2)wait会释放同步锁,yield和sleep一样不会释放同步锁
下一篇:多线程——线程池