Java线程和进程

375 阅读4分钟

1、线程和进程的理解

1.1、什么是进程?什么是线程?

进程是一个应用程序(一个进程是一个软件),线程是一个进程中的执行场景/执行单元。一个进程可以启动多个线程。对于java程序来说,当在DOS命令窗口中输入:java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程。 JVM再启动一个主线程调用main方法。同时再启动一个垃圾回收线程负责看护,回收垃圾。最起码,现在的java程序中至少有两个线程并发,一个是垃圾回收线程,一个是执行main方法的主线程。

1.2、进程和线程是什么关系

进程可以看做是现实生活当中的公司。线程可以看做是公司当中的某个员工。注意:进程A和进程B的内存独立不共享。 线程A和线程B呢?在java语言中:线程A和线程B,堆内存和方法区内存共享。但是栈内存独立,一个线程一个栈。假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。java中之所以有多线程机制,目的就是为了提高程序的处理效率。

1.3、思考一个问题

使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束。main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

1.4、分析一个问题

对于单核的cPU来说,真的可以做到真正的多线程并发吗?对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU同一个时间点上,可以真正的有4个线程并发执行。什么是真正的多线程并发?t1线程执行t1的。t2线程执行t2的。t1不会影响t2。t2也不会影响t1。这叫做真正的多线程并发。 单核的cpu不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发"的感觉。 对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于 CPU的处理速度极快,多个线程之间频繁切换执行,给人的感觉是:多个事情同时在做。

1.5、线程对象的生命周期

  • 新建:采用 new 语句创建完成
  • 就绪:执行 start 后
  • 运行:占用 CPU 时间
  • 阻塞:执行了 wait 语句、执行了 sleep 语句和等待某个对象锁,等待输入的场合\
  • 终止:退出 run()方法

2、实现线程的四种方式

2.1、继承Thread类,重写run方法

public class Demo01Thread {
    public static void main(String[] args) {
        //这里是main方法,这里的代码属于主线程,在主线程中运行
        MyThread myThread = new MyThread();
        //启动线程
        //run方法不会启动线程
        //start方法可以启动一个线程,启动成功的线程会自动调用run方法
        myThread.start();
        for (int i=0;i<1000;i++){
            System.out.println("主线程:"+i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            System.out.println("分支线程:"+i);
        }
    }
}

2.2、实现Runnable接口

public class Demo02Thread {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunThread());
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程:" + i);
        }
    }
}

class MyRunThread implements Runnable{
    @Override
    public void run() {
        for(int i=0;i<1000;i++){
            System.out.println("分支线程:"+i);
        }
    }
}

2.3、匿名内部类

public class Demo02Thread {
    public static void main(String[] args) {
        Thread thread = new Thread(new Runnable() {
            @Override
            public void run() {
                for (int i = 0; i < 1000; i++) {
                    System.out.println("分支线程:" + i);
                }
            }
        });
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("主线程:" + i);
        }
    }
}

2.4、实现callable接口

这种方式实现的线程可以获取线程的返回值。 这种方式的优点:可以获取到线程的执行结果。 这种方式的缺点:在获取线程执行结果的时候,当前线程受阻塞,效率较低。

public class Demo03Thread {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FutureTask futureTask = new FutureTask(new Callable() {
            @Override
            public Object call() throws Exception {
                System.out.println("call method begin");
                Thread.sleep(1000*10);
                System.out.println("call method end");
                int a=10;
                int b=20;
                return a * b;
            }
        });
        Thread thread = new Thread(futureTask);
        thread.start();
        //get()方法的执行会导致"当前线程阻塞"
        Object o = futureTask.get();
        System.out.println("线程执行结果:"+o);
        //main方法这里要想执行必须等待get方法结束,get方法可能需要很久
        //因为get方法是拿另一个线程的执行结果,另一个线程执行是需要时间的
        System.out.println("heelo world");
    }
}

3、线程对象的获取

  • 获取当前线程对象,返回值t就是当前线程
Thread t= Thread.currentThread();
  • 获取线程对象的名字
String name=线程对象.getName();
  • 修改线程对象的名字
线程对象.setName(“线程名字”);
  • 当线程没有设置名字的时候,默认的名字规律
Thread-0
Thread-1
package com.chuxuezhe.thread;

public class Demo01Thread {
    public static void main(String[] args) {
        //currentThread就是主线程
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName());
        MyThread myThread = new MyThread();
        //设置线程的名字
        myThread.setName("tt");
        //获取线程的名字
        String myThreadName = myThread.getName();
        System.out.println(myThreadName);
        //启动线程
        myThread.start();

    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println("分支线程:"+i);
        }
    }
}

4、线程的睡眠

sleep 设置休眠的时间,单位毫秒,当一个线程遇到 sleep 的时候,就会睡眠,进入到阻塞状态,放弃 CPU,腾出 cpu 时间片,给其他线程用,所以在开发中通常我们会这样做,使其他的线程能够取得 CPU 时间片,当睡眠时间到达 了,线程会进入可运行状态,得到 CPU 时间片继续执行,如果线程在睡眠状态被中断了,将会抛出IterruptedException。

关于线程的sleep方法: sleep–进入阻塞状态:

static void sleep(long millis)
  • 1.静态方法: Thread.sleep(1000);
  • 2.参数是毫秒
  • 3.作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片.让给其它线程使用。
    • 这行代码出现在A线程中, A线程就会进入休眠。
    • 这行代码出现在B线程中, B线程就会进入休眠。
    1. Thread.sleep()方法,可以做到这种效果:
    • 间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。
public class Demo01Thread {
    public static void main(String[] args) throws InterruptedException {
        Thread myThread = new MyThread();
        //启动线程
        myThread.start();
        //问题:这行代码会让线程t进入休眠状态吗?
        //不会 sleep()方法是静态方法,实质上还是通过类名调用。
        myThread.sleep(1000*5);//在执行的时候还是会转换成:Thread.sleep(1000*5);
        //这行代码的作用是:让当前线程进入休眠,也就是main线程进入休眠
        System.out.println("hello world");
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<5;i++){
            System.out.println("分支线程:"+i);
        }
    }
}

5、线程的调度

5.1、常见的线程调度模型

  • 抢占式调度模型:
    • 哪个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
    • java采用的就是抢占式调度模型。
  • 均分式调度模型:
    • 平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
    • 平均分配,一切平等。

5.2、线程的优先级

public class Demo01Thread {
    public static void main(String[] args) throws InterruptedException {
        //设置主线程的优先级为1
        Thread.currentThread().setPriority(1);
        System.out.println("最高优先级"+Thread.MAX_PRIORITY);
        System.out.println("最低优先级"+Thread.MIN_PRIORITY);
        System.out.println("默认优先级"+Thread.NORM_PRIORITY);
        Thread currentThread = Thread.currentThread();
        System.out.println(currentThread.getName()+"线程的优先级是:"+currentThread.getPriority());

        Thread t = new MyThread();
        t.setPriority(10);
        t.setName("aa");
        t.start();
        //优先级较高的,只是捡到的CPU时间片相对多一些。
        for (int i=0;i<100;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<100;i++){
            System.out.println("分支线程:"+i);
        }
    }
}

5.3、线程让位

yield 与 sleep()类似,只是不能由用户指定暂停多长时间,并且 yield()方法只能让同优先级的线程有执行的机会,采用 yieid 可以将 CPU 的使用权让给同一个优先级的线程,当前线程暂停,回到就绪状态。

public class Demo01Thread {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.setName("aa");
        t.start();
        for (int i=0;i<10000;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<10000;i++){
            //每100个让位一次
            if(i%100==0){
                Thread.yield();//当前线程暂停一下,让给主线程
            }
            System.out.println("分支线程:"+i);
        }
    }
}

5.4、合并线程

当前线程可以调用另一个线程的 join 方法,调用后当前线程会被阻塞不再执行,直到被调用的线程执行完毕,当前线程才会执行。

public class Demo01Thread {
    public static void main(String[] args) throws InterruptedException {
        Thread t = new MyThread();
        t.setName("aa");
        t.start();
        //合并线程
        t.join();///t合并到当前线程当中,当前线程受阻塞,t线程执行直到结束
        for (int i=0;i<10000;i++){
            System.out.println(Thread.currentThread().getName()+"--->"+i);
        }
    }
}

class MyThread extends Thread{
    @Override
    public void run() {
        for(int i=0;i<10000;i++){
            //每100个让位一次
            if(i%100==0){
                Thread.yield();//当前线程暂停一下,让给主线程
            }
            System.out.println("分支线程:"+i);
        }
    }
}

6、线程同步

线程同步,指某一个时刻,只允许一个线程来访问共享资源,线程同步其实是对对象加锁,如果对象中的方法都是同步方法,那么某一时刻只能执行一个方法,采用线程同步解决以上的问题,我们只要保证线程一操作 s 时,线程 2 不允许操作即可,只有线程一使用完成 s 后,再让线程二来使用 s 变量。

  • 异步编程模型 : t1线程执行t1的,t2线程执行t2的,两个线程之间谁也不等谁。
  • 同步编程模型 : t1线程和t2线程执行,t2线程必须等t1线程执行结束之后,t2线程才能执行,这是同步编程模型。 什么时候要用同步呢?为什么要引入线程同步呢?
  • 1.为了数据的安全,尽管应用程序的使用率降低,但是为了保证数据是安全的,必须加入线程同步机制。
    • 线程同步机制使程序变成了(等同)单线程.
  • 2.什么条件下要使用线程同步?
    • 第一: 必须是多线程环境
    • 第二: 多线程环境共享同一个数据。
    • 第三: 共享的数据涉及到修改操作。
//synchronized 是对对象加锁 
//采用 synchronized 同步最好只同步有线程安全的代码 
//可以优先考虑使用 synchronized 同步块 
//因为同步的代码越多,执行的时间就会越长,其他线程等待的时间就会越长 
//影响效率
public class SynchronizedTest {
    public static void main(String[] args) {
        SynchronizeTest1();
    }

    private static void SynchronizeTest1() {
        Account account=new Account("Actno-001",5000.0);
        Thread t1=new Thread(new Processor(account));
        Thread t2=new Thread(new Processor(account));
        t1.start();
        t2.start();
    }

}
/**
 * 取款线程
 */
class Processor implements Runnable{
    Account act;
    Processor(Account act){
        this.act=act;
    }
    @Override
    public void run() {
        act.withdraw(1000.0);
        System.out.println("取款1000.0成功,余额: "+act.getBalance());
    }

}
class Account {

    private String actno;
    private double balance;

    public Account() {
        super();
    }

    public Account(String actno, double balance) {
        super();
        this.actno = actno;
        this.balance = balance;
    }

    public String getActno() {
        return actno;
    }

    public void setActno(String actno) {
        this.actno = actno;
    }

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }

    /**
     * 对外提供一个取款的方法 对当前账户进行取款操作
     */
    public void withdraw(double money) {
        //把需要同步的代码,放到同步语句块中.
        //遇到synchronized就找锁,找到就执行,找不到就等
        /**
         * 原理: t1线程和t2线程
         * t1线程执行到此处,遇到了synchronized关键字,就会去找this的对象锁,
         * 如果找到this对象锁,则进入同步语句块中执行程序,当同步语句块中的代码执行结束之后,
         * t1线程归还this的对象锁.
         * 
         * 在t1线程执行同步语句块的过程中,如果t2线程也过来执行以下代码,也遇到synchronized关键字,
         * 所以也去找this对象锁,但是该对象锁被t1线程持有,只能在这等待this对象的归还.
         * 
         * synchronized关键字添加到成员方法上,线程拿走的也是this的对象锁.
         * 
         */
        synchronized (this) {
            double after = balance - money;
            try {
                //延迟
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //更新
            this.setBalance(after);
        }
    }
}
public class SynchronizedTest2 {
    public static void main(String[] args) throws InterruptedException {
        MyClass mc1=new MyClass();
        MyClass mc2=new MyClass();
        Thread t1=new Thread(new Runnable1(mc1));
        Thread t2=new Thread(new Runnable1(mc2));
        t1.setName("t1");
        t2.setName("t2");

        t1.start();
        //延迟,保证t1先执行
        Thread.sleep(1000);
        t2.start();
    }
}
class Runnable1 implements Runnable{
    MyClass mc;
    Runnable1(MyClass mc){
        this.mc=mc;
    }
    @Override
    public void run() {
        if("t1".equals(Thread.currentThread().getName())){
            MyClass.m1();//因为是静态方法,用的还是类锁,和对象锁无关
        }
        if("t2".equals(Thread.currentThread().getName())){
            MyClass.m2();
        }
    }
}
class MyClass{
    //synchronized添加到静态方法上,线程执行此方法的时候会找类锁,类锁只有一把
    public synchronized static void m1(){
        try {
            Thread.sleep(10000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("m1()............");
    }
    /**
     * m2方法等m1结束之后才能执行,该方法有synchronized
     * 线程执行该方法需要"类锁",而类锁只有一个.
     */
    public synchronized static void m2(){
        System.out.println("m2()........");
    }
}

7、守护进程

从线程分类上可以分为:用户线程(以上讲的都是用户线程),另一个是守护线程。守护线程是这样的,所有的用户线程结束生命周期,守护线程才会结束生命周期,只要有一个用户线程存在,那么守护线程就不会结束,例如 java 中著名的垃圾回收器就是一个守护线程,只有应用程序中所有的线程结束,它才会结束。

  • 其它所有的用户线程结束,则守护线程退出!
  • 守护线程一般都是无限执行的。
public class DaemonThread {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(new Runnable2());
        t1.setName("t1");
        // 将t1这个用户线程修改成守护线程.在线程没有启动时可以修改以下参数
        t1.setDaemon(true);
        t1.start();
        // 主线程
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread().getName() + "----->" + i);
            Thread.sleep(1000);
        }
    }
}

class Runnable2 implements Runnable {

    @Override
    public void run() {
        int i = 0;
        while (true) {
            i++;
            System.out.println(Thread.currentThread().getName() + "-------->" + i);
            try {
                Thread.sleep(500);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

设置为守护线程后,当主线程结束后,守护线程并没有把所有的数据输出完就结束了,也即是说守护线程是为用户线程服务的,当用户线程全部结束,守护线程会自动结束。

8、定时器

/**
 * 关于定时器的应用 作用: 每隔一段固定的时间执行一段代码
 */
public class TimerTest {

    public static void main(String[] args) throws ParseException {
        // 1.创建定时器
        Timer t = new Timer();
        // 2.指定定时任务
        t.schedule(new LogTimerTask(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").parse("2017-06-29 14:24:00 000"), 10 * 1000);
    }
}

// 指定任务
class LogTimerTask extends TimerTask {

    @Override
    public void run() {
        System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS").format(new Date()));
    }
}