Java多线程

概念

程序:指令集,静态概念

进程: 操作系统 ,调度程序 ,动态概念,占用特定的地址空间,由cpu data code 组成,缺点占内存,CPU负担重。pid

线程:又称为轻量级进程,是进程中的一个单一的连续控 流程。线程之间相互独立。一个进程可拥有多个并行的线程。线程间的通行时在同一地址空间上进行的 ,所以不需要额外的通行机制,可以共享变量,也会造成并发问题。是进程内多条执行路径。(真正的多线程只存在于多cpu机器,单CPU属于模拟多线程,时间片实现(挂起))

进程是资源分配的基本单位,线程是资源调度的基本单位

多线程实现

main方法也是个线程,被称主线程

通过继承Thread类

(1)创建多线程重写run方法(方法体),一切从run开始(alt+shift+s 重写父类的方法)

(2)使用多线程:创建子类对象,调用对象 的start方法(start只是加到了线程组里,cpu调用的时候才是真正执行。不是调用run方法,内部会自己调用。调用run方法就是普通的方法调用,只有一条路径)

package com.pku.java.secondstage;
import java.io.IOException;
import java.sql.Time;

class ThreadA extends Thread{
    public void run() {
        // TODO Auto-generated method stub
        try{
        for(int i=0;i<=100;i++){
            System.out.println("thread A run"+i+"steps");
            sleep(100);
        }}
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

class ThreadB extends Thread{
    @Override
    public void run() {
        // TODO Auto-generated method stub
        try{
        for(int i=0;i<=100;i++){
            System.out.println("thread B run"+i+"steps");
            sleep(100);
        }}
        catch (InterruptedException e) {
            e.printStackTrace();
        }
    }   
}

public class ThreadTest {
    public static void main(String args[]){
        ThreadA A =  new ThreadA();
        ThreadB B=  new ThreadB();
        A.start();
        B.start();
        for (int i=0;i<=100;i++){
            System.out.println("this is the "+i+"step of main thread");
        }
    }   
}

对于继承了Thread的实例对象,只需调用其start方法,之后如何调度由cpu处理。

通过实现Runnable接口

(1)实现runnable接口并重写run方法

(2)启用多线程使用静态代理(代理者为Thread)

  • 创建真实角色
  • 创建代理角色,添加真实角色的引用

(3) 调用代理的Start方法

好处 避免单继承的局限性,便于共享资源

class hello implements Runnable{
    public void run(){
      for(int i=0;i<=1000;i++) {
          System.out.println("输出中文");     
      }
    }
}
public class DclientTest {
    public static void main(String args[]){
        hello aHello = new hello();
        Thread proxya =  new Thread(aHello);
        proxya.start();
        for(int i=0;i<1000;i++){
            System.out.println("print english");
        }
    }

}

eg:

模拟多个线程抢票

public class Web12306 implements  Runnable {
    private int num=50;
    public void run() {
        while(true){
            if(num<=0)
                break;
        System.out.printf(Thread.currentThread().getName()+"抢到了",num--);
        System.out.println();
        }
        
    }
    
    public static void main(String args[]){
        Web12306 test  =  new Web12306();
        Thread aThread =  new Thread(test);
        Thread bThread =   new Thread(test);
        Thread cThread = new Thread(test);
        aThread.start();
        bThread.start();
        cThread.start();    
    }
}

核心方法只有一个,代理可以有多个。代理同时执行即多线程

小结:两种实现多线程的方法

  1. 继承Thread+run():通过创建子类对象并调用对象的start()方法

  2. 实现Runnable接口和run()方法:通过使用静态代理

    1.创建真实角色

    2.创建代理角色 Thread+引用

    3.代理角色.Start()

第二种方法的优点,避免单继承的局限性,便于共享资源

前两种方法只能通过try来声明异常不能抛出,也不能返回值,这时要用到第三种创建线程的方法

通过Callable接口

优点:可以获取返回值

Callable和Future接口

Callable是类似于Runnable的接口,实现Callable接口的类和实现Runnable的类都是可以被其他线程执行的任务。

具体实现:

1)创建Callable实现类+重写Call

2)借助执行调度服务ExecutorService,获取Future对象

ExecutorService ser  =  Executors.newFixedThreadPool(2);//开启线程数

Future <Integer> result = ser.submit(实现类对象); //<返回值类型>

3)获取值

result.get()

4)停止服务

ser.shutdownNow()

Callable和Runnable的不同:

(1)Callable规定的方法是call,而Runnable规定的方法是run()

(2)call()方法可以抛出异常,而而run()方法不能抛出异常

(3)Callable的任务执行后可以返回值,运行Callable任务可以拿到一个Future队 形,而Runnable的任务是不能返回值的,Future对象表示异步计算的结果,通过这个对象可以了解任务的执行情况,或者可以取消任务的执行,还可以获取任务执行的结构

import java.util.FormatFlagsConversionMismatchException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class ThridRunTest {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        ExecutorService ser  =  Executors.newFixedThreadPool(2);
        Race tortoise = new Race("tortoise",200);
        Race rabit =  new Race("rabit",50);
        Future <Integer> result1 = ser.submit(tortoise);
        Future <Integer> result2 = ser.submit(rabit);
        
        Thread.sleep(2000);// 让线程运行两秒钟停下
        tortoise.setFlag(false);
        rabit.setFlag(false);
                
        int nums1 = result1.get();
        System.out.println("the tortoise has run-->"+nums1);
        int nums2 = result2.get();
        System.out.println("the rabit has run-->"+nums2);
        ser.shutdown(); 
    }

}
class Race implements Callable<Integer>{
    private String name;
    private long time;
    public long getTime() {
        return time;
    }
    private boolean flag =true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    private int step =0;
    public  Race(String name) {
        super();
        this.name =name;
        // TODO Auto-generated constructor stub
    }
    public  Race(String name,long time) {
        super();
        this.name =name;
        this.time  =time;
        // TODO Auto-generated constructor stub
    }
    @Override
    public Integer call() throws Exception {
        // TODO Auto-generated method stub
        while(flag){
            Thread.sleep(time);
            step++;
        }
        return step;
    }
    public String  getName(){
        return this.name;   
    }   
}

output:

the tortoise has run-->10
the rabit has run-->40

线程的状态

线程状态:

新生状态:

用new关键字建立一个线程对象后, 该线程对象就处于新生状态。处于新生状态的线程有自己的内存空间, 通过调用start进入就绪状态

就绪状态:
处于就绪状态线程具备了运行条件, 但还没分配到CPU, 处于线程就绪队列, 等待系统为其分配CPU。当系统选定一个等待执行的线程后, 它就会从就绪状态进入执行状态, 该动作称之为“cpu调度” 。

运行状态:
在运行状态的线程执行自己的run方法中代码, 直到等待某资源而阻塞或完成任务而死亡。如果在给定的时间片内没有执行结束, 就会被系统给换下来回到等待执行状态。

阻塞状态:
处于运行状态的线程在某些情况下, 如执行了sleep(睡眠) 方法, 或等待I/O设备等资源, 将让出CPU并暂时停止自己的运行, 进入阻塞状态。在阻塞状态的线程不能进入就绪队列。 只有当引起阻塞的原因消除时, 如睡眠时间已到, 或等待的I/O设备空闲下来, 线程便转入就绪状态, 重新到就绪队列中排队等待, 被系统选中后从原来停止的位置开始继续运行。

死亡状态:
死亡状态是线程生命周期中的最后一个阶段。 线程死亡的原因有三个。 一个是正常运行的线程完成了它的全部工作; 另一个是线程被强制性地终止, 如通过执行stop方法来终止一个线程[不推荐使用】 , 三是线程抛出未捕获的异常

停止线程

1.自然终止:线程体执行完毕

2.外部干涉:

  1. 线程类中定义线程体使用的标志
  2. 线程体中使用该标志
  3. 提供对外的方法改变该标识
public class ThreadStopTest {
    public static void main(String[] args) throws InterruptedException {
        Study aStudy =  new Study();
        new Thread(aStudy).start();
        
        Thread.sleep(2000);
        aStudy.setFlag(false);
        
    }
    
}
class Study implements Runnable{
//  public Study() {
//      // TODO Auto-generated constructor stub
//  }
    private boolean flag =true;
    public void setFlag(boolean flag) {
        this.flag = flag;
    }
    @Override
    public void run() {
        // TODO Auto-generated method stub
        while(flag){
            System.out.println("the thrad is running");
            try {
                Thread.sleep(300);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }   
}
线程阻塞

两种方法:

(1)join:合并线程

public class ZuseTest implements Runnable{
    
    public static void main(String args[]) throws InterruptedException{
        ZuseTest aTest = new ZuseTest();
        Thread aThread = new Thread(aTest);
        aThread.start();
        int i=0;
        while(true){
            System.out.println(i);
            i++;
            if (i==50) aThread.join();
            Thread.sleep(300);
        }
    }   
    public void run() {
        for (int i=0;i<=100;i++){
            System.out.println("A");
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }   
    }
}

开始时主线程和子线程并行执行,等到i=50的时候,执行athread.join(),主线程(main)被阻塞,直到aThread线程执行完毕再执行主线程。

(2)yield:暂停线程,暂停当前正在执行的执行对象,暂停自己(是个静态方法)

写在哪个线程里面就暂停谁

public class YieldTest implements Runnable{

    @Override
    public void run() {
        for (int i=0;i<=1000;i++){
            System.out.println("this is Thread"+i);
        }
        
    }
    public static void main(String[] args) {
        Thread aThread  = new Thread(new YieldTest());
        aThread.start();
        int i=1;
        while(true){
            if(i >500) break;
            i++;
            System.out.println("main ==>"+i);
            if(i % 20 ==0){
                Thread.yield();         
            }
        }
    } 
}

被暂停的线程可能会被cpu再次调度

(3)sleep(静态方法)

休眠,暂停当前进程,休眠的时候不会释放锁。

作用:

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

推荐阅读更多精彩内容