java多线程基础知识

1.进程和线程的区别:

进程 :进程是程序的一次执行过程,是一个正在执行的程序,一个程序可以同时执行多个任务(线程)。进程独占内存空间,保存各自的运行状态,互相之间不会干扰。是并发执行程序在执行过程资源分配和管理的基本单位(资源分配的最小单位)。每个进程都有自己的独立的地址空间,每启动一个进程,系统就会分配地址空间。在任务管理器中可以看到很多的进程。

线程:程序执行的最小单位,一个进程可以有多个线程,多个线程之间共享进程的地址空间及进程级别的其它资源,但各个线程拥有自己的栈空间。

进程与线程的使用场景区分: 如果在程序中需要频繁的创建和销毁就使用线程。因为进程创建和销毁的开销很大。如果程序需要更强的稳定性和安全性时,可以选择进程,追求速度就选择线程。

提高系统并发能力的方法:
垂直扩展

  • 提升单机的处理能力
  • 1)增强单机的硬件性能:增加CPU的核数、内存升级、磁盘扩容
  • 2)提升系统的架构能力:使用Cache来提高效率
  • · 水平扩展
  • 集群、分布式都是水平的扩展方案
  • 集群:多个人做同一事(同时多顾几个厨师同时炒菜)
  • 分布式:一个复杂的事情,拆分成几个简单的步骤,分别找不同的人去完成(1.洗菜 2.切菜 3.炒菜)
  • 1)站点层扩容:通过Nginx反向代理,实现高并发的系统,将服务部署在多个服务器上
  • 2)服务层扩容:通过RPC框架实现远程调用:Dubbo,Spring Clodud,将业务逻辑分拆成不同的RPC Client,
  • Clident完成各自的不同的业务,如果并发量比较大,新增加RPC Client
  • 3)数据层扩容:一台数据库拆分成多态,分库分表,主从复制,读写分离
2.并发和并行的区别:

并发: 多个线程操作一个资源,不是同时执行,很短的时间内可以交替执行多条程序命令,所以看起来像同时执行,单核cpu就可以做到。
并行: 在同一个时间多个任务同时进行,需要多核cpu才可以做到,每一个线程可以使用单独的cpu资源来运行。

3.创建线程的方法:
(1) 继承Thread类,重新run()方法,新线程类创建方法
class MyThread extends Thread{
    @Override
    public void run() {
        System.out.println("hello world");
    }
}
public class Test {
    public static void main(String[] args) {
        MyThread thread = new MyThread();//实例化线程对象
        thread.start();//启动对象
    }
}
(2)实现Runnable接口,实现接口中的run()方法,Thread类创建线程。
class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("implemnts Runnable to get a thread");
    }
}

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread(new MyRunnable());
        thread.start();
    }
}
(3) 匿名线程,通过匿名内部类实现:
public class Test {
    public static void main(String[] args) {
        new Thread("watchTV"){
            @Override
            public void run() {
                System.out.println("watch TV");
            }
        }.start();
  }
}
(4)实现Callable接口,重写call()方法:
/**
         * 通过Callable和FutureTask创建线程
         * a.创建Callable接口的实现类,同时实现call方法
         * b.创建Callable实现类的对象,使用FutureTask包装当前Callable对象,
         * FutureTask对象封装Callable对象中call方法的返回
         * c.使用FutureTask对象作为Thread的参数创建并且启动线程
         * d.调用FutureTask对象的get()来获取子线程执行的结果
         */
class MyCallable implements Callable<Integer> {
    @Override
    public Integer call() throws Exception {
        int sum = 0;
        for (int i = 0; i <= 10000; i++) {
            sum += i;
        }
        return sum;
    }
}
public class Test {
    public static void main(String[] args) {
        Callable<Integer> callable = new MyCallable();
        FutureTask<Integer> task = new FutureTask<>(callable);
        Thread thread = new Thread(task);
        thread.start();
        try {
            Integer result = task.get();
            System.out.println(result);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
Callable接口和Runnable接口的区别:

1. Callable接口实现的是call方法,Runnable接口实现的是run方法
2. Callable执行后有返回值,Runnable无返回值
3. call()方法会抛出异常,run方法不能抛出异常
4. Callable使用了Futuretask,可以拿到Callable异步计算的结果,通过Futuretask中的get方法可以获取当前的结果,并且可以通过get方法来检查当前任务是否已经完成。

4.守护线程:

JVM总没有一个非守护线程,JVM进程会退出
守护线程能够自动结束生命周期,非守护则不具备这一特点

非守护线程测试:
public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread("sleep") {
            @Override
            public void run() {
                try {
                    while (true) {
                        TimeUnit.SECONDS.sleep(1);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main thread fininshed");
    }
}

结果:
在这里插入图片描述
因为为非守护线程,所以子线程并没有自动结束。当把子线程改为守护线程后:

public class Test {
    public static void main(String[] args) {
        Thread thread = new Thread("sleep") {
            @Override
            public void run() {
                try {
                    while (true) {
                        TimeUnit.SECONDS.sleep(1);
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        thread.setDaemon(true);
        thread.start();
        try {
            TimeUnit.MILLISECONDS.sleep(1000);

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("main thread fininshed");
    }
}

结果:
在这里插入图片描述

5.线程的生命周期:
生命周期:
  1. NEW(新建状态):new Thread()创建线程对象
  2. RUNNABLE(就绪状态):线程对象此时调用start方法,此时在JVM进程中创建了一个线程,线程并不是一经创建就直接得到执行,需要等到操作系统的其他资源,比如:处理器
  3. BLOCKED(阻塞状态):等到一个监视器锁进入到同步代码块或者同步方法中,代码块/方法某一个时刻只允许一个线程去执行,其他线程只能等待,这种情况下等待的线程会从 RUNNABLE状态转换到BLOCKED状态 Objcet.wait()
  4. WAITING(等待状态):调用Object.wait()/join()/LockSupport.park()等方法,此时线程从RUNNABLE转换到WAITING状态
  5. TIMED_WAITING(睡眠状态):调用带超时参数的THread.sleep(long millis)/Object.wait(long timeout)/join(long milles)/LockSupport.parkNanos()/ LockSupport.parkUntil等方法都会使得当前线程进入到TIMED_WAITING状态
  6. TERMINATED(终止状态): 是线程的最终状态
    1.线程正常运行结束 2.线程运行出错 3.JVM crash
线程六状态间的转换图:

在这里插入图片描述

6.线程中的常用方法:
  1. start()
    用来启动一个线程,并且将该线程添加到一个线程组当中。此时线程处于Runnable就绪状态.
  2. sleep()
    Thread.sleep(),使得当前线程进入指定毫秒级的休眠,暂停执行当前线程,不会放弃monitor锁的使用权。jdk1.5之后引入了枚举类型TimeUnit,对sleep方法进行了封装,可以给定休眠的时间单位。sleep()是一种可中断方法。
public class Test2 {
    public static void main(String[] args) {
        new Thread(){
            @Override
            public void run() {
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }
}

  1. yield()
    属于启发式的方法,线程A.yield(),会提醒调度器线程A愿意放弃本次的cpu资源,如果cpu资源不紧张,cpu可能会忽略这种提醒。yield()是一种不可中断方法。
  2. join()
    在thread B中调用thread A.join()时,thread B进入到等待状态,直到thread A结束自己的生命周期或者达到join方法的超时时间。
public class Test2 {
    public static void main(String[] args) {
        Thread A = new Thread("A") {
            @Override
            public void run() {
                for (int i = 0; i < 5; i++) {
                    System.out.println("thread: " + Thread.currentThread().getName() + "," + i);
                }
                try {
                    TimeUnit.MILLISECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

            Thread B = new Thread("B") {
                @Override
                public void run() {
                    for (int i = 0; i < 5; i++) {
                        System.out.println("thread: " + Thread.currentThread().getName() + "," + i);
                    }
                    try {
                        TimeUnit.MILLISECONDS.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            };
        try {
            A.start();
            A.join();
            B.start();
            B.join();
            for (int i = 0; i < 5; i++) {
                System.out.println("thread: " + Thread.currentThread().getName() + "," + i);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

结果:
在这里插入图片描述
5. 实现线程中断的方法:
(1)interrupt() 将java线程当中的终端位置为true,thread A : sleep()/join()/wait throw InterruptedException 可中断方法,以上方法都会使得当前进入阻塞状态,另外一个线程调用被阻塞线程的interrupt方法会打断当前的这种阻塞状态,抛出一个InterruptedException的异常,这样的方法称之为可中断方法。 并不是结束当前被阻塞线程的生命周期,只是打断了当前线程的阻塞状态。

public class Test2 {
    public static void main(String[] args) {
        Thread A = new Thread("A") {
            @Override
            public void run() {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    
                    e.printStackTrace();
                }
            }
        };
        A.start();
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        A.interrupt();
    }
}

(2)isInterrupted() 判断中断状态位是否为true

public class Test2 {
    public static void main(String[] args) {
        Thread A = new Thread("A") {
            @Override
            public void run() {
                    while (true);
            }
        };
        A.start();
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(A.isInterrupted());
        A.interrupt();
        System.out.println(A.isInterrupted());
    }
}

(3)interrupted() 判断中断状态位是否为true,与isInterrupted的区别为调用之后会擦除掉线程的interrupt标识

public class Test2 {
    public static void main(String[] args) {
        Thread A = new Thread("A") {
            @Override
            public void run() {
                    while (true){
                        System.out.println(Thread.interrupted());
                    }
            }
        };
        A.setDaemon(true);
        A.start();
        try {
            TimeUnit.MILLISECONDS.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        A.interrupt();
    }
}
  1. wait/notify/notifyALL
    wait调用该synchornized同步代码块或方法当中,使得当前线程进入阻塞,notify/notifyAll唤醒当前的阻塞。均为Object类中的方法。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值