01-多线程

Java多线程

线程和进程的区别

  • 本质:由CPU进行调度的\color{red}{并发式}执行任务,多个任务被快速轮换执行,使得宏观上具有多个线程或者进程同时执行的效果。
  • 进程\color{blue}{系统进行资源分配和调度的一个独立单位},也是拥有系统资源的基本单位。进程是系统中独立存在的实体,它可以拥有自己独立的资源,拥有自己私有的地址空间,进程之间不能直接访问其他进程的地址空间。
  • 线程:线程是CPU调度的基本单位,也就是说在一个进程中可以有多个并发程序执行流,线程拓展了进程的概念,使得任务的执行得到更加的细分,所以Thread有时候也被称为Lightweight Process。
    \color{red}{线程是进程的执行单元,但是线程不是分配系统资源的单位},它们共享所在进程的资源,包括共享内存,公有数据,全局变量,进程文件描述符,进程处理器,进程代码段,进程用户ID等等,线程独立拥有自己的线程ID,堆栈,程序计数器,局部变量,寄存器组值,优先级,信号屏蔽码,错误返回码等等,。\color{red}{线程是独立运行的,其执行是抢占式的,线程共享进程资源} ,线程之间的通信要进程之间的通信来得容易得多。此外,线程的创建和销毁的开销也远远小于进程的系统开销。

线程和线程池

  • 线程池:虽然线程的创建销毁的开销相对较小,但是频繁得创建和销毁也会消耗有限的资源,从而带来性能上的浪费,也不够高效。因此线程池的出,现就是为了解决这一问题,\color{red}{即在初始状态创建并维护一定数量的空闲线程},当有需要执行的任务,就交付给线程中的一个线程,任务执行结束后,该线程也不会死亡,而是回到线程池中重新变为空闲状态。

线程创建的三种方法

1.继承Thread创建多线程

此时每次创建的Thread对象并不能共享线程类的实例变量,也就是下面程序中的i。

public class FirstThread extends Thread{
    private int i;
    @Override
    public void run() {
        for(i=0;i<10;i++) 
            System.out.println(getName()); // 继承自Thread 
    }
    
    public static void main(String[] args) {    
        new FirstThread().start();
        new FirstThread().start(); // 注意启动线程需要用Start
    }
}

2.实现Runnable接口创建线程类

Runnable接口是一个函数式接口(可以使用Lambda表达式),通常做法是重写接口中的run方法,此时方法体即为线程执行体,使用Runnable接口实现类的实例作为Thread的target来创建Thread对象,此时因为使用一个共同的target线程执行体,多个线程可以共享一个实例变量。

public class SecondThread implements Runnable{
    private int i;
    @Override
    public void run() {
        for(;i<10;i++) {
            System.out.println(Thread.currentThread().getName() + " "+ i);
        }
    }
    
    public static void main(String[] args) {
        SecondThread targetRunnable = new SecondThread();
        new Thread(targetRunnable,"线程1").start();
        new Thread(targetRunnable).start();
    }
}

3.使用Callable和Future创建线程

Callable类似于Runnable,提供一个Call()方法作为线程执行体,并且可以有返回值,以及抛出异常,那么我们如何拿到返回值,java提供了future接口,在接口里定义了一些公共方法来控制关联它的Callable任务,然后java还贴心的给了FutureTask类,该类实现了Future接口和Runnable接口,所以FutureTask的实例就可以作为Thread的Target,所以通常做法是创建Callable接口实现类,并对该实现类的实例使用FutureTask来包装。

public class ThridThread {
    public static void main(String[] args) {        
        // lambda 表达式 + functionInterface 类型转换
        // Callbable: 有返回值
        FutureTask<Integer> task = new FutureTask<Integer>((Callable<Integer>)()->{
            int i =0;
            for(;i<100;i++) {
                System.out.println(Thread.currentThread().getName() + " "+ i);
            }
            return i;
    }); 
        new Thread(task,"有返回值的线程").start();
        try {
            System.out.println("子线程的返回值"+task.get());
        }catch(Exception e) {
            e.printStackTrace();
        }
 }
}

线程的生命周期

image.png

image.png

线程控制

1.join()线程

让一个线程等待另一个线程,当在某个线程执行流中调用其他线程的join()方法,该线程将被阻塞,知道join线程执行完毕为止。

2.后台线程

后台线程又称为\color{red}{Daemon Thread},守护线程,JVM的垃圾回收线程就是典型的后台线程。特征是:\color{red}{如果所有前台线程都死亡,那么后台线程自动死亡}。调用Thread对象的setDaemon(true)可以将指定线程设置为后台线程,注意\color{red}{需要在Start()之前调用,主线程默认为前台线程,}
\color{red}{前台线程创建的子线程默认为前台线程,后台线程创建的子线程默认为后台线程。}

3.yield():线程让步

Thread的静态方法,使得正在执行的线程暂停,但不会阻塞线程,只是交出CPU的控制权,将线程转为就绪状态,让系统调度器重新调度一次。当某个线程调用yield方法暂停后,只有优先级与当前线程相同,或者优先级比当前线程更高的线程才有可能获得执行机会。

4.setPriority(int newPriority)

改变线程优先级,高优先级的线程能获得更多的执行机会。

线程同步

线程的同步的意义在于线程安全,也就是说有多个线程并发访问同一个对象,而线程调度的不确定性可能带来潜在的安全问题

同步方法块:显式指定同步监视器

public class DrawThread extends Thread {
    private Account account;
    private double drawaccout;
    
    public DrawThread(String name,Account account,double drawaccount) {
        super(name);
        this.account = account;
        this.drawaccout= drawaccount;
    }
    
    public void run() {
        synchronized(account) {
            if(account.getBlance()>=drawaccount) {
                System.out.println(getName()+"取钱成功");
                try {
                    Thread.sleep(1);
                }catch(InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

同步方法

  public synchronized void draw(double amount) {
        ……
    }

同步监视器的释放

  • break,return等终止了代码块的执行
  • 同步代码块或者方法中出现未处理的Error或者Exception,导致异常结束
  • 当前线程执行同步代码块或者同步方法时,程序中执行了同步监视器的wait()方法,wait是object的方法,范围是该object实例所在的线程

同步锁

lock,更加强大的线程同步机制,通过显式定义锁对象来实现同步,也就是Lock对象,线程在访问共享资源之前,需要先获得锁对象。线程安全控制中比较常用的是ReetrantLock可重入锁。一个线程可以对已经加锁的ReetrantLock再度加锁。

class X{
        private final ReentrantLock lock = new ReentrantLock();
        
        //需要定义线程安全的方法
        public void foo() {
            lock.lock();//加锁
            try {
                // 需要保证线程安全的代码
            }
            finally {
                lock.unlock();//使用finally块保证释放锁
            }
        }
    }

线程通信

Object类提供的wait(),notify(),notifyAll()三个方法

  • wait(): 当前线程等待或者等待若干ms,当前线程自动释放同步监视器,线程进入等待状态(阻塞),直到其他线程调用了该同步监视器的notify()或者notifyAll方法。
  • notify():唤醒在同步监视器上等待的单个线程,若有多个线程等待,则任意选择其中一个。
  • notifyAll():唤醒在此同步监视器上等待的所有线程。

Condition控制线程通信

使用Condition控制线程通信:使用Lock对象来保证同步问题时,我们可以使用Condition类来释放Lock以及唤醒其他等待线程。

阻塞队列(BlockingQueue)

当生产者线程试图向Blocking Queue中放入元素时,如果队列已满,则该线程被阻塞,当消费者线程试图从BlockingQueue中取出元素时,如果队列已空,则该线程阻塞。

线程池

使用线程池执行线程任务步骤:

  • 调用Executors类的静态工厂方法创建一个ExecutorService对象,该对象代表一个线程池。
  • 创建Runnable实现类或者Callable实现类的实例,作为线程的执行任务。
  • 调用ExecutorService对象的submit方法来提交Runnable或者Callable实例。
  • 当没有任务时,使用shutdown()方法来关闭线程池。
public class Testjava{
  public static void main(String[] args)
  throws Exception{
      ExecutorService pool = Executors.newFixedThreadPool(6);
      Runnable target = ()->{
          for(int i=0;i<100;i++) {
          System.out.println(Thread.currentThread().getName()
                      + "的i值为:"+ i);
          }
      };      
      // 向线程池中提交两个线程
      pool.submit(target);
      pool.submit(target);        
      pool.shutdown();
  }
}
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,033评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,725评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,473评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,846评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,848评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,691评论 1 282
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,053评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,700评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 42,856评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,676评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,787评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,430评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,034评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,990评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,218评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,174评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,526评论 2 343

推荐阅读更多精彩内容

  •   一个任务通常就是一个程序,每个运行中的程序就是一个进程。当一个程序运行时,内部可能包含了多个顺序执行流,每个顺...
    OmaiMoon阅读 1,662评论 0 12
  • 写在之前 以下是《疯狂Java讲义》中的一些知识,如有错误,烦请指正。 概述 ** 进程特征** 独立性:进程是系...
    hainingwyx阅读 368评论 0 0
  • 该文章转自:http://blog.csdn.net/evankaka/article/details/44153...
    加来依蓝阅读 7,331评论 3 87
  • Java 多线程 线程和进程的区别 线程和进程的本质:由CPU进行调度的并发式执行任务,多个任务被快速轮换执行,使...
    安安zoe阅读 2,190评论 1 18
  • 己亥大年初一,是在鞭炮与鸡鸣声中醒来的,但因为山村人少,声音并不混乱,反是热闹得恰到好处。洗漱过后,阿弟便匆匆出门...
    德音流韵阅读 171评论 0 0