1、程序,进程和线程
程序:程序(program)是为完成特定任务、用某种语言编写的一组指令的集合,即指一段静态的代码
进程:进程((process)是正在执行的程序,从Windows角度讲,进程是操作系统进行资源分配的最小单位.
线程:线程(thread)进程可进一步细化为线程,是一个进程内部的最小执行单元,是操作系统进行任务调度的最小单元,隶属于进程
2、进程和线程的关系
- 一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程而独立运行;
- 每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序, java程序的入口main()方法就是在主线程中被执行的
- 在主线程中可以创建并启动其它的线程
- 一个进程内的所有线程共享该进程的内存资源
3、如何创建线程
3.1、第一种方法:继承Thread类
- 在Java中要实现线程,最简单的方式就是扩展Thread类,重写其中的run方法,方法原型如下:
- Thread类中的run方法本身并不执行任何操作,如果我们重写了run方法,当线程启动时,它将执行run方法。
// 定义
public class MyThread extends Thread {
public void run() {
}
}
// 调用
MyThread thread = new MyThread();
thread.start();
例:
/*
实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法。
怎么创建线程对象? new就行了
怎么启动线程呢? 调用线程对象的start()方法
方法体中的代码永远是一次自上而下的顺序逐行进行的
*/
public class ThreadTest02 {
public static void main(String[] args) {
// 这里是main()方法,这里的代码属于主线程,在主线程中运行
// 新建一个分支线程
MyThread myThread = new MyThread();
// myThread.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程)不是并发的
// 启动线程
// start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码完成之后,瞬间就结束了
// 这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开辟出来,start()方法就结束了。线程就启动成功了
// 启动成功的线程会自动调用run方法,并且run方法在栈的栈底部压栈。
myThread.start();
// 这里的代码还是运行在主线程中
for (int i = 0; i < 100; i++) {
System.out.println("主线程-->" + i);
}
}
}
class MyThread extends Thread {
@Override
public void run() {
// 编写程序。这段程序运行在分支线程中(分支线)
for (int i = 0; i < 100; i++) {
System.out.println("分支线程-->" + i);
}
}
}
3.2、第二种方法:实现Runnable接口
- 避免了单继承的局限性,多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处理同一份资源。
- java.lang.Runnable接口中仅仅只有一个抽象方法
public interface Runnable {
public abstract void run();
}
例:
/*
实现线程的第二种方式,编写一个类实现java.lang.Runnable接口
*/
public class ThreadTest03 {
public static void main(String[] args) {
// 创建一个可运行的对象
// MyRunnable r = new MyRunnable();
// 将一个可运行的对象,封装为一个线程对象
// Thread t = new Thread(r);
// 合并
Thread t = new Thread(new MyThread());
// 启动线程
t.start();
for (int i = 0; i < 100; i++) {
System.out.println("主线程-->" + i);
}
}
}
// 这并不是一个线程类,是一个可运行的类,他还不是一个线程
class MyRunnable implements Runnable{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("分支线程-->" + i);
}
}
}
3.3、第三种方式:实现Callable接口:
- 优点:可以获取到线程的执行结果
- 缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻,效率较低
例:
/*
实现线程的第三种方式:实现Callable接口:
*/
public class ThreadTest14 {
public static void main(String[] args) throws Exception{
// 第一步:创建一个匿名内部类对象
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception { // call()方法相当于run方法,只不过这个有返回值
// 线程执行一个任务,执行之后可能会有一个执行结果
// 模拟换行
System.out.println("call method begin");
Thread.sleep(1000 * 10);
System.out.println("call method end!");
int a = 100;
int b = 200;
return a + b; // 自动装箱(300结果编程Integer)
}
});
// 创建线程对象
Thread t = new Thread(task);
// 启动线程
t.start();
// 直立式main方法,是在主线程中,
// 在主线程中,怎么获取线程返回结果。
// get()方法的执行会导致“当前线程阻塞”
Object obj = task.get();
System.out.println("线程执行结果:" + obj);
// main方法这里的程序要想缓慢执行必须等待get()方法的结果
// 而get()方法可能会需要很久,因为get()方法是为了拿另一个现成的执行结果
// 另一个县城是需要时间的。
System.out.println("helloo world");
}
}
3.4、第四种方式:通过 Callable 和 Future 创建线程
- 相比run()方法可以有返回值,方法可以抛出异常,支持泛型的返回值,但需要借助FutureTask类获取返回结果
3.5、 继承方式和实现方式的联系与区别
区别:
- 继承Thread: 线程代码存放Thread子类run方法中。
- 实现Runnable:线程代码存在接口的子类的run方法
4、Thread类中的常用方法
方法 | 说明 |
void start()
|
启动线程
|
void setName() | 设置线程的名称 |
final String getName()
|
返回线程的名称
|
final void setPriority(int newPriority)
|
设置线程的优先级
|
final int getPriority()
|
返回线程的优先级
|
final void join()
|
等待线程终止
|
static Thread currentThread()
| 返回对当前正在执行的线程对象的引用 |
static void sleep(long millis)
| 使线程休眠 以毫秒数暂停 |
yield()
| 对程序调度的暗示,即线程让步 |
5、线程优先级
- 事实上,计算机只有一个CPU,各个线程轮流获得CPU的使用权,才能执行任务;
- 优先级较高的线程有更多获得CPU的机会,反之亦然;
- 优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPriority和getPriority方法来设置或返回优先级
- 每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
- 具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
Thread类有如下3个静态常量来表示优先级
- MAX_PRIORITY:取值为10,表示最高优先级。
- MIN_PRIORITY:取值为1,表示最底优先级。
- NORM_PRIORITY:取值为5,表示默认的优先级。
线程优先级测试用例:
public class ThreadTest10 {
public static void main(String[] args) {
// 设置主线程的优先级
Thread.currentThread().setPriority(1);
/*System.out.println("最高优先级" + Thread.MAX_PRIORITY); // 10
System.out.println("最低优先级" + Thread.MIN_PRIORITY); // 1
System.out.println("默认优先级" + Thread.NORM_PRIORITY); // 5*/
// 获取当前线程对象的优先级
System.out.println(Thread.currentThread().getName() + "线程的默认优先级" + Thread.currentThread().getPriority()); // 5
// 创建实例方法
/*MyRunable06 my = new MyRunable06();
Thread t = new Thread(my);
t.start();*/
Thread t = new Thread(new MyRunable06());
t.setName("tt");
t.setPriority(10);
t.start();
// 优先级较高的,只是见到的CPU时间片相对较多一些
// 大概率方向偏向于优先级较高的
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
class MyRunable06 implements Runnable{
@Override
public void run() {
/*// 获取线程优先级
System.out.println(Thread.currentThread().getName() + "线程的默认优先级" + Thread.currentThread().getPriority()); //5*/
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "-->" + i);
}
}
}
6、线程状态
线程在他的生命周期中会处于不同的状态,大致分为下面几个:
- 新建:使用Thread类或其子类建立一个线程对象后,该线程对象就处于新建状态,保持这个状态直到start()线程。
- 就绪:当线程对象调用了start()方法以后,该线程就进入就绪状态。在就绪队列等待调度。
- 运行:当就绪的线程获取了CPU资源,就可以执行run(),此时线程处于运行状态。此时可以变成阻塞状态,就绪状态和死亡状态。
- 阻塞:如果一个线程执行了sleep()方法,失去所占用的资源后,就从运行状态变为阻塞状态。在睡眠时间已到或者重新获得资源后进入就绪状态。
- 死亡:一个运行状态的线程完成任务或者其他终止条件发生时,该线程就切换到终止状态。
7、守护线程
Java中的线程分为两类:用户线程和守护线程
- 任何一个守护线程都是整个JVM中所有非守护线程的保姆,只要当前JVM实例中尚存在任何一个非守护线程没有结束,守护线程就全部工作
- 只有当最后一个非守护线程结束时,守护线程随着JVM一同结束工作。
- 守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。
- 用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没 有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了.
- 设置守护线程: setDaemon(boolean on)
注意
:
设置线程为守护线程必须在启动线程之前,否则会跑出一个IllegalThreadStateException异常。
8、多线程
多线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程来执行不同的任务,也就是说允许单个程序创建多个并行执行的线程来完成各自的任务。
优点:(1)提高CPU利用率(2)提高程序响应(3)改善程序结构,将复杂任务分为多个线程,独立运行缺点:(1)线程也是内存,线程越多占用内存也越多(2)多线程需要协调和管理,所以需要CPU时间跟踪线程(3)线程之间对共享资源的访问会相互影响
9、线程同步
多个线程同时要读写一份共享资源时,可能会发生冲突,所以才引入线程同步。
9.1、并发与同步
并行:多个CPU同时执行多个任务
并发:在一个时间段依次执行操作
同步:就是排队+锁
- 多个线程直接要进行排队,一个一个对共享资源进行操作。为了保证数据正确性,在访问时加入锁,确保一个时间点只有一个线程访问共享资源。
如何实现同步:使用关键字synchronized(同步锁)同步方法或代码块
同步锁:同步锁可以是任何对象,必须为一,保证多个线程获得的是同一个对象(用来充当锁标记)
/*
守护线程
*/
public class ThreadTest13 {
public static void main(String[] args) {
Thread t = new BakDataThread();
t.setName("备份数据的线程");
// 自动线程之后,将线程
t.setDaemon(true);
t.start();
// 主线程、主线程是用户线程
for (int i = 0; i < 10 ; i++){
System.out.println(Thread.currentThread().getName() + "-->" + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class BakDataThread extends Thread{
public void run(){
int i = 5;
// 即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动终止
while(true){
System.out.println(Thread.currentThread().getName() + "-->" + (++i));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}