Java--线程(Thread)

一、线程简介

  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);
    }
}

你可能感兴趣的:(Java--线程(Thread))