一、线程简介:
1.程序、进程和线程:
程序:开发写的代码称之为程序。程序就是一堆代码,一组数据和指令集,只是一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体。
进程:它是程序在某个数据集上的执行,是一个动态的实体。进程存在生命周期,它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因任务完成而被撤销,反映了一个程序在一定的数据集上运行的全部动态过程。是系统执行资源分配和调度的独立单位每一进程都有属于自己的存储空间和系统资源
注意:进程A和进程B的内存独立不共享。
线程:是进程内一个相对独立的、可调度的执行单位,是程序的实际执行者,是最小的执行单位。通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程。线程是CPU调度和执行的最小单位。
内存图:
2.串行、并行和并发:
串行:一个程序处理完当前进程,按照顺序接着处理下一个进程,一个接着一个进行。
并行:多个任务同时进行。并行必须有多核才能实现,否则只能是并发。
并发:同一个对象被多个线程同时操作。(这是一种假并行。即一个CPU的情况下,在同一个时间点,CPU只能执行一个代码,因为切换的很快,所以就有同时执行的错觉)。
二、多线程:
多线程程序是指一个程序中包含多个执行流,它是实现并发机制的有效手段。从逻辑的观点看,多线程意味着一个程序的多个语句块同时执行,但不等于多次启动一个程序。
优点:
1.某些应用具有内在的多控制流结构,这些控制流具有合作性质,需要共享内存。采用多线程易于对问题建模。
2.在需要多控制流的应用中,多线程比多进程在速度上有绝对优势。
3.采用多线程可提高处理机与设备之间的并行性。
4.在多处理机的硬件环境中,多线程可并行执行,从而可提高资源利用率和进程的推进速度。
1.构建Thread子类对象:
步骤:
1.自定义一个类MyThread类,用来继承与Thread类。
2.在MyThread类中重写run()方法。
3.在测试类中创建MyThread类的对象
4.线程对象调用start()方法启动改线程。
示例:
//继承父类Thread
public class MyThread1 extends Thread{
public MyThread1(){}
//重写run方法
@Override
public void run(){
int i = 0;
do{
System.out.println(Thread.currentThread().getName() + "---->" + i);
i ++;
}while(i != 5);
}
}
public class Main {
public static void main(String[] args){
int i = 0;
//创建线程对象
MyThread1 t = new MyThread1();
//线程对象调用start()方法启动该线程
t.start();
do{
System.out.println(Thread.currentThread().getName() + "------>" + i);
i++;
}while(i != 5);
}
}
运行结果:
2.用实现Runnable接口对象构建Thread
步骤:
1.将需要实现多线程的类声明为实现Runnable接口的类,实现run()方法,并将线程体放在该方法中。
2.创建一个该类的实例。
3.从该实例创建一个Thread的实例。
4.调用start()方法启动该线程。
示例:
ublic class MyThread2 implements Runnable{
public MyThread2(){}
@Override
public void run(){
for(int i = 0;i < 3;i++){
System.out.println(Thread.currentThread().getName() + "----->" + i);
}
}
}
public class Main1 {
public static void main(String[] args){
MyThread2 t = new MyThread2();
Thread t1 = new Thread(t);
t1.start();
for(int i = 0;i < 3;i++){
System.out.println(Thread.currentThread().getName() + "----->" + i);
}
}
}
运行结果:
三、线程的状态和生命周期:
1.新生状态:
在创建Thread实例后,Thread对象处于新生状态,此时线程处于已被创建还未开始执行的一个特殊状态。当线程处于新生状态时,仅仅是一个空线程对象,它还没分配到系统资源,因此只能启动或终止它,其他操作会引起异常。
2.就绪状态:
使用start()方法启动线程,线程进入就绪状态。处于就绪状态的线程已具备了运行的条件,但还没获得CPU时间片,因此进入线程就绪队列中排队。一旦获得CPU时间片,线程就进入运行状态并自己调用自己的run()方法。
3.运行状态:
线程一旦获得CPU时间片,就开始执行run()方法中的线程执行体,线程进入运行状态。如果线程不能在一个时间片内执行完毕就会被中断,由执行状态回到就绪状态,进入到线程就绪队列中排队等待下一次被调度执行。如果优先级高的线程进入就绪状态,它会抢占正在执行的优先级低低线程的CPU,优先级高的线程会进入运行状态,优先级低的线程会回到就绪状态。运行状态的线程可以调用yield()方法主动放弃执行,转入到就绪状态。但是它仍然可能会被选中执行。对于yield()方法,只给相同优先级的线程可执行的机会。
4.阻塞状态:
线程是活的,但是当前缺乏运行它的条件,等条件满足了,就返回到就绪状态。
a.睡眠:线程调用sleep()方法睡眠一段时间。
b.资源阻塞:线程在等待一种资源或试图获得一个同步锁,但同步锁正被其他线程持有。
c.等待:调用了wait()方法使线程挂起,直到线程得到了notify()或notifyAll()消息。
5.死亡状态:
如果线程的run()方法执行完毕。线程正常结束;或者线程执行过程中抛出一个未捕获的异常或错误,线程异常结束,结束后,线程处于死亡状态。一旦线程死亡,就不能复生。如果在死亡状态的线程状态上再次调用start()方法,则会出现运行时异常IllegalThreadException.
四、线程的常用方法:
1.sleep()方法:
让线程阻塞的指定的毫秒数。 -指定的时间到了后,线程进入就绪状态。 sleep可研模拟网络延时,倒计时等。 每一个对象都有一个锁,sleep不会释放锁。
示例:
public class MyThread extends Thread{
private Boolean flag = true;
String threaddId;
public MyThread(){}
@Override
public void run(){
for(int i = 0;i < 10;i++){
if(flag){
System.out.println(Thread.currentThread().getName() + "------->" + i);
try {
Thread.sleep(100*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}else{
break;
}
}
}
}
public class Main {
public static void main(String[] args){
MyThread t = new MyThread();
t.start();
try {
Thread.sleep(100*10);
} catch (InterruptedException e) {
e.printStackTrace();
}
for(int i = 0;i < 10;i ++){
System.out.println(Thread.currentThread().getName() + "------>" + i);
}
}
}
2.yield()方法:
暂停当前正在执行的线程对象,并执行其他线程
yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。
yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。
注意:在回到就绪之后,有可能还会再次抢到。
示例:
public class Yield {
public static void main(String[] args) {
//创建线程
MyThread3 t1 = new MyThread3();
//开启线程
t1.start();
//创建线程
MyThread3 t2 = new MyThread3();
//开启线程
t2.start();
//创建线程
MyThread3 t3 = new MyThread3();
//开启线程
t3.start();
}
}
public class MyThread3 extends Thread{
public MyThread3(){}
@Override
public void run(){
for(int i = 0;i < 5;i++){
if(i == 3){
Thread.yield();
//当循i环到30时,让线程让步
//1、回到抢占队列中,又争夺到了执行权
//2、回到抢占队列中,没有争夺到执行权
}
System.out.println(Thread.currentThread().getName() + "------>" + i);
}
}
}
3.join方法():
作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行
示例:
package com.multithread.join;
class Thread1 extends Thread{
private String name;
public Thread1(String name) {
super(name);
this.name=name;
}
public void run() {
System.out.println(Thread.currentThread().getName() + " 线程运行开始!");
for (int i = 0; i < 5; i++) {
System.out.println("子线程"+name + "运行 : " + i);
try {
sleep((int) Math.random() * 10);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName() + " 线程运行结束!");
}
}
public class Main {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getName()+"主线程运行开始!");
Thread1 mTh1=new Thread1("A");
Thread1 mTh2=new Thread1("B");
mTh1.start();
mTh2.start();
try {
mTh1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
mTh2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+ "主线程运行结束!");
}
}