进程与线程

进程

Hello.java

编译并运行Hello.java

jps查询java当前运行的进程

24900 Hello 表示名为Hello的进程正在运行
关闭运行Hello.java的cmd后再查询jps

Hello进程消失了

说明运行一个程序就会启动一个进程,进程的名字就是执行的类的名字

线程

线程可以理解成工厂的流水线,一个进程可以有多个线程

package Thread;

public class demo {
    public static void main(String[] args) {
        /*
        Thread是线程类, Thread.currentThread()可以获取当前运行的线程
        getName获取线程名称
        */
        System.out.println(Thread.currentThread().getName());//main
        /*main方法在一个线程中运行,这个线程的名字是main
        java程序在运行时默认会产生一个进程,这个进程会有一个主线程
        代码都在主线程中执行
        */
    }
}

创建线程

工厂里肯定不止一条流水线,在程序中也可以如此,多几条线程来运行代码效率会提高,也可以通过不同线程来隔离逻辑

package Thread;

public class demo {
    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName());//main
        /*
        Thread thread = new Thread();//创建线程
        thread.start();//启动线程
        光创建和启动Thread线程是没有效果的,因为没有代码可以与它关联
        所以一般会自己写个线程来继承并重写Thread的方法
        */
        MyThread t = new MyThread();
        t.start();
        System.out.println(Thread.currentThread().getName());//main
        /*结果还是main,说明main方法在主线程,MyThread在新的线程,两者互不干扰
         而且启动线程需要些时间,而main线程是一直都在执行的,所以t.start()的结果
         比下面的getName结果要慢,控制台先打印出了main
        */
    }
}
class MyThread extends Thread{//声明自定义线程类
    @Override
    public void run() {//重写了run方法
        System.out.println("MyThread: "+Thread.currentThread().getName());//MyThread: Thread-0
    }
}

线程的生命周期

线程的执行方式

当前主流CPU几乎是多核的,所以线程默认的执行方式是并发执行
即多个线程是独立的,谁抢到了CPU的执行权,谁就能先执行

package Thread;

public class demo {
    public static void main(String[] args) {
        MyThread0 t0 = new MyThread0();
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();
        t0.start();
        t1.start();
        t2.start();
        System.out.println("main线程执行完毕");

    }
}
class MyThread0 extends Thread{//声明自定义线程类
    @Override
    public void run() {//重写了run方法
        System.out.println("MyThread0: "+Thread.currentThread().getName());
    }
}

class MyThread1 extends Thread{//声明自定义线程类
    @Override
    public void run() {//重写了run方法
        System.out.println("MyThread1: "+Thread.currentThread().getName());
    }
}

class MyThread2 extends Thread{//声明自定义线程类
    @Override
    public void run() {//重写了run方法
        System.out.println("MyThread2: "+Thread.currentThread().getName());
    }
}



说明线程间是独立运行的,打印顺序是不确定的。这就是并发执行

如果要是线程按顺序执行,可以将线程连接成串,称为串行执行

package Thread;

public class demo {
    public static void main(String[] args) throws Exception{//join方法要产生异常
        MyThread0 t0 = new MyThread0();
        MyThread1 t1 = new MyThread1();
        MyThread2 t2 = new MyThread2();
        t0.start();
        t0.join();//将t0线程跟主线程连在一起,且t0必须在t1线程启动前执行,不然不会按顺序执行
        t1.start();
        t1.join();
        t2.start();
        t2.join();
        System.out.println("main线程执行完毕");

    }
}
class MyThread0 extends Thread{//声明自定义线程类
    @Override
    public void run() {//重写了run方法
        System.out.println("MyThread0: "+Thread.currentThread().getName());
    }
}

class MyThread1 extends Thread{//声明自定义线程类
    @Override
    public void run() {//重写了run方法
        System.out.println("MyThread1: "+Thread.currentThread().getName());
    }
}

class MyThread2 extends Thread{//声明自定义线程类
    @Override
    public void run() {//重写了run方法
        System.out.println("MyThread2: "+Thread.currentThread().getName());
    }
}


三个线程都串到主线程中,所以会按照顺序执行程序

线程休眠

使线程进入定时等待状态,常用于周期性工作,或者让其他线程有机会交互

package Thread;

public class demo {
    public static void main(String[] args) throws Exception{
        while(true){
            //每3秒打印一次
            Thread.sleep(3000);//在哪个线程中调用此方法,哪个线程就会休眠
            //这里是主线程main
            System.out.println("main线程执行完毕");
        }
    }
}

构建线程的多种方式

一般方法

package Thread;

public class demo {
    public static void main(String[] args) {
        MyThread0 t0 = new MyThread0();
        t0.start();
        MyThread1 t1 =new MyThread1();
        t1.start();
        System.out.println("main线程执行");
    }
}
class MyThread0 extends Thread{
    //一般构建线程的方法就是自定义线程
    //1.继承线程类 2.重写run方法
    @Override
    public void run() {
        System.out.println("t0线程执行");
    }
}
class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("t1线程执行");
    }
}

面向对象思想方法

但这样做有个问题,每次想新增个线程就要多自定义一个线程类,这样做跟面向对象的思想有所出入
应该是定义一个线程类后就跟据这个模板来创建多个对象来实现创建多个线程

package Thread;

public class demo {
    public static void main(String[] args) {
        Mythread2 t2 = new Mythread2("t2");
        Mythread2 t22 = new Mythread2("t22");
        t2.start();
        t22.start();
        System.out.println("main线程执行");
    }
}
class Mythread2 extends Thread{
    private String threadName;
    public Mythread2(String name){
        this.threadName = name;
    }
    @Override
    public void run() {
        System.out.println(this.threadName+"线程执行");
    }
}

传递逻辑法

如果觉得上面的代码复杂,可以用这种方法,不用自定义线程类,通过类似于箭头函数的方式传递逻辑

package Thread;

public class demo {
    public static void main(String[] args) {
        Thread t3 = new Thread(() -> {
            System.out.println("t3线程执行");
        });
        t3.start();
        System.out.println("main线程执行");
    }
}

Runnable接口法

与上面类似,不过这个是通过Runnable接口来传递逻辑

package Thread;

public class demo {
    public static void main(String[] args) {
        Thread t4 = new Thread(new Runnable() {//一般用匿名类来实现接口
            @Override
            public void run() {
                System.out.println("t4线程执行");
            }
        });
        t4.start();
        System.out.println("main线程执行");
    }
}

线程池

线程池就是线程对象的容器,可以根据需要在启动时创建一个或多个线程对象
4种常见的线程池

创建固定数量的线程对象


假设在线程池创建了3个线程
现在有3个用户想要做一件事情(小圆圈),把这个事情提交到线程池对象中,线程池对象会帮助用户找到某个空闲的线程让它执行用户的事情。此时该线程用红色表示为忙碌状态

此时有第4个用户也想做些事情,但已经没有空闲的线程可用了。此时线程池对象会进入阻塞状态,当有线程如t3空闲后才将用户4提交的事情交给那个重新空闲的线程t3

package Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo {
    public static void main(String[] args) {
        //ExecutorService是线程服务对象,管理线程池
        ExecutorService executorService = Executors.newFixedThreadPool(3);//创建3个线程

        for (int i = 0; i < 5; i++) {//模拟有5个用户提交5件事
            executorService.submit(new Runnable() {//把工作提交给线程池对象
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());//模拟用户要做的事
                }
            });
        }
    }
}


5件事只要3个线程是不够的,所以会出现重复使用的情况,线程3和2在空闲后又执行了两个用户的事情

动态创建线程

package Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo {
    public static void main(String[] args) {
        //ExecutorService是线程服务对象,管理线程池
        ExecutorService executorService = Executors.newCachedThreadPool();

        for (int i = 0; i < 5; i++) {//模拟有5个用户提交5件事
            executorService.submit(new Runnable() {//把工作提交给线程池对象
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());//模拟用户要做的事
                }
            });
        }
    }
}


根据事情的数量来动态创建对应数量的线程,如果事情的数量超过了最大的线程数(看设备配置),多余的事情还是要等待有空闲的线程再执行。

单一线程

单一线程虽然效率低,但往往用于需要按顺序执行事情的情况

package Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo {
    public static void main(String[] args) {
        //ExecutorService是线程服务对象,管理线程池
        ExecutorService executorService = Executors.newSingleThreadExecutor();

        for (int i = 0; i < 5; i++) {//模拟有5个用户提交5件事
            executorService.submit(new Runnable() {//把工作提交给线程池对象
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());//模拟用户要做的事
                }
            });
        }
    }
}

定时调度线程

package Thread;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class demo {
    public static void main(String[] args) {
        //ExecutorService是线程服务对象,管理线程池
        ExecutorService executorService = Executors.newScheduledThreadPool(3);

        for (int i = 0; i < 5; i++) {//模拟有5个用户提交5件事
            executorService.submit(new Runnable() {//把工作提交给线程池对象
                @Override
                public void run() {
                    System.out.println(Thread.currentThread().getName());//模拟用户要做的事
                }
            });
        }
    }
}


看起来跟固定数量创建线程很像,但这种方式可以定义每个线程在什么时候执行,只是示例没有定义。

同步与异步

两个线程相互独立,各做各的事就是异步

当线程1通过join()方法与线程2串行时,需要完成线程1才能执行线程2,两者有先后关系就是同步

同步关键字synchronized

用synchronized修饰的方法称为同步方法,当多个线程访问同步方法时只能一个个访问,线程1访问完了才到线程2。
synchronized还可以修饰代码块,称为同步代码块

以客户去银行排号举例

package Thread;

public class demo {
    public static void main(String[] args) {
        Num num = new Num();
        User user = new User(num);
        user.start();
        Bank bank = new Bank(num);
        bank.start();
    }
}
class Bank extends Thread {
    private Num num;
    public Bank(Num num){
        this.num = num;
    }
    @Override
    public void run() {
        synchronized (num){
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("9点了,开始叫号");
            num.notifyAll();//取号机叫号
        }
    }
}
class User extends Thread {
    private Num num;
    public User(Num num){//num就是用于同步的对象
        this.num = num;
    }
    @Override
    public void run() {
        synchronized (num){
            try {
                Thread.sleep(5000);//给Bank线程启动的时间,结果还是先执行user线程
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("我是号码1,银行还没开门");
            try{
                num.wait();//取完号等待叫号
            /*wait和notifyAll都是Object对象的方法,所有对象都能用
            wait方法执行后要等待notify或者notifyAll来唤醒
            */
            }catch (InterruptedException e){
                throw new RuntimeException(e);
            }
            System.out.println("叫到我的号码了,到我办业务了");
        }
    }
}
class Num {//取号机

}


user和bank线程启动后都会执行run方法,它们的run方法都有同步的代码块,num是用于同步的对象
所以先启动的user线程先执行num的代码,执行完后才到bank线程执行它的num代码。

阻塞wait&sleep

线程的安全问题

package Thread;

public class demo {
    public static void main(String[] args) {
        User user = new User();
        for (int i = 0; i < 15; i++) {
            Thread t1 = new Thread(() -> {
                user.name = "zhangsan";
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t1"+user.name);
            });
            Thread t2 = new Thread(() -> {
                user.name = "lisi";
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("t2"+user.name);
            });

            t1.start();
            t2.start();
        }

    }
}
class User {
public String name;
}



循环10次目的在于看睡眠后哪个线程先启动,结果表明抢到cpu的先启动
发现结果大多数时候是lisi,极少数是zhangsan,可能赋值时也是看睡眠前谁先抢到cpu吧

线程一启动就会改变user的name,t1、t2都指向同一个对象,t1启动name赋值为zhangsan,
然后t1没来得及打印就休眠了。
紧接着t2启动,name的值被lisi覆盖,然后也没打印就休眠了。
1秒后t1和t2都打印name,可能是t1先打印的,也可能是t2。

线程的安全问题就是
多个线程在并发执行时,修改了共享内存中共享对象的属性,导致的数据冲突问题

posted @ 2023-02-23 16:58  ben10044  阅读(10)  评论(0编辑  收藏  举报