Java--线程(Thread)

来源:互联网 发布:nginx error log 关闭 编辑:程序博客网 时间:2024/05/21 18:49

一、线程简介

  1. 作业|程序|进程|线程
    (1). 作业:一般存放在外存中;
    (2). 程序(Program):静态概念;
    (3). 进程(Process):动态概念,是程序一次动态执行的过程,占用特定的地址空间。每个进程都是独立的,由进程控制块(PCB)、程序块和数据块组成。可能存在的问题是,内存的浪费,cpu的负担。
    (4). 线程(Thread):进程中一个”单一的连续控制流程/执行路径”。线程又被称为轻量级的进程。
  2. “进程与线程”的区别与关系
    (1). 进程是并发执行的程序在执行过程中分配和管理资源的基本单位,是竞争计算机系统资源的基本单位。线程是进程的一部分,一个没有线程的进程可以被看做是单线程,是CPU调度的基本单位。
    (2). 进程拥有一个完整的虚拟地址空间,不依赖于线程而独立存在;反之,线程是进程的一部分,没有自己的地址空间,与进程内的其他线程一起共享分配给该进程的所有资源(相同的变量和对象),它们从同一堆中分配对象,来进行通信、数据交换和同步操作等。
    (3). 包含关系:作业是从外存中调度进内存的,而进程是在创建成功时起就在内存中了。一个作业可由多个进程组成,且必须至少由一个进程组成。一个进程可以有一个或多个线程组成。
    (4). 由于线程间的通信是在同一地址空间上进行的,所以不需要额外的通信机制,这就使得通信更加简便而且信息传递速度也更快了。

二、线程的创建方式

1 . 继承Thread
详细分析见代码注释

/** * 模拟龟兔赛跑 * 1. 创建多线程,重写run()方法,run()方法里的即线程体 * 2. 使用线程:创建子类对象,调用对象.start()方法启动线程 * @author Fanff * */public class Rabbit extends Thread{    @Override    public void run() {        for(int i = 0; i < 100; i++){            System.out.println("兔子跑了" + i + "步");        }    }}class Tortoise extends Thread{    @Override    public void run() {        for(int i = 0; i < 100; i++){            System.out.println("乌龟跑了" + i + "步");        }    }}public class RabbitApp {    public static void main(String[] args) {        // 创建线程对象        Rabbit rab = new Rabbit();        Tortoise tor = new Tortoise();        // 线程启动        rab.start();// 将线程调入到CPU的线程组中进入就绪状态,等待CPU安排        tor.start();        for (int i = 0; i <= 1000; i++){            System.out.println("Main线程跑了"+i+"步");        }        /* 以上有5个线程:Main、后台的gc、异常、Rabit、Tortoise         * 每个线程会交替执行,而不是非得按代码从上到下的执行         */        /*如果是如下直接调用run方法,就是对象调用普通的方法         * 此时就只用3个线程:Main、后台gc、异常         * run方法属于Main线程中,会按照代码从上到下的执行         */        // 直接调用run方法        //rab.run();        //tor.run();    }}

运行的部分结果:
这里写图片描述

2 .实现Runnable接口
本质来说是通过静态代理设计模式来实现的。

  • 实现静态代理的三个条件:
    1)真实角色;
    2)代理角色:需要持有真实角色的引用;
    3)二者得实现相同的接口
  • 创建步骤
    1). 实现Runnable类创建真实角色;
    2). 利用Thread构造器创建代理角色,Thread+真实角色的引用;
    3). 代理角色.start();启动线程
/** * 使用Runnable创建线程 * 1. Thread是代理角色,实现了Runnable接口 * 2. 创建一个实现Runnable接口的真实角色 * 3. 代理角色持有真实角色的引用 * @author Fanff * */public class Programmer implements Runnable{    @Override    public void run() {        for (int i = 1; i <= 1000; i++){            System.out.println("边听歌边睡觉...");        }    }}public class ProgrammerApp {    public static void main(String[] args) {        // 1. 创建真实角色        Programmer p = new Programmer();        // 2. 创建代理角色,并持有真实角色的引用        Thread t = new Thread(p);        // 3. 代理角色启动线程        t.start();    }}

两种方式的对比
A. 继承Thread方法实现多线程的缺点:Java是单继承的的,如果我们的类已经从一个类继承了,则无法再继承Thread,或者说如果继承了Thread类就无法再去继承其他的类,显得很单一。
B. Runnable创建线程的优点:避免单继承的局限;便于共享资源
C. 在通过Thread实现多线程的时候,Thread类已经是实现了Runnable接口,它属于代理角色

/** * 简单模拟抢票 * 说明实现Runnable接口便于资源共享 * @author Fanff * */public class Web12306 implements Runnable{    int num = 50;// 总票数    @Override    public void run() {        while (true){            if (num <= 0)break;            System.out.println(Thread.currentThread().getName() + "抢到了第" + (num--) + "张票");        }    }    public static void main(String[] args) {        // 创建真实角色        Web12306 web = new Web12306();        // 创建多个代理角色,共享资源        Thread t1 = new Thread(web, "t1");        Thread t2 = new Thread(web, "t2");        Thread t3 = new Thread(web, "t3");        // 代理启动        t1.start();        t2.start();        t3.start();    }}

3 使用Callable
(1). 优点:可以解决run()方法中不能返回值和不能抛出异常的缺点;
(2). 缺点:创建太繁琐。因此这里也就不附代码了。

三、线程的状态

这里写图片描述
1. 线程的状态:新生状态(new创建对象后)->就绪状态(调用start()方法,或者挂起)->运行状态(CPU调用)->就绪状态(如果在cpu时间片段内执行完了,这次可能就绪状态不会出现)->阻塞状态(sleep)|死亡状态。

注意:【就绪<->运行】就绪状态下,线程已经具备了运行的条件,只差分配到CPU,处于线程的就绪队列,一旦获得CPU便进入运行状态并自动调用自己的run方法;运行状态下的线程如果在给定的时间片段内没有执行完,就会系统强行拿走CPU,回到就绪状态;
【运行->阻塞】运行状态下的线程,在等待IO设备或者sleep等情况下,主动让出CPU并暂时停止自己的运行时,会进入阻塞状态;
【阻塞->就绪】导致阻塞的事件已经结束,如睡眠事件到了,则线程就会进入到就绪队列。
【死亡|结束】原因有两个,一个是正常运行的线程完成了它全部的工作,另一个是线程被强制性地终止了,如执行了stop()或destroy()方法(不推荐)。前者会产生异常,后者是强制被终止的,因此不会释放锁。

2 .相关状态方法
(1). join():合并线程,写在哪个线程里就暂停哪个线程

public class JoinTest extends Thread{    @Override    public void run() {        for (int i = 1; i <= 30; i++){            System.out.println("新线程在运行!");        }    }    public static void main(String[] args) throws InterruptedException {        JoinTest t = new JoinTest();        t.start();// 就绪状态        for (int i = 0; i < 100; i++){            if (i == 50){                // 放在主线程中,因此阻塞主线程,等待创建的t线程运行完了再运行主线程                t.join();// 如果没有join则主线程和新线程根据CPU的调度交替执行            }            System.out.println("主线程在运行");        }    }}

(2). yiled():暂停本线程(并不代表要等待下一线程完成了再执行本线程,而是暂时先执行另外的线程),是Thread里的static方法,写在那个线程里就暂停哪个线程

public class YeildTest extends Thread{    @Override    public void run() {        for (int i = 1; i <= 30; i++){            System.out.println("新线程在运行!");        }    }    public static void main(String[] args) throws InterruptedException {        YeildTest t = new YeildTest();        t.start();// 就绪状态        for (int i = 0; i < 1000; i++){            if (i == 50){                // 放在主线程中,因此阻塞**本**线程,即主线程,等待创建的线程运行完了再运行主线程                Thread.yield();// 如果没有yield则主线程和新线程根据CPU的调度交替执行            }            System.out.println("主线程在运行");        }    }}
// sleep模拟倒计时public class SleepTest1 {    public static void main(String[] args) throws InterruptedException {        Date starttime = new Date();        Date endtime = new Date(System.currentTimeMillis() + 10 * 1000);        SimpleDateFormat sdf = new SimpleDateFormat("ss");        while (endtime.after(starttime)){            Thread.sleep(1000);            endtime = new Date(endtime.getTime() - 1000);            System.out.println(sdf.format(endtime));        }    }}
/** * sleep模拟网络延时 * @author Fanff * */public class SleepTest2 {    public static void main(String[] args) {        // 创建真实角色        Web12306 web = new Web12306();        // 创建多个代理角色,共享资源        Thread t1 = new Thread(web, "t1");        Thread t2 = new Thread(web, "t2");        Thread t3 = new Thread(web, "t3");        // 代理启动        t1.start();        t2.start();        t3.start();    }}class Web12306 implements Runnable{    int num = 50;// 总票数    @Override    public void run() {        while (true){            if (num <= 0)break;            try {                Thread.sleep(1000);// 模拟网络延时,即前1秒看到了票,等到取的时候却没了                System.out.println(Thread.currentThread().getName() + "抢到了第" + (num--) + "张票");            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }    }}

在模拟网络延时情况下抢票会出现-1票等问题,解决办法见下面的线程锁。

(3). 死亡状态(停止线程)的方法
A. 自然终止:线程体自然执行完毕;
B. 外部干涉:标志法,步骤如下
a). 在线程类中定义线程体使用的标志
b). 线程体使用该标志
c). 对外提供改变该标志的方法
d). 外部根据条件来调用该方法改变标志

/** * 线程的终止: * 由于提供的stop方法不安全,不推荐使用 * 因此利用外部干涉来终止线程 * @author Fanff * */public class Stop1 implements Runnable{    // 在线程类中定义线程体使用的标志    private boolean flag = true;    @Override    public void run() {        while (flag){            System.out.println("new的线程在运行");        }        System.out.println("new的线程被外部终止了");    }    // 对外提供改变该标志的方法    public void setStop(boolean oflag){        this.flag = oflag;    }    public static void main(String[] args) {        Stop1 s = new Stop1();        new Thread(s).start();        for (int i = 0; i < 1000; i++){            System.out.println("主线程在运行");            if (i == 50){                // 对外提供方法改变标志以改变线程状态                s.setStop(false);            }        }    }}

3 .线程的基本方法与属性
1). 基本方法
a). isAlive()线程是否还未终止;
b). getPriority()获得线程的优先级数值;
c). setPriority()设置线程的优先级;
d). setName()给线程一个名字;
e). getName()获得线程的名字;
f). currentThread():是一个静态方法,获得当前正在运行的线程对象也就是取得自己本身
2). 优先级:只是指概率,不代表绝对的先后
a). MAX_PRIORITY 10
b). MIN_PRIORITY 1
c). NORM_PRIORITY 5(默认)

四、同步与死锁

  1. 同步:并发的多个线程访问同一份资源,确保资源安全(线程安全)的一种手段。
    (1). 同步块:注意锁定的范围(范围过大会造成资源浪费,范围过小会造成锁定无效),一般锁定的是对象。
    格式:
synchronized(引用类型变量|this|类.class){    }

(2). 同步方法:在方法的返回值前加入synchronized

/** * 模拟网络延时,造成线程不安全 * @author Administrator * */public class TestSynchronized implements Runnable{    private int num = 10;    private boolean flag = true;    @Override    public void run() {        while (flag){               //test1();            //test2();            //test3();            //test4();            //test5();            test6();        }    }    // 线程不安全    public void test1(){        if (num <= 0){            flag = false;            return ;        }        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票");    }    // 线程安全:同步方法,给方法上锁    public synchronized void test2(){        if (num <= 0){            flag = false;            return ;        }        try {            Thread.sleep(1000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票");    }    //线程安全: 同步块,锁定对象    public void test3(){        synchronized(this){            if (num <= 0){                flag = false;                return ;            }            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票");        }    }    //同步块:锁定引用类型    public void test4(){        synchronized((Integer) num){        if (num <= 0){            flag = false;            return ;        }           try {            Thread.sleep(1000);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票");        }    }    // 同步块:锁定this对象,但是锁定的范围不正确,线程不安全    public void test5(){            if (num <= 0){                flag = false;                return ;            }            synchronized(this){                try {                    Thread.sleep(1000);                } catch (InterruptedException e) {                    // TODO Auto-generated catch block                    e.printStackTrace();                }                System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票");                       }    }    // 同步块:锁定this对象,但是锁定的范围不正确,线程不安全        public void test6(){            synchronized(this){                if (num <= 0){                    flag = false;                    return ;                }            }            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }               System.out.println(Thread.currentThread().getName() + "抢到了第" + num-- + "张票");                   }    public static void main(String[] args) {        // 共用一份资源: 一个真实角色,多个代理        TestSynchronized my = new TestSynchronized();        Thread proxy1 = new Thread(my, "甲");        Thread proxy2 = new Thread(my, "乙");        Thread proxy3 = new Thread(my, "丙");        proxy1.start();        proxy2.start();        proxy3.start();        // 自然终止     }}

3 .死锁:形成条件参见操作系统,这里不做说明。解决死锁的方法,生产者消费者模式。实现生产者消费者模式的方法有管道法和信号灯法。这里只实现信号灯法。信号灯法里三个方法:
- wait():释放已持有的锁,进入wait队伍。(sleep()是不会释放锁的,而且wait()是Object的方法,而sleep()是Thread的方法);
- notify():唤醒wait队列中的第一个线程,并将该线程移入互斥锁申请队列;
- notifyAll():唤醒wait队列中的所有的线程,并将这些线程移入互斥锁申请队列

/** * 一个场景,共同的资源 * 生产值消费者模式--信号灯法 * 生产者生产影片,消费者观看影片 * 生产者和消费者不能同时进行 --->上锁 * @author Fanff * */public class Movie {    private String pic;    // 信号灯:flag = true表示生产者生产,消费者等待,反之...    // 生产完成后,通知消费;消费完成后,通知生产    private boolean flag = true;    // 制作影片    public synchronized void play(String pic){        if (!flag){// 生产者等待            try {                this.wait();            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        try {            // 生产者无需等待时,开始生产,以500ms表示生产的过程            Thread.sleep(500);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("生产了:" + pic);        // 生产完毕,通知消费者消费,同时设置信号灯        this.pic = pic;        this.notify();        this.flag = false;// 生产者停止生产    }    // 播放影片    public synchronized void watch(){        if (flag){// 消费者等待            try {                this.wait();            } catch (InterruptedException e) {                // TODO Auto-generated catch block                e.printStackTrace();            }        }        try {            // 开始消费            Thread.sleep(300);        } catch (InterruptedException e) {            // TODO Auto-generated catch block            e.printStackTrace();        }        System.out.println("消费了:" + pic);        // 消费完毕        // 通知生产        this.notify();        // 设置信号灯        this.flag = true;    }}/** * 相当于生产者 * @author Fanff * */public class Player implements Runnable{    private Movie m;    public Player(Movie m) {        super();        this.m = m;    }    @Override    public void run() {        for (int i = 0; i < 20; i++){            if (0 == i % 2){                m.play("奥特曼");            }else{                m.play("火影忍者");            }        }    }}public class Watcher implements Runnable{    private Movie m;    public Watcher(Movie m) {        super();        this.m = m;    }     @Override    public void run() {        for (int i = 0; i < 20; i++){            m.watch();        }    }}public class App {    public static void main(String[] args) {        // 共同的资源        Movie m = new Movie();        Player p = new Player(m);        Watcher w = new Watcher(m);        new Thread(p).start();        new Thread(w).start();    }}
注意:1). 如果线程对一个指定的对象x发出一个wait()调用,该线程会暂停执行,直到另一个线程对同一个指定的对象x发出一个notify ()调用。2). wait()、notify()、notifyAll()三种方法只能出现在synchronized作用的范围内,即只能在同步中出现,没有同步这三个方法时无用的。

五、任务调度类任务调度:TimerTask任务类

Timer();
void schedule(TimerTask task,long delay);// 只执行一次
void schedule(TimerTask task,Date time);// 在time的时候执行
void schedule(TimerTask task, long delay, long period); // delay是距离调用run()方法的时间,即第一次调用的时间;period是第一次调用 // 之后,从第二次开始每隔多长的时间调用一次 run() 方法

public class TestTimer {    public static void main(String[] args) {        Timer timer = new Timer();        timer.schedule(new TimerTask(){            @Override            public void run() {                System.out.println("So easy");            }        }, 500, 1000);    }}
0 0
原创粉丝点击