精华内容
下载资源
问答
  • Java多线程知识点总结

    2021-05-11 20:19:56
    JAVA多线程Java多线程知识点总结(1)什么是进程?什么是线程?(2)多线程的运行状态(3)线程的创建和使用(4)Runnable 接口实现多线程(50)Callable接口实现多线程 Java多线程知识点总结 (1)什么是进程?什么是...

    Java多线程知识点总结

    (1)什么是进程?什么是线程?

    进程:

    是并发执行程序在执行过程中分配和管理资源的基本单位,当程序进入内存圆形时即为线程

    进程三大特点:

    1. 独立性: 进程是系统中独立存在的实体,它可以独立拥有资源,每个进程都有自己的独立空间。
    2. 动态性: 进程和程序的区别在于进程动态的,进程具有自己的生命周期
    3. 并发性:多个进程可以在单个处理器上并发执行互不影响

    并行和并发:

    1. 并行是指同一时刻,多个命令同时执行;
    2. 并发是指在同一时刻,只有一条命令是在处理器上执行的,但多个进程命令被快速轮换执行,使得在宏观上具有多个进程同时执行的效果。

    线程:

    1. 线程(Thread)被称为轻量级进程,线程是进程的执行单元
    2. 线程是进程的组成部分,一个进程可以拥有多个线程,而一个线程必须拥有一个父进程。线程可以拥有堆栈,局部变量…但不能拥有系统资源,它与父进程的其他线程共享该进程的所有资源
    3. 一个线程可与其父进程的其他线程共同拥有父进程共享变量和部分环境,共同非合作完成任务。
    4. 可以简便理解一个Class类执行时就是一个JVM的进程,其中的成员属性是子线程共享资源,main方法是主线程,其他方法是子线程,子线程(方法)相互合作共同完成Class任务。
    5. 线程是独立运行的,线程的执行是抢占式的,也就是说执行的线程可能被挂起,以便运行另一个线程。
    6. 一个进程可以创建或者撤销另一个线程,一个进程中的线程是并发执行的。

    (2)多线程的运行状态

    (1)线程一般具有5种基本状态 :

         创建, 就绪, 运行, 阻塞, 终止
    

    在这里插入图片描述
    (2)

    1. 创建状态:在程序中

    (3)线程的创建和使用

    (1)线程的创建

    1. 在Java 中如果要想实现多线程,那么必须依靠线程的主题类,但这个线程主题类在定义的时候,也需要一类特殊的要求,这个类可以继承 Thread ,实现Runnable 接口或者实现 Callable 接口来完成定义。
    2. 任何一个类只要继承了 Thread 类就可以成为一个类的主类,同时线程中需要覆写父类终点 run( )方法。

    (2)线程的使用

    public class JavaDemoA {
        public static void main(String[] args) {
            new MyThread("线程A:").start();
            new MyThread("线程B:").start();
            new MyThread("线程C:").start();
        }
    }
    class MyThread extends Thread{ //线程的主体类
        private String title;
    
        public MyThread(String title){
            this.title=title;
        }
        @Override
        public void run() {
            for(int i=1;i<=4;i++){
                System.out.println(this.title+i);
            }
        }
    }
    
    

    (4)Runnable 接口实现多线程

    1. ThreadRunnable 的子类,继承了 Runnable 的接口,Thread 类的确可以方便实现多线程,但这种方式最大的缺陷就是单继承局限性
    2. Runnable 接口从 JDK1.8 开始成为一个函数接口,可以直接利用lambda表达式来实现线程主体代码,同时在该接口中提供有run()方法进行线程功能定义
    3. Thread构造方法:public Thread(Runnable target).
      在Thread类中会保存有target属性,该属性保存的是Runnable的核心业务主体对象
      Thread.start() 方法启动多线程时也会调用Thread.run() 方法,而在 Thread.run() 会判断是否提供有target实例,如果提供则调用真实主体
    public class JavaDemoB {
        public static void main(String[] args) {
            new Thread(new MyThreadB("线程A:")).start();
            new Thread(new MyThreadB("线程B:")).start();
            new Thread(new MyThreadB("线程C:")).start();
        }
    }
    class MyThreadB implements Runnable{
        private String title;
        public MyThreadB(String title){
            this.title=title;
        }
        @Override
        public void run() {
            for(int i=1;i<=5;i++){
                System.out.println(title+i);
      加粗样式      }
        }
    }
    
    

    (5)Callable接口实现多线程

    1. Runnable 接口实例化多线程可以避免单继承问题,但 Runnable 中的run() 方法不能返回操作结果,为啦解决这样的问题,从JDK1.5开始对于多线程的实现提供了一个新的接口 java.util.concurrent.Callable
    2. Callable接口在定义时需要定义泛型。
    3. 通过FutureTask 实现Callable接口和Thread类的联系,并且通过FutureTask类获取Callable接口中call()方法的返回值。
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.FutureTask;
    public class JavaDemoC {
        public static void main(String[] args) throws Exception{
            FutureTask<String> task=new FutureTask<>(new MyThreadC());
            new Thread(task).start();
            System.out.println(task.get());
        }
    }
    class MyThreadC implements Callable<String> {
        @Override
        public String call() throws Exception {
            for(int i=1;i<=5;i++){
                System.out.println("线程执行:i="+i);
            }
            return "Callable实现多线程";
        }
    }
    
    

    多线程常用操作方法

    (1)线程的命名和取得

    1. public Thread (Runnable target,String name) 构造实例化线程对象,接受 Runnable 接口子类对象,同时设置线程名称;
    2. public final void setName(String name) 普通 设置线程名称
    3. public final String getName() 取得线程名字
    4. 每当实例化Thread类对象时,都会调用init()方法,并且在没有为线程命名时,自动命名。
    
    package ThreadTest;
    public class JavaDemoD {
        public static void main(String[] args) {
            MyThreadD myThread=new MyThreadD();
            new Thread(myThread,"线程A").start();//线程手动命名
            new Thread(myThread).start();//线程自动命名
            new Thread(myThread,"线程B").start();
        }
    }
    class MyThreadD implements Runnable{
        @Override
        public void run() {
            //获取当前线程名称
            System.out.println(Thread.currentThread().getName());
        }
    }
    
    

    (2)线程休眠

    1. 线程有时候需要减缓执行速度,所以Thread 类提供啦sleep()方法
    2. public static void sleep(long millis) throws InterruptedException设置线程毫秒数,时间一到自动唤醒
      public static void sleep(long millis,int nanos) throws InterruptedException设置线程毫秒数,纳秒数,时间一到自动唤醒
    
    public class JavaDemoE {
        public static void main(String[] args){
            Runnable runnable=(()->{//Runnable接口实例
               for(int i=1;i<=5;i++){
                   System.out.println(Thread.currentThread().getName()+i);
                   try {
                       Thread.sleep(2000);//强制让线程休眠2秒
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
            });
            new Thread(runnable,"线程A:").start();
            new Thread(runnable,"线程B:").start();
        }
        //本程序两个线程对象,每一个线程对象执行时都要休眠一秒,因为多线程的启动和执行都是有操作系统,
        //随机分配,虽然看起来A,B线程同时休眠,但也有先后顺序
    }
    
    

    (3)线程中断

    1. Thread 中提供的线程方法很多都会抛出InterruptedException中断异常,所以线程在执行过程中可以被另一个线程中断。
    2. public boolean isInterrupted( ) 普通方法 判断线程是否被中断
      public void interrupt( ) 普通 中断线程执行
    
    public class JavaDemoF {
        public static void main(String[] args) throws InterruptedException {
            Thread thread=new Thread(()->{
                System.out.println("线程累了想休眠10S");
                try {
                    Thread.sleep(10000);
                    System.out.println("时间到,自然醒来");
                } catch (InterruptedException e) {
                    System.out.println("被强制唤醒,继续工作");
                }
            });
            thread.start();//线程执行启动
            Thread.sleep(2000);//先让子线程运行2秒
            if(!thread.isInterrupted()){//如果线程没有中断
                System.out.println("让线程终止休眠");
                thread.interrupt();//线程中断
            }
        }
    }
    
    

    (4)线程强制执行

    1. 在多线程并发执行中每个线程对象都会交替执行,如果某个线程对象需要优先完成执行,可以使用 join ()方法强制执行,待其执行完毕后其他线程才会继续执行
    2. public final void join() throws InterruptedException
    
    public class JavaDemoG {
        public static void main(String[] args) {
            Thread threadB=new Thread(new MyThreadH(),"线程B");
            Runnable runnable=(()->{
               for(int i=1;i<=9;i++){
                   System.out.println(Thread.currentThread().getName()+i);
                   if(i==5){
                       try {
                           threadB.join();//如果i=5,强制执行线程B
                       } catch (InterruptedException e) {
                           e.printStackTrace();
                       }
                   }
               }
            });
            new Thread(runnable,"线程A").start();
            threadB.start();
        }
    
    }
    class MyThreadH implements Runnable{
    
        @Override
        public void run() {
            for(int i=1;i<=5;i++){
                System.out.println(Thread.currentThread().getName()+i);
            }
        }
    }
    
    

    (5)线程礼让

    1. 多线程在彼此交替执行时往往需要进行资源轮流占用,如果某些不是很重要的线程抢占到资源但又不着急执行时就可以暂时礼让出去,让其他线程先执行。
    2. public static void yield( ) 线程礼让
    
    public class JavaDemoH {
        public static void main(String[] args) {
            Runnable runnable=(()->{//Lambda实例化线程类对象,方便演示
                for(int j=1;j<=15;j++){
                    System.out.println(Thread.currentThread().getName()+j);
                }
            });
            new Thread(new MyThreadI(),"礼让线程A").start();
            new Thread(runnable,"非礼让线程B:").start();
            new Thread(runnable,"非礼让线程C:").start();
            new Thread(runnable,"非礼让线程D:").start();
        }
    }
    
    class MyThreadI implements Runnable{
        @Override
        public void run() {
            for(int i=1;i<=10;i++){
                System.out.println(Thread.currentThread().getName()+i);
                if(i%3==0){
                    Thread.yield();//线程礼让
                }
            }
        }
    }
    
    

    (6)线程优先级

    1. 在java线程操作时,所有线程运行前都保持就绪状态,此时那个线程的优先级高就有可能优先被执行
    2. 所有创建的线程都是子线程,启动时都是同样的优先级。
    3. 主线程是中等优先级 5
    4. public final void setPriority ( int newPriority ) 设置线程优先级
      MAX_PRIORITY 最高优先级 10
      NORM_PRIORITY 中等优先级 5
      MIN_PRIORITY 最低优先级 1
    5. public final int getPriority ( ) 获取线程优先级
    
    public class JavaDemoI {
        public static void main(String[] args) {
            Runnable runnable=(()->{
                for (int i=1;i<=5;i++){
                    System.out.println(Thread.currentThread().getName()+i);
                }
            });
            Thread threadA=new Thread(runnable,"线程A");
            Thread threadB=new Thread(runnable,"线程B");
            Thread threadC=new Thread(runnable,"线程C");
            threadA.setPriority(Thread.MIN_PRIORITY);
            threadB.setPriority(Thread.NORM_PRIORITY);
            threadC.setPriority(Thread.MAX_PRIORITY);
            threadA.start();
            threadB.start();
            threadC.start();
        }
    }
    
    

    (7)如何停止线程

    (1)停止线程的3个方法(了解

    1. 停止多线:public void stop ()
    2. 挂起多线程::public final void suspend()
    3. 恢复挂起的多线程:public final void resume()

    (2)对多线程中 stop (), suspend(), resume() 方法在jdk1.2开始不建议使用,主要是因为这三个方法在操作时会产生死锁的问题。

    (3)优雅的停止一个线程

    代码演示:

    
    package ThreadTest;
    
    public class JavaDemoO {
        private static boolean flag=true;
        public static void main(String[] args) throws InterruptedException {
            new Thread(()->{
                long num=0;
                while (flag){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"正在运行"+num++);
                }
            },"执行线程").start();
            Thread.sleep(200);
            flag=false;
        }
    }
    
    

    (8)后台守护线程

    (1)java中的线程分为两类:

    1. 用户线程和守护线程,守护线程:是一种运行在后台的线程服务线程,当用户线程存在时,守护线程也可以用时存在;当用户线程消失时,守护线程也会消失。
    2. public final void setDaemon(boolean on) 设置为守护线程
    3. public final boolean idDaemon() 判断是否是守护线程

    代码:

    package ThreadTest;
    
    public class JavaDemoP {
        public static void main(String[] args) {
            Thread threadA=new Thread(()->{
                for(int i=1;i<=100;i++){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+i);
                }
            },"用户线程");
            Thread threadB=new Thread(()->{
                for(int i=1;i<=200;i++){
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+i);
                }
            },"守护线程");
    
            threadB.setDaemon(true);//设置为守护线程
            threadA.start();
            threadB.start();
        }
    }
    
    

    结果:用户线程结束时,守护线程也结束
    在这里插入图片描述

    (9)volatile 关键字

    1. 在多线程编程中,若干个线程为了可以实现公共资源的操作,往往是复制相应变量的副本,待完成操作后再将副本变量的数据与原始数据进行同步处理,如果开发者不希望通过副本数据进行操作,而是希望可以直接通过原始变量的操作(节省了复制变量副本与同步的时间),则可以在变量声明时使用volatile关键字.

    使用volatile关键字
    代码:

    
    package ThreadTest;
    
    public class JavaDemoQ {
        public static void main(String[] args) {
            new Thread(new MyThreadQ(),"线程A售票成功_剩余票数:").start();
            new Thread(new MyThreadQ(),"线程B售票成功_剩余票数:").start();
            new Thread(new MyThreadQ(),"线程C售票成功_剩余票数:").start();
            new Thread(new MyThreadQ(),"线程D售票成功_剩余票数:").start();
        }
    }
    class MyThreadQ implements Runnable{
    
        private volatile int ticket=50;//直接内存操作
        @Override
        public void run() {
            while (ticket>0){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                ticket--;
                System.out.println(Thread.currentThread().getName()+ticket);
            }
        }
    }
    
    

    结果:
    在这里插入图片描述
    volatile与synchronized的区别:

    1. volatile无法进行同步处理操作,它只是一种直接内存的处理,避免副本操作
    2. volatile主要是在属性上使用,而synchronize是在方法和代码块上使用。

    线程的同步和死锁

    (1)线程同步问题

    (1) 同步问题的引出

    
    public class JavaDemoJ {
        private static int ticket=5;//总票数5张
        public static void main(String[] args) {
            Runnable runnable=(()->{
                while (true){//持续卖票
                    if(ticket>0){
                        try {
                            Thread.sleep(100);//模拟网络延迟
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+"卖票成功\t剩余票数:"+ticket--);
                    }
                    if(ticket<=0){
                        System.out.println(Thread.currentThread().getName()+"卖票失败\t剩余票数:"+ticket);
                        break;
                    }
                }
            }) ;
            new Thread(runnable,"售票员A:").start();
            new Thread(runnable,"售票员B:").start();
            new Thread(runnable,"售票员C:").start();
            //假设此时还有一张票,当第一个线程满足售票条件时(还未减少票数),其他线程也可能满足售票条件,
            //就有可能造成同一张票卖出去两次。
        }
    }
    
    

    问题所在:

    1. 假设此时还有一张票,当第一个线程满足售票条件时(还未减少票数),其他线程也可能满足售票条件就有可能造成同一张票卖出去两次。
      在这里插入图片描述

    (2) 解决同步问题

    1. Java使用Synchronized关键字实现同步操作,同步的关键是要给代码上“锁”,而对于锁的操作有两种:同步代码块,同步方法。

    (1)同步代码块

    1. 同步代码块是指使用synchronized关键字定义的代码块,在代码执行时,往往需要设置一个同步对象,由于线程操作的不确定性所以这时候的同步对象可以选择 this。
    2. 将票数判断和票数自减放在同一代码块中,当多线程并发执行时,只允许一个线程执行该部分代码块,就实现啦同步处理操作、
    
    public class JavaDemoK {
        public static void main(String[] args) {
            MyThreadK myThreadK=new MyThreadK();
            new Thread(myThreadK,"线程A售票成功,剩余票数:").start();
            new Thread(myThreadK,"线程B售票成功,剩余票数:").start();
            new Thread(myThreadK,"线程C售票成功,剩余票数:").start();
            new Thread(myThreadK,"线程D售票成功,剩余票数:").start();
        }
    }
    class MyThreadK implements Runnable{
        private static int ticket=10;//总票数
        @Override
        public void run() {
            while (true){
                synchronized (this){//同步代码块
                    if(ticket>0){
                        try {
                            Thread.sleep(100);//模拟网络延迟
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                        System.out.println(Thread.currentThread().getName()+(--ticket));
                    }else {
                        System.out.println("全部售空");
                        break;
                    }
                }
            }
        }
    
    }
    
    

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

    (2)同步方法解决

    
    public class JavaDemoL {
        public static void main(String[] args) {
            MyThreadL myThreadL=new MyThreadL();
            new Thread(myThreadL,"售票员A售票成功,剩余票数:").start();
            new Thread(myThreadL,"售票员B售票成功,剩余票数:").start();
            new Thread(myThreadL,"售票员C售票成功,剩余票数:").start();
            new Thread(myThreadL,"售票员D售票成功,剩余票数:").start();
        }
    }
    class MyThreadL implements Runnable{
        private static int ticket=10;
        @Override
        public void run() {
            sale();
        }
        public synchronized void sale() {//同步方法
            while (true){
            if(this.ticket>0){
                try {
                    Thread.sleep(100);//网络延迟
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.ticket--;
                System.out.println(Thread.currentThread().getName()+this.ticket);
            } else {
                System.out.println("票已售空");
                break;
            }
        }
    
        }
    
    }
    
    

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

    (2)线程死锁问题

    (1)死锁问题的引出

    代码:

    
    package ThreadTest;
    public class JavaDemoJ implements Runnable{
        private book book=new book();
        private money money=new money();
        public JavaDemoJ(){
            new Thread(this).start();
            book.tell(money);
        }
        public void run(){
            money.tell(book);
        }
    
        public static void main(String[] args) {
            new JavaDemoJ();
        }
    }
    
    class book{
        public synchronized void tell(money money){
            System.out.println("你先给我钱,我才给你书!");
            money.get();
        }
        public synchronized void get(){
            System.out.println("收到钱,把书给买家!");
        }
    }
    class money{
        public synchronized void tell(book book){
            System.out.println("你先给我书,我再给你钱!");
            book.get();
        }
        public synchronized void get(){
            System.out.println("收到书,把钱给卖家!");
        }
    }
    
    

    结果:
    在这里插入图片描述
    结论:本程序中采用大量同步处理操作,而死锁一旦出现线程将进入等待操作,并且不会向下继续执行。

    (3)生产者消费者问题

    (1)生产者生产,消费者取出,生产者生产一个,消费者取出一个

    未同步代码:

    
    package ThreadTest;
    
    public class JavaDemoM {
        public static void main(String[] args) {
            Message message=new Message();
            new Thread(new Producer(message)).start();
            new Thread(new Consumer(message)).start();
        }
    }
    class Message{//消息类
        private String title;//标题
        private String content;//内容
    
        public String getTitle() {
            return title;
        }
    
        public void setTitle(String title) {
            this.title = title;
        }
    
        public String getContent() {
            return content;
        }
    
        public void setContent(String content) {
            this.content = content;
        }
    }
    class Producer implements Runnable{//定义生产者
        private Message msg=null;
        public Producer(Message msg){
            this.msg=msg;
        }
        @Override
        public void run() {
            for(int i=1;i<=50;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i%2==0){
                    msg.setTitle(i+"英雄");
                    msg.setContent("名垂千古");
                }else {
                    msg.setTitle(i+"小人");
                    msg.setContent("遗臭万年");
                }
            }
        }
    
    }
    class Consumer implements Runnable{
        private Message msg;
        public Consumer(Message msg){
            this.msg=msg;
        }
        @Override
        public void run() {
            for (int i=1;i<=50;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("【"+msg.getTitle()+","+msg.getContent()+"】");
            }
        }
    }
    
    

    错误产生结果:
    在这里插入图片描述
    问题分析:

    1. 数据错位:假设生产者线程还刚在存储空间添加一个数据的标题,还未添加内容,程序就切换到消费者线程,消费者就会把没有生产者没有添加的内容和上一组生产的内容联系在一起导致数据错位。
    2. 重复操作:生产者线程放入多组数据,消费者线程才开始取出,或者是消费者还没生产数据,消费者就已经重复取出数据。

    同步代码解决:

    
    package ThreadTest;
    
    public class JavaDemoN {
        public static void main(String[] args) {
            Message2 msg=new Message2();
            new Thread(new Producer2(msg)).start();
            new Thread(new Consumer2(msg)).start();
        }
    }
    class Message2 {
        private String title;
        private String content;
        private boolean key=true;
        //true 允许生产不允许消费
        //false 允许消费不允许生产
    
        public synchronized void set(String title,String content) {//同步方法
            if(this.key==false){//允许消费不允许生产
                try {
                    super.wait();//线程等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            this.title = title;
            this.content = content;
            this.key=false;//生产完毕
            super.notify();//唤醒线程
        }
    
        public synchronized String get() {
            if(this.key==true){//允许生产不允许消费
                try {
                    super.wait();//线程等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
           try {
               return title +","+ content;
           }finally {
               this.key=true;
               super.notify();
           }
        }
    }
    class Producer2 implements Runnable{//定义生产者
        private Message2 msg=null;
        public Producer2(Message2 msg){
            this.msg=msg;
        }
        @Override
        public void run() {
            for(int i=1;i<=50;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if(i%2==0){
                    this.msg.set(i+"英雄","万古流芳");
                }else {
                    this.msg.set(i+"小人","遗臭万年");
                }
            }
        }
    
    }
    class Consumer2 implements Runnable{
        private Message2 msg;
        public Consumer2(Message2 msg){
            this.msg=msg;
        }
        @Override
        public void run() {
            for (int i=1;i<=50;i++){
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("【"+msg.get()+"】");
            }
        }
    }
    
    

    正确执行结果:
    在这里插入图片描述
    原理解释:

    1. 将同步操作设置在Message类中,使用synchronized关键字可以使消息内容同步,不会造成消息错位。
    2. 在生产者和消费者线程中根据key判断,是否可以生产或者取出,如果不能生产或者取出,可使用 wait( ) 线程等待方法 等待另一方线程取出或者生产后,使用 notify( ) 线程唤醒,继续执行操作。notifyAll( ) 是唤醒全部等待的线程
    展开全文
  • java多线程知识点总结

    2020-04-09 00:05:21
    java多线程知识点总结 什么是进程?  一个程序运行起来时在内存中开辟一段空间用来运行程序,这段空间包括heap、stack、data segment和code segment。例如,开一个QQ就表明开了一个QQ进程。 什么是线程?  每...

    java多线程知识点总结


    什么是进程?


     一个程序运行起来时在内存中开辟一段空间用来运行程序,这段空间包括heap、stack、data segment和code segment。例如,开一个QQ就表明开了一个QQ进程。

    什么是线程?


     每一个进程中都至少有一个线程。线程是指程序中代码运行时的运行路径,一个线程表示一条路径。例如QQ进程中,发送消息、接收消息、接收文件、发送文件等各种独立的功能都需要一个线程来执行。

     世间万物都可以同时完成很多工作。例如,人体可以同时进行呼吸、血液循环、思考问题等活动。用户既可以使用计算机听歌,也可以编写文档和发送邮件,而这些活动的完成可以同时进行。这种同时执行多个操作的“思想”在 Java 中被称为并发,而将并发完成的每一件事称为线程。

     在 Java 中,并发机制非常重要,但并不是所有程序语言都支持线程。在以往的程序中,多以一个任务完成以后再进行下一个任务的模式进行,这样下一个任务的开始必须等待前一个任务的结束。Java 语言提供了并发机制,允许开发人员在程序中执行多个线程,每个线程完成一个功能,并与其他线程并发执行。这种机制被称为多线程。
     多线程是非常复杂的机制,比如同时阅读 3 本书。首先阅读第 1 本第 1 章,然后再阅读第 2 本第 1 章,再阅读第 3 本第 1 章,接着回过头阅读第 1 本第 2 章,以此类推,就体现了多线程的复杂性。

     既然多线程这么复杂,那么它在操作系统中是怎样工作的呢?其实,Java 中的多线程在每个操作系统中的运行方式也存在差异,在此以 Windows 操作系统为例介绍其运行模式。

     Windows 系统是多任务操作系统,它以进程为单位。一个进程是一个包含有自身地址的程序,每个独立执行的程序都称为进程,也就是正在执行的程序。图 1 所示为 Windows 7 系统下使用任务管理器查看进程的结果。
    在这里插入图片描述
     系统可以分配给每个进程一段有限的执行 CPU 的时间(也称为 CPU 时间片),CPU 在这段时间中执行某个进程,然后下一个时间段又跳到另一个进程中去执行。由于 CPU 切换的速度非常快,给使用者的感受就是这些任务似乎在同时运行,所以使用多线程技术后,可以在同一时间内运行更多不同种类的任务。

     图 2 的左图是单线程环境下任务 1 和任务 2 的执行模式。任务 1 和任务 2 是两个完全独立、互不相关的任务,任务 1 是在等待远程服务器返回数据,以便进行后期的处理,这时 CPU 一直处于等待状态,一直在“空运行”。如果任务 2 是在 5 秒之后被运行,虽然执行任务 2 用的时间非常短,仅仅是 1 秒,但必须在任务1运行结束后才可以运行任务 2。由于运行在单任务环境中,所以任务 2 有非常长的等待时间,系统运行效率大幅降低。

     单任务的特点就是排队执行,也就是同步,就像在 cmd 中输入一条命令后,必须等待这条命令执行完才可以执行下一条命令一样。这就是单任务环境的缺点,即 CPU 利用率大幅降低。

    图2 单线程和多线程执行模式

     右侧则是多线程环境下的执行模式。从中可以发现,CPU 完全可以在任务 1 和任务 2 之间来回切换,使任务 2 不必等到 5 秒再运行,系统的运行效率大大得到提升。这就是要使用多线程技术、要学习多线程的原因。

     那么什么是线程呢?线程可以理解成是在进程中独立运行的子任务。比如,QQ.exe 运行时就有很多的子任务在同时运行。像好友视频、下载文件、传输数据、发送表情等,这些不同的任务或者说功能都可以同时运行,其中每一项任务完全可以理解成是“线程”在工作,传文件、听音乐、发送图片表情等功能都有对应的线程在后台默默地运行。

    以上知识点摘抄自 Java线程的概念:什么是线程?
    http://c.biancheng.net/view/1157.html


    进程和线程的区别:


     从资源的角度来考虑,进程主要考虑的是CPU和内存,而线程主要考虑的是CPU的调度,某进程中的各线程之间可以共享这个进程的很多资源。
     从粒度粗细来考虑,进程的粒度较粗,进程上下文切换时消耗的CPU资源较多。线程的粒度要小的多,虽然线程也会切换,但因为共享进程的上下文,相比进程上下文切换而言,同进程内的线程切换时消耗的资源要小的多的多。在JAVA中,除了java运行时启动的JVM是一个进程,其他所有任务都以线程的方式执行,也就是说java应用程序是单进程的,甚至可以说没有进程的概念。

    什么是线程组(ThreadGroup)


     线程组提供了一些批量管理线程的方法,因此通过将线程加入到线程组中,可以更方便地管理这些线程。

    线程的状态


     就绪态、运行态、睡眠态。还可以分为存活和死亡,死亡表示线程结束,非死亡则存活,因此存活包含就绪、运行、睡眠。

    线程中断睡眠(interrupt)


    将线程从睡眠态强制唤醒,唤醒后线程将进入就绪队列等待cpu调度。

    并发操作


    多个线程同时操作一个资源。这会带来多线程安全问题,解决方法是使用线程同步。

    线程同步


     让线程中的某些任务原子化,即要么全部执行完毕,要么不开始执行。通过互斥锁来实现同步,通过监视这个互斥锁是否被谁持有来决定是否从睡眠态转为就绪态(即从线程池中出去),也就是是否有资格去获取cpu的执行权。线程同步解决了线程安全的问题,但降低了程序的效率。

    死锁


     线程全睡眠了无法被唤醒,导致程序卡死在某一处无法再执行下去。典型的是两个同步线程,线程1持有A锁,且等待B锁,但线程2持有B锁且等待A锁,这样的僵局会造成死锁。但需要注意的是,死锁并非都是因为僵局,只要两边的线程都无法继续向下执行代码(或者两边的线程池都无法被唤醒,这是等价的概念,因为锁等待也会让进程进入睡眠态),则都是死锁。

    用多线程的目的是什么?


     以前我认为多线程的作用就是提升性能。实际上,多线程并不一定能提升性能(甚至还会降低性能);多线程也不只是为了提升性能。多线程主要有以下的应用场景:

    1. 避免阻塞(异步调用)

     单个线程中的程序,是顺序执行的。如果前面的操作发生了阻塞,那么就会影响到后面的操作。这时候可以采用多线程,我感觉就等于是异步调用。这样的例子有很多:
     ajax调用,就是浏览器会启一个新的线程,不阻塞当前页面的正常操作;
     流程在某个环节调用web service,如果是同步调用,则需要等待web service调用结果,可以启动新线程来调用,不影响主流程;
     android里,不要在ui thread里执行耗时操作,否则容易引发ANR;
     创建工单时,需要级联往其他表中插入数据,可以将级联插入的动作放到新线程中,先返回工单创建的结果……

    1. 避免CPU空转

     以http server为例,如果只用单线程响应HTTP请求,即处理完一条请求,再处理下一条请求的话,CPU会存在大量的闲置时间
     因为处理一条请求,经常涉及到RPC、数据库访问、磁盘IO等操作,这些操作的速度比CPU慢很多,而在等待这些响应的时候,CPU却不能去处理新的请求,因此http server的性能就很差
     所以很多web容器,都采用对每个请求创建新线程来响应的方式实现,这样在等待请求A的IO操作的等待时间里,就可以去继续处理请求B,对并发的响应性就好了很多
     当然,这种设计方式并不是绝对的,现在像node.js、Nginx等新一代http server,采用了事件驱动的实现方式,用单线程来响应多个请求也是没问题的。甚至实现了更高的性能,因为多线程是一把双刃剑,在提升了响应性的同时,创建销毁线程都是需要开销的,另外CPU在线程之间切换,也会带来额外的开销。避免了这些额外开销,可能是node.js等http server性能优秀的原因之一吧

    1. 提升性能

     在满足条件的前提下,多线程确实能提升性能
     打一个比方,多线程就相当于,把要炒的菜放到了不同的锅里,然后用不同的炉来炒,当然速度会比较快。本来需要先炒西红柿,10分钟;再炒白菜10分钟;加起来就需要20分钟。用了多线程以后,分别放在2个锅里炒,10分钟就都炒好了

    基本上,需要满足3个条件:

    第1,任务具有并发性,也就是可以拆分成多个子任务。并不是什么任务都能拆分的,条件还比较苛刻

    子任务之间不能有先后顺序的依赖,必须是允许并行的

    比如

    Java代码

    a = b + c
    d = e + f

    这个是可以并行的;

    Java代码

    a = b + c
    d = a + e

    这个就无法并行了,第2步计算需要依赖第1步的计算结果,即使分成2个线程,也不会带来任何性能提升

    另外,还不能有资源竞争。比如2个线程都需要写一个文件,第1个线程将文件锁定了,第2个线程只能等着,这样的2个子任务,也不具备并发性;执行sychronized代码,也是同样的情况

    第2,只有在CPU是性能瓶颈的情况下,多线程才能实现提升性能的目的。比如一段程序,瓶颈在于IO操作,那么把这个程序拆分到2个线程中执行,也是无法提升性能的

    第3,有点像废话,就是需要有多核CPU才行。否则的话,虽然拆分成了多个可并行的子任务,但是没有足够的CPU,还是只有一个CPU在多个线程中切换来切换去,不但达不到提升性能的效果,反而由于增加了额外的开销,而降低了性能。类似于虽然把菜放到了2个锅里,但是只有1个炉子一样

    如果上述条件都满足,有一个经验公式可以计算性能提升的比例,叫阿姆达尔定律:

    速度提升比例 = 1/[(1-P)+(P/N)],其中P是可并行任务的比例,N是CPU核心数量

    假设CPU核心是无限的,则公式简化为1/(1-P)

    假设P达到了80%(已经非常高了),那么速度提升比例也只能达到5倍而已

    总结:
     多线程可以使程序反应更快,交互性更强,执行效率更高。即使在单处理器系统上,多线程程序的运行速度也比单线程程序更快。java对多线程程序的创建及运行,以及锁定资源以避免冲突提供了非常好的支持。
     可以在程序中创建附加的线程以执行并发任务。在java中,每个任务都是Runnable
    接口的一个实例,也称为可运行对象.线程本质上讲就是便于任务执行的对象。

    原文链接:https://blog.csdn.net/ajax_yan/article/details/82149362
    ————————————————
    版权声明:本文为CSDN博主「励志重写JDK」的原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接及本声明。


    什么是任务?


     任务就是对象。为了创建任务,必须首先为任务定义一个实现Runnable接口的类。Runnable接口非常简单。他只包含一个run()方法。需要实现这个方法来告诉系统线程将如何运行。任务必须在线程中执行。从java代码层面看任务就是Runnable对象的run()方法内的代码块。在java程序中任务的执行包括第三个步骤:

    1. 创建一个任务类,该类实现Runnable接口
    2. 使用任务类创建一个线程
    3. 调用线程的start()方法,告诉java虚拟机该线程准备运行

    开发一个任务类的模板如下如代码所示

    package com.poolStudy;
    
    public class TaskThreadFemo {
    
        public static void main(String[] args) {
            Runnable printA = new PrintChar('a',100);
            Runnable printB = new PrintChar('b',100);
            Runnable print100 = new PrintNum(100);
    
            Thread thread1 = new Thread(printA);
            Thread thread2 = new Thread(printB);
            Thread thread3 = new Thread(print100);
    
            thread1.start();
            thread2.start();
            thread3.start();
        }
    }
    
    class PrintChar implements Runnable{
        private char charToPrint;
        private int times;
    
        public PrintChar(char c,int t){
            this.charToPrint = c;
            this.times = t;
        }
    
        @Override
        public void run() {
            for (int i = 0; i < times; i++) {
                System.out.print(charToPrint);
            }
        }
    }
    
    
    class PrintNum implements Runnable{
    
        private int lastNum;
    
        public PrintNum(int n){
            this.lastNum = n;
        }
    
        @Override
        public void run() {
            for (int i = 1; i <= lastNum; i++) {
                System.out.print(i);
            }
        }
    }
    
    

    执行结果如下:

    123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaabbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbaaaaaaaaaaaaaaaaa

    该程序创建了三个任务,为了同时运行他们,创建了三个线程。调用线程的start()方法启动一个线程,他会导致任务中的run()方法被执行。当run()方法执行完毕,线程就终止了。

    重要的注意事项:任务中的run()方法指明如何完成这任务。java虚拟机会自动调用该方法,无需特意调用它。直接调用run()只是在同一个线程中执行该方法,而没有新线程被启动。

    在这里插入图片描述
     可以理解为run()方法内的代码块装载到线程内,由线程给到cpu中执行代码

    如何在java程序中创建一个线程?


    创建的方式有多少种方式??
    new Thread()

    Java中有两种创建线程的方式。

    1.创建线程方式一:

    • 继承Thread类(在java.lang包中),并重写该类的run()方法,其中run()方法即线程需要执行的任务代码。
    • 然后new出这个类对象。这表示创建线程对象。
    • 调用start()方法开启线程来执行任务(start()方法会调用run()以便执行任务)。

    例如下面的代码中,在主线程main中创建了两个线程对象,先后并先后调用start()开启这两个线程,这两个线程会各自执行MyThread中的run()方法。

    class MyThread extends Thread {
        String name;
        String gender;
    
        MyThread(String name,String gender){
            this.name = name;
            this.gender = gender;
        }
    
        public void run(){
            int i = 0;
            while(i<=20) {
                //除了主线程main,其余线程从0开始编号,currentThread()获取的是当前线程对象
                System.out.println(Thread.currentThread().getName()+"-----"+i+"------"+name+"------"+gender);
                i++;
            }
        }
    }
    
    public class CreateThread {
        public static void main(String[] args) {
            MyThread mt1 = new MyThread("malong","Male");
            MyThread mt2 = new MyThread("Gaoxiao","Female");
    
            mt1.start();
            mt2.start();
            System.out.println("main thread over");
        }
    }
    

     上面的代码执行时,有三个线程,首先是主线程main创建2个线程对象,并开启这两个线程任务,开启两个线程后主线程输出"main thread over",然后main线程结束。在开启两个线程任务后,这两个线程加入到了就绪队列等待CPU的调度执行。如下图。因为每个线程被cpu调度是随机的,执行时间也是随机的,所以即使mt1先开启任务,但mt2可能会比mt1线程先执行,也可能更先消亡。
    在这里插入图片描述
    2.创建线程方式二:

    • 实现Runnable接口,并重写run()方法。
    • 创建子类对象。
    • 创建Thread对象来创建线程对象,并将实现了Runnable接口的对象作为参数传递给Thread()构造方法。
    • 调用start()方法开启线程来执行run()中的任务。
    class MyThread implements Runnable {
        String name;
        String gender;
    
        MyThread(String name,String gender){
            this.name = name;
            this.gender = gender;
        }
    
        public void run(){
            int i = 0;
            while(i<=200) {
                System.out.println(Thread.currentThread().getName()+"-----"+i);
                i++;
            }
        }
    }
    
    public class CreateThread2 {
        public static void main(String[] args) {
            //创建子类对象
            MyThread mt = new MyThread("malong","Male");
            //创建线程对象
            Thread th1 = new Thread(mt);
            Thread th2 = new Thread(mt);
    
            th1.start();
            th2.start();
            System.out.println("main thread over");
        }
    }
    

    这两种创建线程的方法,无疑第二种(实现Runnable接口)要好一些,因为第一种创建方法继承了Thread后就无法继承其他父类。


    线程执行的是什么?


    一条代码执行流,完成一组代码的执行,称它为一个任务。线程就是执行run()代码块中的代码

    线程中的常用方法


    Thread类中的方法:

    • isAlive():判断线程是否还活着。活着的概念是指是否消亡了,对于运行态、就绪态、睡眠态的线程都是活着的状态。
    • currentThread():返回值为Thread,返回当前线程对象。
    • getName():获取当前线程的线程名称。
    • setName():设置线程名称。给线程命名还可以使用构造方法Thread(String thread_name)或Thread(Runnable r,String thread_name)。
    • getPriority():获取线程优先级。优先级范围值为1-10(默认值为5),相邻值之间的差距对cpu调度的影响很小。一般使用3个字段MIN_PRIORITY、NORM_PRIORITY、MAX_PRIORITY分别表示1、5、10三个优先级,这三个优先级可较大地区分cpu的调度。
    • setPriority():设置线程优先级。
    • run():封装的是线程开启后要执行的任务代码。如果run()中没有任何代码,则线程不做任何事情。
    • start():开启线程并让线程开始执行run()中的任务。
    • toString():返回线程的名称、优先级和线程组。
    • sleep(long millis):让线程睡眠多少毫秒。以确保其他线程的执行,休眠时间为指定的毫秒数
    • join(t1):将线程t1合并到当前线程,并等待线程t1执行完毕后才继续执行当前线程。即让t1线程强制插队到当前线程的前面并等待t1完成。
    • yield():将当前正在执行的线程退让出去,以让就绪队列中的其他线程有更大的几率被cpu调度。即强制自己放弃cpu,并将自己放入就绪队列。由于自己也在就绪队列中,所以即使此刻自己放弃了cpu,下一次还是可能会立即被cpu选中调度。但毕竟给了机会给其它就绪态线程,所以其他就绪态线程被选中的几率要更大一些。

     java给每个线程指定一个优先级。默认情况下,线程继承生成它的线程的优先级。可以用setPriority方法提高或降低线程的优先级,开能用getPriority方法获取线程的优先级。优先级是从1到10的数字。Thread类有int型常量,如下:
    public static final int MIN_PRIORITY = 1;
    public static final int NORM_PRIORITY = 5;
    public static final int MAX_PRIORITY = 10;
    分别代表1,5,10.主线程的优先级是Thread.NORM_PRIORITY。
     java虚拟机总是选择当前优先级最高的可运行线程。较低优先级的线程只有在没有比它更高的优先级的线程运行时才能运行。如果所有可运行线程具有相同的优先级,那将会用循环队列给他们分配相同的CPU份额。这被称为循环调度(round-robin scheduling)。

    提示: 在java将来的版本中,优先级的数字可能会改变。为将这种变化带来的影响降到最低。可以使用Thread类中的常量来指定线程的优先级。

    提示: 如果总有一个优先级较高的线程在运行,或者有一个相同优先级的线程不退出,那么这个线程可能永远也没有运行的机会。这种情况称为资源竞争或缺乏(contention or starvation)。为避免竞争现象,高优先级的线程必须定时地调用sleep方法或yield方法,来给低或相同优先级的线程一个运行的机会。

    Object类中的方法:

    • wait():线程进入某个线程池中并进入睡眠态。等待notify()或notifyAll()的唤醒。
    • notify():从某个线程池中随机唤醒一个睡眠态的线程。
    • notifyAll():唤醒某个线程池中所有的睡眠态线程。

     这里的某个线程池是由锁对象决定的。持有相同锁对象的线程属于同一个线程池。见后文。
     一般来说,wait()和唤醒的notify()或notifyAll()是成对出现的,否则很容易出现死锁。
     sleep()和wait()的区别:(1)所属类不同:sleep()在Thread类中,wait()则是在Object中;(2)sleep()可以指定睡眠时间,wait()虽然也可以指定睡眠时间,但大多数时候都不会去指定;(3)sleep()不会抛异常,而wait()会抛异常;(4)sleep()可以在任何地方使用,而wait()必须在同步代码块或同步函数中使用;(5)最大的区别是sleep()睡眠时不会释放锁,不会进入特定的线程池,在睡眠时间结束后自动苏醒并继续往下执行任务,而wait()睡眠时会释放锁,进入线程池,等待notify()或notifyAll()的唤醒。

    java.util.concurrent.locks包中的类和它们的方法:

    • Lock类中:

    • lock():获取锁(互斥锁)。

    • unlock():释放锁。

    • newCondition():创建关联此lock对象的Condition对象。

    • Condition类中:

    • await():和wait()一样。

    • signal():和notify()一样。

    • signalAll():和notifyAll()一样。

    注意: Thread类还包括方法stop(),suspend()和resume()。由于普遍认为这些方法具有内在的不安全因素,所以,在java 2中不提倡(或者不流行)这些方法。为替代方法stop()的使用,可以通过Thread变量赋值null来表名他已经停止。

    线程是不是越多越好?


    在这里插入图片描述
     多线程就是让系统在更少的时间做更多的事情,处理更多的用户请求。
    线程不是越多越好

    1. 从时间资源的角度来看

    • 线程在java中是一个对象,每一个java线程都需要一个操作系统线程支持
    • 线程的创建和销毁都需要时间
    • 如果创建时间+销毁时间 > 执行任务时间 就很不划算

    2. 空间资源

    • java对象占用堆内存,操作系统线程占用系统内存。
    • 根据jvm规范,一个线程默认最大栈大小为1M,这个栈空间时需要从系统内存中分配的。

    什么是多线程


     我们知道,一个任务就是一个线程 ,但实际上,一个应用程序为了同时执行多个任务提供运行效率,一般会涉及到一个线程以上的数量。如果,一个应用程序有一个以上的线程,我们把这种情况就称之为多线程。

     本质来说,多线程是为了使得多个线程完成多项任务,以提高系统的效率。目前为止我们使用多线程应用程序的目的是尽可能多地使用计算机处理器资源(本质是为了让效率最大化)。所以,看起来我们仅需要为每个独立的任务分配一个不同的线程,并让处理器确定在任何时间它总会处理其中的某一个任务。但是,这样就会出现一些问题,对小系统来说这样做很好。但是当系统越来越复杂时,线程的数量也会越来越多,操作系统将会花费更多时间去理清线程之间的关系。为了让我们的程序具备可扩展性,我们将不得不对线程进行一些有效的控制。

     针对这种情况,开发者通过使用线程池就可以有效规避上述风险。

    多线程安全问题

    线程安全问题是指多线程同时执行时,对同一资源的并发操作会导致资源数据的混乱。

    例如下面是用多个线程(窗口)售票的代码。

    class Ticket implements Runnable {
        private int num;    //票的数量
    
        Ticket(int num){
            this.num = num;
        }
    
        //售票
        public void sale() {
            if(num>0) {
                num--;
                System.out.println(Thread.currentThread().getName()+"-------"+remain());
            }
        }
    
        //获取剩余票数
        public int remain() {
            return num;
        }
    
        public void run(){
            while(true) {
                sale();
            }
        }
    }
    
    public class ConcurrentDemo {
        public static void main(String[] args) {
            Ticket t = new Ticket(100);
            //创建多个线程对象
            Thread t1 = new Thread(t);
            Thread t2 = new Thread(t);
            Thread t3 = new Thread(t);
            Thread t4 = new Thread(t);
    
            //开启多个线程使其执行任务
            t1.start();
            t2.start();
            t3.start();
            t4.start();
        }
    }
    

    执行结果大致如下:
    在这里插入图片描述
    以上代码的执行过程大致如下图:
    在这里插入图片描述
    共开启了4个线程执行任务(不考虑main主线程),每一个线程都有4个任务:

    ①判断if条件if(num>0);
    ②票数自减num–;
    ③获取剩余票数return num;
    ④打印返回的num数量System.out.println(Thread.currentThread().getName()+"-------"+remain())。
    这四个任务的共同点也是关键点在于它们都操作同一个资源Ticket对象中的num,这是多线程出现安全问题的本质,也是分析多线程执行过程的切入点。

     当main线程开启t1-t4这4个线程时,它们首先进入就绪队列等待被CPU随机选中。(1).假如t1被先选中,分配的时间片执行到任务②就结束了,于是t1进入就绪队列等待被CPU随机选中,此时票数num自减后为99;(2).当t3被CPU选中时,t3所读取到的num也为99,假如t3分配到的时间片在执行到任务②也结束了,此时票数num自减后为98;(3).同理t2被选中执行到任务②结束后,num为97;(4).此时t3又被选中了,于是可以执行任务③,甚至是任务④,假设执行完任务④时间片才结束,于是t3的打印语句打印出来的num结果为97;(5).t1又被选中了,于是任务④打印出来的num也为97。

    显然,上面的代码有几个问题:
    (1)有些票没有卖出去了但是没有记录;
    (2)有的票重复卖了。这就是线程安全问题。

    该如何正确使用多线程


    1. 多线程的目的是充分利用cpu并发处理任务
    2. 线程的本质是将代码发送给cpu去执行
    3. 使用合适数量的线程不断运送代码即可
    4. 这个合适数量的线程就构成了池

    什么是线程池


     线程池是指在初始化一个多线程应用程序过程中创建一个线程集合,然后在需要执行新的任务时重用这些线程而不是新建一个线程(提高线程复用,减少性能开销)。线程池中线程的数量通常完全取决于可用内存数量和应用程序的需求。然而,增加可用线程数量是可能的。线程池中的每个线程都有被分配一个任务,一旦任务已经完成了,线程回到池子中然后等待下一次分配任务。

    在这里插入图片描述

    为什么要使用线程池

    基于以下几个原因在多线程应用程序中使用线程池是必须的:

    1. 线程池改进了一个应用程序的响应时间。由于线程池中的线程已经准备好且等待被分配任务,应用程序可以直接拿来使用而不用新建一个线程。

    2. 线程池节省了CLR 为每个短生存周期任务创建一个完整的线程的开销并可以在任务完成后回收资源。

    3. 线程池根据当前在系统中运行的进程来优化线程时间片。

    4. 线程池允许我们开启多个任务而不用为每个线程设置属性。

    5. 线程池允许我们为正在执行的任务的程序参数传递一个包含状态信息的对象引用。

    6. 线程池可以用来解决处理一个特定请求最大线程数量限制问题。

     本质上来讲,我们使用线程池主要就是为了减少了创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务;节约应用内存(线程开的越多,消耗的内存也就越大,最后死机)

    线程池的作用:

     线程池作用就是限制系统中执行线程的数量。根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果;少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待任务,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

    说完了线程池的概念和作用,我们再看看代码中的线程池:

    在Java中,线程池的代码起源之Executor(翻译过来就是执行者)注意:这个类是一个接口。

    线程什么时候可以创建


    初始化时候可以创建,提交任务的时候可以创建

    • 线程池中的线程可以被复用,不会随意关掉(执行用户提交的runnale)

    线程池线程主要干什么事情?

    提交一个任务,执行逻辑应该是什么样的?

    线程池线程是怎么被销毁的?

    什么是任务仓库?


    任务仓库实际是一个队列,队列的特性是先进先出。

    BlockingQueue阻塞队列,是线程安全的。
    BlockingQueue的操作有四种形式,这四种形式的处理方式不同:

    1. 第一种是抛出一个异常
    2. 第二种是返回一个特殊值
    3. 第三种是在操作可以成功前,无限期地阻塞当前线程
    4. 第四种是在抛弃前只在给定的最大时间限制内阻塞。下表中总结了这些方法
      在这里插入图片描述

    本知识点总结查阅的文献:
    https://www.cnblogs.com/f-ck-need-u/p/8197547.html#1-(Java 线程和多线程执行过程分析)
    https://blog.csdn.net/ajax_yan/article/details/82149362(为什么要用多线程?即多线程的目的)
    http://c.biancheng.net/view/1157.html(Java线程的概念:什么是线程?)
    https://www.jianshu.com/p/50fffbf21b39 (必须要理清的Java线程池)
    《java语言程序设计》
    学习视频知识总结

    展开全文
  • 1、Java线程实现方式:Java中实现多线程主要有两种方式,通过extends Thread类的方式来实现;另一种通过implements Runnable接口来实现。 2、线程的生命周期:Java中,一个线程的生命周期有4种状态,初始化、可执行...

    一、Java线程

    1、Java线程实现方式:Java中实现多线程主要有两种方式,通过extends Thread类的方式来实现;另一种通过implements Runnable接口来实现。

    2、线程的生命周期:Java中,一个线程的生命周期有4种状态,初始化、可执行、阻塞、死亡。

    • 初始化状态:通过new语句创建一个线程对象。
    • 可执行状态:调用start()方法,线程分配到了CPU时间,或者等待分配CPU时间。
    • 阻塞状态:通过调用sleep()方法或wait()方法,线程进入挂起状态,线程不会分配到CPU时间。
    • 死亡状态:run()方法中的逻辑正常运行结束进入死亡状态;调用stop()方法或destroy()方法时也会非正常地终止当前线程。

     二、创建Java线程

    Java中创建一个子线程用到Thread类和Runnabe接口。Thread是线程类,创建一个Thread对象就是创建一个新线程,线程执行的代码程序是在实现Runnable接口对象的run()方法中编写的,即线程执行对象。

    1、继承Thread线程类

    Thread类也实现了Runnable接口,所以Thread类也可以作为线程执行对象,需要继承Thread类,覆盖run()方法。通过start()方法来启动该线程,它会触发run()方法。

    创建继承thread类

    public class SimpleThread extends Thread{
    	int index;//线程编号
    	//通过构造函数指定该线程编号
    	public SimpleThread(int index) {
    	   this.index=index;
    	   System.out.println("创建线程"+index);
    	}
    	//定义线程的运行代码
    	public void run()
    	{
    		for(int i=0;i<=3;i++)
    		{
    			System.out.println("线程"+index+" : "+i);
    		}
    		System.out.println("线程"+index+" : "+index);
    	}
    
    	
    }

    测试该线程类

    public static void main(String[] args) {
    			
    		for(int j=0;j<3;j++)
    		{
    			Thread t=new SimpleThread(j+1);
    			t.start();
    		}
    		
    	}

     2、实现Runnable接口

    Java语言中一个类不能继承多个类,如果一个类已经继承另一个类,就无法再继承thread类,这时可以实现Runnable的方式实现多线程。

    public class ThreadPriority implements Runnable {
    	int numble;//线程编号
    	public ThreadPriority(int numble) {
    		   this.numble=numble;
    		   System.out.println("创建线程"+numble);
    		}
    	@Override
    	public void run() {	
    		// TODO Auto-generated method stub
    		for(int i=0;i<=3;i++)
    		{
    			System.out.println("线程"+numble+" : "+i);
    		}
    		System.out.println("线程"+numble+" : "+numble);
    	}
    
    }

    调用线程

    	public static void main(String[] args) {
    			
    		for(int j=0;j<3;j++)
    		{
    			Thread t=new Thread(new ThreadPriority(j+1));
    			t.start();
    		}
    		
    	}

     如果线程体使用的地方不多,可以不用单独定义一个类,使用匿名内部类或Lambda表达式直接实现Runnable接口,不需要定义一个线程类文件,使得代码变得简洁。

    (1)使用匿名内部类

    public static void main(String[] args) {
    			Thread t1=new Thread(new Runnable() {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    				public void run() {	
    					// TODO Auto-generated method stub
    					for(int i=0;i<=3;i++)
    					{
    						System.out.println(i);
    					}
    					System.out.println("执行完成"+Thread.currentThread().getName());
    				}
    			});
    			
    	      t1.start();
    	}

     

    (2)使用Lambda表达式

    public static void main(String[] args) {
    			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    					// TODO Auto-generated method stub
    					for(int i=0;i<=3;i++)
    					{
    						System.out.println(i);
    					}
    					System.out.println("执行完成"+Thread.currentThread().getName());	
    			});
    			
    	      t1.start();
    	}

     3、设置线程优先级

    Java语言把线程分成了10个不同的优先级别,用1-10表示,数字越小级别越高。Thread类的setPriority(int newPriority)方法可以设置线程优先级,通过getPriority()方法获得线程优先级。

    public static void main(String[] args) {
    			
    
    			Thread t1=new Thread(new ThreadPriority(1));//线程1
    			t1.setPriority(3);//设置线程优先级别为3
    			t1.start();
    			Thread t2=new Thread(new ThreadPriority(2));//线程2
    			t2.setPriority(2);//设置线程优先级别为2
    			t2.start();
    	
    	}

     4、线程让步

    线程类Thread中的静态方法yield(),可以使当前线程给其他线程让步。

    public static void main(String[] args) {
    			
    
    			Thread t1=new Thread(new ThreadPriority(1));//线程1
    			t1.setPriority(3);//设置线程优先级别为3
    			t1.start();
    			Thread t2=new Thread(new ThreadPriority(2));//线程2
    			t2.setPriority(2);//设置线程优先级别为2
    			t2.start();
    			Thread.yield();//当前线程让步		
    	
    	}

     5、线程休眠

    Thread类的sleep()静态方法,可以让当前线程阻塞指定时间。

    注意:调用sleep()方法时,必须要包含在try...catch代码中,否则会有语法错误。

    try {
    		Thread.sleep(1000);
    }
    catch(InterruptedException e)
    {
    				
    } 

    三、多线程同步

           多线程对临界资源的访问有时会导致数据的不一致性。即启动多个线程时,它们可能并发执行某个方法或某块代码,从而可能会发生不同线程同时修改同块存储空间内容的情况。

           Java提供了互斥机制,在任意时刻只能由一个线程访问,即使该线程出现阻塞,该对象的被锁定状态也不会被解除,其他线程仍不能访问该对象。可以通过synchronized关键字实现同步,一种是使用synchronized关键字修饰方法,对方法进行同步;

    以下两种写法一致:

     

    另一种是使用synchronized关键字放在对象前面限制一段代码的执行。

    • 对于实例方法,要给调用该方法的对象加锁。
    • 对于静态方法,要给这个类加锁。

    应用举例:

    编写售票系统,ticketCount为当前票数,getTicketCount()方法获得当前票数,sellTicket()方法销票。

    不加同步机制的运行结果如下所示:

    1、使用synchronized修饰方法实现线程同步

    对getTicketCount()方法和sellTicket()方法使用synchronized修饰

    public class TicketDB {
    	private int ticketCount=5;//机票数量
    	
    	//获得当前机票数量
    	public synchronized int getTicketCount() {
    		return ticketCount;
    	}
    	
    	//销售机票
    	public synchronized void sellTicket() {
    		try {
    			Thread.sleep(1000);//阻塞当前线程,模拟用户付款
    		}
    		catch(InterruptedException e) {
    			
    		}
    		System.out.printf("第%d号票,已售出\n",ticketCount);
    		ticketCount--;
    	}
    }

    测试:

    	public static void main(String[] args) {
    		TicketDB db=new TicketDB();
    		//模拟1号售票网点
    			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    					while(true) {
    						int currTicketCount=db.getTicketCount();//获取当前票数
    						//查询是否有票
    						if(currTicketCount>0) {
    							db.sellTicket();
    						}else {
    							break;
    						}
    					}
    			});
    			
    	      t1.start();
    	      
    	    //模拟2号售票网点
    			Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    					while(true) {
    						int currTicketCount=db.getTicketCount();//获取当前票数
    						//查询是否有票
    						if(currTicketCount>0) {
    							db.sellTicket();
    						}else {
    							break;
    						}
    					}
    			});
    			
    	      t2.start();
    	}

    运行结果:

     2、使用synchronized语句

    getTicketCount()方法和sellTicket()方法为普通方法,不用synchronized修饰

    public static void main(String[] args) {
    		TicketDB db=new TicketDB();
    		//模拟1号售票网点
    			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    					while(true) {
    						synchronized (db) {
    							int currTicketCount=db.getTicketCount();//获取当前票数
    							//查询是否有票
    							if(currTicketCount>0) {
    								db.sellTicket();
    							}else {
    								break;
    							}						
    						}				
    					}
    			});
    			
    	      t1.start();
    	      
    	    //模拟2号售票网点
    			Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    					while(true) {
    						synchronized (db) {
    						int currTicketCount=db.getTicketCount();//获取当前票数
    						//查询是否有票
    						if(currTicketCount>0) {
    							db.sellTicket();
    						}else {
    							break;
    						}
    				       }
    					}
    			});
    			
    	      t2.start();
    	}

     四、线程通信

    1、wait和notify方法

    (1)wait和notify需要放置在synchronized的作用域中。wait属于object类,一旦一个线程执行wait方法后,该线程就会释放synchronized所关联的锁(对象锁),进入阻塞状态,所以该线程一般无法再次主动回到可执行状态,一定要通过其他线程的notify方法去唤醒它。

    notifyAll会让所有因wait方法进入阻塞状态的线程退出阻塞状态,这些线程会竞争对象锁,如果其中一个线程获得了对象锁,则会继续执行。在它释放锁后,其他已被唤醒的线程继续竞争。

    (2)一旦一个线程执行了notify方法,则会通知那些可能因调用wait方法而等待对象锁的其他线程。如果有多个线程等待,则任意挑选一个线程,通知该线程得到对象锁从而继续执行下去。

     应用举例:使用wait和notify方法实现生产者和消费者模型

    public class Stack {
    	//堆栈指针初始值0
    	private int pointer=0;
    	//定义堆栈字符空间
    	private char [] data=new char[5];
    	
    	//压栈
    	public synchronized void push(char c) {
    		//堆栈已满,不能压栈
    		while(pointer==data.length) {
    			try {
    				this.wait();//等待,直到有数据出栈
    			}catch(InterruptedException e) {
    				
    			}
    		}
    		//通知其他线程把数据出栈
    		this.notify();
    		//数据压栈
    		data[pointer]=c;
    		pointer++;//指针向上移动
    	}
    	
    	//出栈
    	public synchronized char pop() {
    		//堆栈无数据,不能出栈
    		while(pointer==0) {
    			try {
    				this.wait();//等待其他线程把数据压栈
    			}catch(InterruptedException e) {
    				
    			}
    		}
    		//通知其他线程压栈
    		this.notify();
    		pointer--;//指针向下移动
    		return data[pointer];
    	}
    }

    测试:

    public static void main(String[] args) {
    		Stack stack =new Stack();
    		//生成者线程
    			Thread t1=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    				char c;
    				for(int i=0;i<10;i++) {
    					c=(char)(Math.random()*26+'A');//随机产生10个字符
    					stack.push(c);//字符压栈
    					System.out.println("生成: "+c);
    					try {
    						Thread.sleep(1000);
    					}catch(InterruptedException e) {
    						
    					}
    				}
    			});
    			
    	    
    	      
    	    //消费者线程
    			Thread t2=new Thread(() -> {//此处使用Thread(Runnable target)构造方法
    				//编写执行线程代码
    					char c;
    					for(int i=0;i<10;i++) {
    						c=stack.pop();//从堆栈取字符
    						System.out.println("消费: "+c);
    						try {
    							Thread.sleep(1000);
    						}catch(InterruptedException e) {
    							
    						}
    					}
    			});
    		  t1.start();	
    	      t2.start();
    	}

    展开全文
  • JAVA多线程知识点总结

    2019-07-10 20:02:32
    2.在Java中,一般通过两种方式来创建多线程。一个是创建一个我们自己的类(MyThread),这个类要继承Thread类,同时覆盖run方法,再实例化这个我们自己的类。 另一个方法是创建一个我们自己的类,这个类要实现...

    1.在Java中有一个主线程。这个主线程不需要我们自己创建,是一运行就有的。而我们自己要创建的是我们自己的线程。(一个线程是以一个对象的形式呈现的。)

    2.在Java中,一般通过两种方式来创建多线程。
    一个是创建一个我们自己的类(MyThread),这个类要继承Thread类,同时覆盖run方法,再实例化这个我们自己的类。

    class MyThread extends Thread{
    }
    public static void main(String[] args) {
    
    		MyThread t1=new MyThread("Thread1");
    
    		MyThread t2=new MyThread("Thread2");
    
    		t1.start();
    
    		t2.start();
    
    	}
    

    另一个方法是创建一个我们自己的类,这个类要实现Runnable接口,同时覆盖run方法。通过new Thread(MyThread())来创建线程。

    class MyThread implements Runnable{
    }
    public static void main(String[] args) {
    
    		Thread t1=new Thread(new MyThread("Thread1"));
    
    		Thread t2=new Thread(new MyThread("Thread2"));
    
    		t1.start();
    
    		t2.start();
    
    	}
    

    【注意,在接口方法实现是,要使用
    Thread t1=new Thread(new MyThread("Thread1"));
    语句,Thread后跟的括号内填自己定义的进程对象】

    3.调用对象的start方法即可让线程开始跑。start象征着对run方法的控制执行。(而非run方法,run方法仅指的是单线程的执行)

    4.synchronized是对象的一个同步锁。被这个关键字修饰的部分代码,称为互斥区。当某个线程在访问这段代码的时候,其他线程对这段代码互斥访问。
    (1)修饰代码段是:

    synchronized(this) {
    	code
    }
    

    (2)修饰方法

    	public synchronized void run() {
                           code
    }
    

    (3)修饰静态static方法,就是给类加锁(也就是给整个类加锁,对这个类的对象都有用。对class对象加锁也是这样的效果)。

    5.Thread.currentThread().getName()方法的作用是获得当前活动线程的名字。

    Thread t1=new Thread(sellTicket,"t1");
    //sellTicket是线程对象名(可重复进行)。t1是每个线程的名。
    

    7.wait()的作用是让当前调用这个方法的线程进入等待状态,同时,wait()也会让当前线程释放它所持有的锁,直到它被其他线程通过notify()或者notifyAll()唤醒。
    该方法只能在同步方法或同步代码段中调用。如果当前线程不是锁的持有者,该方法抛出一个IllegalMonitorStateException异常。

    8.而notifyAll()解除所有那些在该对象上调用wait方法的线程的阻塞状态,notify()随机选择一个在该对象上调用wait方法的线程,解除其阻塞状态。
    这两种方法只能在同步方法或同步块内部调用。

    9.yield()方法是Thread类的静态方法。他的作用是让当前线程从运行状态进入就绪态,而时间片有可能会交给另一个线程(优先级越高,交给的可能性越大)。
    操作系统也许并不会把时间片给其他线程,而是又给回给这个进入就绪态的线程了。而且yield()是不会令线程释放锁的。

    10.sleep()也是Thread的静态方法。他有两个实现,Thread.sleep(long millis)Thread.sleep(long millis, int nanos)。
    他让当前线程进入阻塞状态持续一段时间,这期间时间片一定会让给其他线程。他不会释放锁,然后过了这个特定的时间后该线程变回就绪态。

    11.join()是Thread类中的方法(不是static)。他的主要作用就是同步,令本来交替执行的线程变得顺序执行。
    例如,main线程调用了线程son的join方法,会令main线程进入阻塞态,而son线程继续运行,直到son线程运行完了,main线程才会重新运行。join一定要在start后面调用,否则没用。

    12.如果要终止线程,一般都采用interrupt()方法。
    与线程的中断有关的方法有几个,都是Thread类的方法,分别是

    public void interrupt(),public static boolean interrupted(), public boolean isInterrupted()。
    

    13.interrupt()方法的作用是将中断标记位置位为true。interrupted()isInterrupted()都能够用于检测对象的“中断标记”。

    14.相比多进程而言,多线程开销较小。

    15.getPriority()确定线程的优先级
    setPriority()设置线程的优先级

    展开全文

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,493
精华内容 597
关键字:

java多线程知识点总结

java 订阅