一:线程与进程
1 进程
进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。
2 线程
线程是一条执行路径,是程序执行时的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每个线程有自己的堆栈和局部变量,每个线程并行执行不同的任务。
例如:腾讯视频正在运行是一个进程,我们用腾讯视频看视频,还有下载多个视频。也就是一个进程(腾讯视频)创建多个线程(看视频和下载视频),看视频就是一个线程。一个进程可以运行多个线程(看视频和下载视频)。可以简单认为进程是线程的集合。
二:创建线程的方式
1.1继承Thread
1.2实现Runnale接口
1.3实现Callabe
1 继承Thread类,重写run方法
public class ThreadDemo1 extends Thread {
@Override
public void run() {
System.out.println("一边学习一边听音乐");
}
public static void main(String[] args){
ThreadDemo1 th=new ThreadDemo1();
th.start();
System.out.println("打球");
}
}
**2实现Runnable接口,重写run方法,然后包装成Thread类
public class ThreadDemo implements Runnable{
@Override
public void run() {
System.out.println("一边学习一边听音乐");
}
public static void main(String[] args){
ThreadDemo threadDemo=new ThreadDemo();
Thread t=new Thread(threadDemo);
t.start();
System.out.println("打球");
}
}
3实现Callable接口,重写call()方法,然后包装成java.util.concurrent.FutureTask, 再然后包装成Thread
public class CallableDemo implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<=100;i++){
sum+=i;
}
for(int i=0;i<100;i++){
Thread.sleep(3000);
System.out.println(Thread.currentThread().getName()+"\t"+sum);
}
return sum;
}
public static void main(String[] args) {
CallableDemo callable = new CallableDemo();
FutureTask<Integer> futureTask = new FutureTask<>(callable);
new Thread(futureTask).start();
int num=0;
for(int i=0;i<100;i++){
if(i%2==0){
num+=i;
}
}
for(int i=0;i<50;i++){
System.out.println("num="+num);
}
}
}
4 继承Thread类与实现Runable两者的比较
1 Runable方式可以避免Thread方式由于Java单继承特性带来的缺陷
2 Runnable方式的代码可以被多个线程共享,适合于多个线程处理同一资源的情况
三:实现线程的方式
1 顺序编程:
程序从上往下的同步执行,即如果第一行代码执行没有结束,第二行代码就只能等待第一行执行结束后才能结束。
例如:
public class MainDemo {
public static void eat(){
System.out.println("开始吃午饭");
System.out.println("吃完了,休息十分钟睡中午觉");
}
public static void drink(){
System.out.println("喝饮料");
System.out.println("喝完饮料了");
}
public static void main(String[] args){
eat();
drink();
}
}
2 并发编程:
多个任务同时做,各个任务独立,互不影响。
例如:
public class MainDemo {
public static void main(String[] args){
Eating eat=new Eating();
Drinking drinking=new Drinking();
new Thread(eat).start();
new Thread(drinking).start();
}
}
class Eating implements Runnable {
@Override
public void run() {
System.out.println("开始吃午饭");
System.out.println("吃完了,休息十分钟睡中午觉");
}
}
class Drinking implements Runnable{
@Override
public void run() {
System.out.println("喝饮料");
System.out.println("喝完饮料了");
}
}
3 两者区别:
顺序编程:代码固定从上到下顺序执行,上面的代码运行完了,才能执行下面的代码。若上面的代码有错误,下面的代码就不能执行了。就比如老师批改一道数学题,解题步骤是有顺序的,从上到下,一步一步去看懂,一个步骤错了,下面的步骤就不用看了,只给一些分数。
并发编程:将一个任务分割成多个任务去执行。例如:一个软件的开发是由一个团队负责的。组长安排任务给组员。任务的进度看个人的能力。谁的能力强,谁想完成,不分先后。最后组员都把任务做完,集中合成代码。
对于电脑而言,一台电脑只有一个cpu,多个线程是并发进行的。
四:线程的生命周期
线程从创建。运行到结束总是处于5个状态:新建状态,就绪状态,运行状态,阻塞状态,死亡状态。
如图所示:
1 新建状态(new Threda):准备好了一个线程(Thread)对象,即新建了一个线程对象。新建好后就需要为线程分配内存。(注意:此时线程只是进入了线程队列怕,等待获取cpu服务,具备了运行的条件,但并不一定已经开始运行了)
2 就绪状态 (Runnable):线程对象调用start()方法,即启动了线程。
3 运行状态 (Running): 执行run()方法.
4 阻塞状态 (Blocked): 正在运行的线程没有运行结束,暂时停止运行线程。
5 死亡状态(Dead): 状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)
五:Thread类方法的说明
1start():线程对象调用此方法,就启动了一个线程,线程之间没有顺序的,按照CPU分配的时间运行。
例如:
public class ThreadDemo1 extends Thread {
@Override
public void run() {
System.out.println("一边学习一边听音乐");
}
public static void main(String[] args){
ThreadDemo1 th=new ThreadDemo1();
th.start();//ThreadDemo1对象调用start方法,启动线程。
System.out.println("打球");
}
}
2 run():调用线程的run方法,就是普通的方法调用,虽然将代码封装到两个线程体中,可以看到线程中打印的线程名字都是main主线程,run()方法用于封装线程的代码,具体要启动一个线程来运行线程体中的代码(run()方法)还是通过start()方法来实现,调用run()方法就是一种顺序编程不是并发编程。
3 join()(插队):合并线程,待此线程执行完成后,再执行其他线程,其他线程进入阻塞状态。如买奶茶看见一条长长队伍,插队买奶茶。我买完后,到下一个人买。
例如:
public class ThreadDemo implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"Thread0 --"+i);
}
}
public static void main(String[] args){
try{
ThreadDemo threadDemo=new ThreadDemo();
Thread t=new Thread(threadDemo);
t.start();//
for(int i=0;i<100;i++){
t.join();
System.out.println("Thread1 --"+i);
}
}catch(InterruptedException e){
e.toString();
}
}
}
注意:使用join方法一般是当前运行线程和某个线程的关系较强,必须先等插入后的线程运行结束后,后一个线程才能执行。
2notify()(礼让):让当前的线程暂停运行。不是阻塞线程,而是将线程从运行状态转入就绪状态,让cpu重新调度
例如:
//yield写在哪个线程,就哪个线程礼让
public class YeildDemo implements Runnable{
@Override
public void run() {
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
public static void main(String[] args){
YeildDemo yeildDemo=new YeildDemo();
new Thread(yeildDemo,"a").start();
for(int i=0;i<100;i++){
if(i%2==0){
Thread.yield();
System.out.println(Thread.currentThread().getName()+"-->"+i);
}
}
}
}
3 notify()
线程调用wait()方法,等待某个条件变量,此时该线程进入阻塞状态。直到被通知了(被唤醒了,调用了notify()方法后),结束等待后,进入就绪状态。
例如:
public class MainDemo{
final static Object person =new Object();
public static class Eating implements Runnable{
public void run(){
synchronized (person){
System.out.println(System.currentTimeMillis()+"\t"+Thread.currentThread().getName()+"Eating come");
try{
System.out.println(System.currentTimeMillis()+"\t"+Thread.currentThread().getName()+"Eating wait");
person.wait();
}catch (InterruptedException r){
r.getStackTrace();
}
System.out.println(System.currentTimeMillis()+"\t"+Thread.currentThread().getName()+"Eating over");
}
}
}
public static class Drinking implements Runnable{
public void run(){
synchronized (person){
System.out.println(System.currentTimeMillis()+"\t"+Thread.currentThread().getName()+"Drinking come");
person.notify();
System.out.println(System.currentTimeMillis()+"\t"+Thread.currentThread().getName()+"Drinking over");
try{
Thread.sleep(2000);
}catch (InterruptedException r){
r.getStackTrace();
}
}
}
}
public static void main(String args[]){
try{
Thread thread1=new Eating();
Thread thread2=new Drinking();
thread1.start();
thread2.start();
}catch (Exception e){
e.printStackTrace();
}
}
}
六:线程中断与停止
**1线程停止有四种情况:
1.1.run运行结束
1.2.异常退出
1.3.stop强制中止,这个不推荐使用,存在风险,比如说扫尾清理工作未完成,关闭资源未完成 就退出了。
1.4.interrupt方法中断线程, 需要主动编码配合,写中断逻辑,但是可控。
2线程中断:
调用interrupt()方法中断线程
public class ThreadDemo implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "Thread0 --" + i);
}
}
public static void main(String[] args) {
ThreadDemo threadDemo = new ThreadDemo();
Thread t = new Thread(threadDemo);
t.start();//
for (int i = 0; i < 100; i++) {
Thread.interrupted();
System.out.println("Thread1 --" + i);
}
}
}
注意:调用interrupt()方法并没有退出线程。
Thread中提供了两个方法
1.this.interrupted 测试当前线程是否中断(如果是中断,则会清除中断的状态标志,也就是如果中断了线程,第一次调用这个方法返回true,第二次继续调用则返回false)
2.this.isInterrupted 测试线程是否已经中断(不清除中断的状态标志)