Java笔记(4)-线程、Thread、Runable、开子线程、线程运行状态、线程同步、线程死锁


蓬山此去无多路,青鸟殷勤为探看。—李商隐《无题》


38 线程 线程的基本定义

线程和进程

现在的操作系统是多任务操作系统。多线程是实现多任务的一种方式。

进程是指一个内存中运行的应用程序,每个进程都有自己独立的一块内存空间,一个进程中可以启动多个线程。比如在Windows系统中,一个运行的exe就是一个进程。

线程是指进程中的一个执行流程,一个进程中可以运行多个线程。比如java.exe进程中可以运行很多线程。线程总是属于某个进程,进程中的多个线程共享进程的内存。

“同时”执行是人的感觉,在线程之间实际上轮换执行。


进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1–n个线程。

线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。

线程和进程一样分为五个阶段:创建、就绪、运行、阻塞、终止。

多进程是指操作系统能同时运行多个任务(程序)。

多线程是指在同一程序中有多个顺序流在执行。

http://www.mamicode.com/info-detail-517008.html

39 在 Java 当中实现线程的两种方法(使用Thread 或Runnable)

//package com.thread;

class Thread2 implements Runnable{
    private String name;

    public Thread2(String name) {
        this.name=name;
    }

    @Override
    public void run() {
          for (int i = 0; i < 5; i++) {
                System.out.println(name + "运行  :  " + i);
                try {
                    Thread.sleep((int) Math.random() * 10);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }

    }

}
public class Main2 {

    public static void main(String[] args) {
        new Thread(new Thread2("C")).start();
        new Thread(new Thread2("D")).start();
    }

}

//package com.thread;

class Thread1 extends Thread{
    private String name;
    public Thread1(String name) {
       this.name=name;
    }
    public void run() {
        for (int i = 0; i < 5; i++) {
            System.out.println(name + "运行  :  " + i);
            try {
                sleep((int) Math.random() * 10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }

    }
}
public class Main {

    public static void main(String[] args) {
        Thread1 mTh1=new Thread1("A");
        Thread1 mTh2=new Thread1("B");
        mTh1.start();
        mTh2.start();

    }

}

40 线程运行状态介绍

  • 新建状态(New)

产生一个Thread对象就生成一个新线程。当线程处于”新线程”状态时,仅仅是一个空线程对象,它还没有分配到系统资源。因此只能启动或终止它。任何其他操作都会引发异常。例如,一个线程调用了new方法之后,并在调用start方法之前的处于新线程状态,可以调用start和stop方法。


  • 可运行态(Runnable)

start()方法产生运行线程所必须的资源,调度线程执行,并且调用线程的run()方法。在这时线程处于可运行态。该状态不称为运行态是因为这时的线程并不总是一直占用处理机。特别是对于只有一个处理机的PC而言,任何时刻只能有一个处于可运行态的线程占用处理
机。Java通过调度来实现多线程对处理机的共享。注意,如果线程处于Runnable状态,它也有可能不在运行,这是因为还有优先级和调度问题。


  • 阻塞/非运行态(Not Runnable)中断

当以下事件发生时,线程进入非运行态。
①jvm将CPU资源从当前线程切换给其他线程;
②sleep()方法被调用;
③线程使用wait()来等待条件变量;
④线程处于I/O请求的等待,读写操作引起阻塞。

  • 死亡态(Dead)
  • 当run()方法返回,执行结束。
  • 别的线程调用stop()方法,线程进入死亡态。通常Applet使用它的stop()方法来终止它产生的所有线程。

示例1

这里写图片描述


SpeakCat.java

//package com.threadstate;

public class SpeakCat extends Thread{
    public void run() {
        for(int i=1;i<=20;i++) {
            System.out.println("小猫"+i+"  ");
        }
    }
}

SpeackDog .java

//package com.threadstate;

public class SpeackDog extends Thread {
    public void run() {
        for(int i=1;i<=20;i++) {
            System.out.println("小狗"+i+"  ");
        }
    }
}

Main.java

//package com.threadstate;
public class Main {
    public static void main(String[] args) {
        SpeackDog speakdog;
        SpeakCat speakcat;
        speakdog = new SpeackDog();
        speakcat = new SpeakCat();
        speakdog.start();
        speakcat.start();

        for (int i = 1; i <= 15; i++) {
            System.out.println("动物" + i + "  ");
        }

    }
}

jvm在编译Main文件的时候,就会知道有三个线程,主线程、speakdog、speakcat需要轮换使用CPU资源了。

动物1  小猫1  小狗1  小猫2  动物2  小猫3  小猫4  小猫5  小猫6  小狗2  小猫7  动物3  小猫8  小狗3  小猫9  动物4  小猫10  小狗4  小猫11  动物5  小猫12  小狗5  小猫13  动物6  小猫14  小猫15  小猫16  小猫17  小狗6  小猫18  动物7  小猫19  小狗7  小猫20  动物8  动物9  动物10  动物11  动物12  动物13  小狗8  动物14  小狗9  动物15  小狗10  小狗11  小狗12  小狗13  小狗14  小狗15  小狗16  小狗17  小狗18  小狗19  小狗20  

示例2

cake.java

//package com.threadcake;

public class Cake {
    int size;

    public void setSize(int n) {
        size = n;
    }

    public int getSize() {
        return size;
    }

    public void lost(int m) {
        if (size - m >= 0) {
            size = size - m;
        }
    }
}

Main.java

//package com.threadcake;

public class Main {
    public static void main(String[] args) {
        Cake cake = new Cake();
        int size = 10;
        cake.setSize(size);
        System.out.println("蛋糕的大小是" + size + "克");
        Ant antRed = new Ant("红蚂蚁", cake);
        Ant antBlack = new Ant("黑蚂蚁",cake);
        antRed.start();
        antBlack.start();

    }
}

Ant.java

//package com.threadcake;

public class Ant extends Thread {
    Cake cake;

    Ant(String name, Cake c) {
        setName(name);
        cake = c;
    }

    public void run() {
        while (true) {
            int n = 2;
            System.out.print(getName() + "吃" + n + "克蛋糕。");
            cake.lost(n);
            System.out.println(getName() + "发现蛋糕还剩" + cake.getSize() + "克");
            try {
                sleep(1000);
            } catch (InterruptedException e) {}
            if(cake.getSize()<=0){
                System.out.println(getName() + "进入死亡状态");
                return;// 结束run方法
            }
        }
    }
}
//每次执行可能有不同的结果
蛋糕的大小是10克
红蚂蚁吃2克蛋糕。黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩6克
红蚂蚁发现蛋糕还剩8克
黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩4克
红蚂蚁吃2克蛋糕。红蚂蚁发现蛋糕还剩2克
黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩0克
红蚂蚁进入死亡状态
黑蚂蚁进入死亡状态

蛋糕的大小是10克
红蚂蚁吃2克蛋糕。红蚂蚁发现蛋糕还剩8克
黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩6克
红蚂蚁吃2克蛋糕。黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩4克
红蚂蚁发现蛋糕还剩2克
黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩0克
红蚂蚁吃2克蛋糕。红蚂蚁发现蛋糕还剩0克
黑蚂蚁进入死亡状态
红蚂蚁进入死亡状态

蛋糕的大小是10克
红蚂蚁吃2克蛋糕。红蚂蚁发现蛋糕还剩8克
黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩6克
红蚂蚁吃2克蛋糕。红蚂蚁发现蛋糕还剩4克
黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩2克
红蚂蚁吃2克蛋糕。红蚂蚁发现蛋糕还剩0克
黑蚂蚁吃2克蛋糕。黑蚂蚁发现蛋糕还剩0克
黑蚂蚁进入死亡状态
红蚂蚁进入死亡状态
。。。
//由于ant线程执行过程中有可能被中断,导致很多结果

示例3-吵醒线程

Road.java

//package com.threadinterrupt;

public class Road implements Runnable {
    Thread attachThread;

    public void setAttachThread(Thread t) {
        attachThread = t;
    }

    @Override
    public void run() {
        String name = Thread.currentThread().getName();
        if (name.equals("司机")) {
            try {
                System.out.println("我是" + name + "在马路上开车.");
                System.out.println("想睡上一个小时后再开车");
                Thread.sleep(1000 * 60 * 60);
            } catch (Exception e) {
                System.out.println("被警察叫醒了");
            }
            System.out.println(name+"继续开车");
        } else if (name.equals("警察")) {
            for (int i = 1; i <= 3; i++) {
                System.out.println(name + "喊:开车!");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            attachThread.interrupt();
            /*interrupt方法经常用来“吵醒”休眠的线程。当一些线程调用sleep方法休眠时,一个占cpu资源的线程可以让休眠的线程调用interrup方法“吵醒自己”,即导致休眠的线程发生InterruptedException异常,从而结束休眠。*/
        }

    }

}

Main.java

//package com.threadinterrupt;

public class Main {
    public static void main(String[] args) {
        Road road = new Road();
        Thread police,driver;
        police = new Thread(road);
        driver = new Thread(road);
        police.setName("警察");
        driver.setName("司机");
        road.setAttachThread(driver);
        driver.start();
        police.start();

    }
}
//1
我是司机在马路上开车.
想睡上一个小时后再开车
警察喊:开车!
警察喊:开车!
警察喊:开车!
被警察叫醒了
司机继续开车

//2
警察喊:开车!
我是司机在马路上开车.
想睡上一个小时后再开车
警察喊:开车!
警察喊:开车!
被警察叫醒了
司机继续开车

41 线程间通信的方法

42 线程同步

所谓线程同步就是若干个线程都需要使用一个 synchronized 修饰的方法,即程序中若干个线程都需要使用一个方法,而这个方法用 synchronized 给予了修饰。多个线程调用 synchronized 方法必须遵守同步机制:当一个线程使用这个方法时,其他线程想使用这个方法时就必须等待,知道线程使用完成该方法。

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。按照这个定义,其实绝大多数函数都是同步调用(例如sin, isdigit等)。但是一般而言,我们在说同步、异步的时候,特指那些需要其他部件协作或者需要一定时间完成的任务。例如WindowAPI函数SendMessage。该函数发送一个消息给某个窗口,在对方处理完消息之前,这个函数不返回。当对方处理完毕以后,该函数才把消息处理函数所返回的LRESULT值返回给调用者。

在多线程编程里面,一些敏感数据不允许被多个线程同时访问,此时就使用同步访问技术,保证数据在任何时刻,最多有一个线程访问,以保证数据的完整性。

在Java里面,通过synchronized [’sɪŋkrənaɪzd] 进行同步的保证。例如

class MyTest {

    private static final Object lock = new Object();

    public static synchronized void test() {
        // 同步的方法
    }

    public void test2() {
        synchronized (lock) {
            // 方法级同步,也可以使用this实现对象级同步
        }
    }

}

示例-会计和出纳

两个线程:会计和出纳,他俩共同拥有一个账本。使用saveOrTake方法对账本进行管理
Main.java

//package com.synchronize;

public class Main {
    public static void main(String[] args) {
        Bank bank = new Bank();
        bank.setMoney(200);
        Thread accountant, cashier;
        accountant = new Thread(bank);
        cashier = new Thread(bank);
        accountant.setName("会计");
        cashier.setName("出纳");
        accountant.start();
        cashier.start();
    }

}

Bank.java

//package com.synchronize;

public class Bank implements Runnable {
    int money = 200;

    public void setMoney(int n) {
        money = n;
    }

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("会计"))
            saveOrTake(300);
        else if (Thread.currentThread().getName().equals("出纳"))
            saveOrTake(150);
    }

    public synchronized void saveOrTake(int amount) {
        // 存取方法
        if (Thread.currentThread().getName().equals("会计")) {
            for (int i = 1; i <= 3; i++) {
                money = money + amount / 3;// 每存入amount/3,休息一下
                System.out.println(Thread.currentThread().getName() + "存入" + amount / 3 + ",账上有" + money + "万,休息一下再存");
                try {
                    Thread.sleep(1000);// 这时出纳不能是使用saveOrTake方法
                } catch (InterruptedException e) {
                }
            }
        } else if (Thread.currentThread().getName().equals("出纳")) {
            for (int i = 1; i <= 3; i++) {
                money = money - amount / 3;// 每存入amount/3,休息一下
                System.out.println(Thread.currentThread().getName() + "取出" + amount / 3 + ",账上有" + money + "万,休息一下再取");
                try {
                    Thread.sleep(1000);// 这时出纳不能是使用saveOrTake方法
                } catch (InterruptedException e) {
                }
            }
        }
    }

}

//1
会计存入100,账上有300万,休息一下再存
会计存入100,账上有400万,休息一下再存
会计存入100,账上有500万,休息一下再存
出纳取出50,账上有450万,休息一下再取
出纳取出50,账上有400万,休息一下再取
出纳取出50,账上有350万,休息一下再取

//2
...

示例-同步避免线程切换

利用同步,确保一个线程能执行完整个过程,不会在中途被中断
Main.java

//package com.syncake;

public class Main {
    public static void main(String[] args) {
        House house = new House();
        house.setCake(10);
        Thread antone, anttwo;
        antone = new Thread(house);
        anttwo = new Thread(house);
        antone.setName("胖子");
        anttwo.setName("小溪");
        antone.start();
        anttwo.start();
    }
}

House.java

//package com.syncake;

public class House implements Runnable {
    int cake ;

    public void setCake(int c) {
        cake = c;
    }

    @Override
    public void run() {
        while (true) {
            antDoing();
            if (cake <= 0) {
                System.out.println(Thread.currentThread().getName() + "进入死亡状态");
                return;
            }
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
            }
        }
    }

    private synchronized void antDoing() {
        // 同步方法
        int m = 2;
        System.out.println(Thread.currentThread().getName() + "想吃" + m + "克蛋糕");
        cake = cake - m;
        if (cake >= 0) {
            System.out.println(Thread.currentThread().getName() + "发现蛋糕还剩" + cake + "克");
        } else {
            System.out.println(Thread.currentThread().getName() + "发现蛋糕没了");
        }
    }
}

胖子想吃2克蛋糕
胖子发现蛋糕还剩8克
小溪想吃2克蛋糕
小溪发现蛋糕还剩6克
胖子想吃2克蛋糕
胖子发现蛋糕还剩4克
小溪想吃2克蛋糕
小溪发现蛋糕还剩2克
胖子想吃2克蛋糕
胖子发现蛋糕还剩0克
胖子进入死亡状态
小溪想吃2克蛋糕
小溪发现蛋糕没了
小溪进入死亡状态

示例-同步方法中使用wait(),Notify(),notifyAll()

同步方法中,有时会涉及特殊情况。
情景模拟:小胖电影院买票,卖票员没有零钱,他只能等待,让后面的的人买票,卖票员找零。

当一个线程使用的同步方法使用到了某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用wait()方法。wait方法可以中断方法的执行,使本线程等待,暂时让出cpu的使用权,并允许其他线程使用这个同步方法。其他线程如果在使用这个同步方法时不需要等待,那么它使用这个同步方法的同时,应当用notifyAll()方法通知所有的由于这个同步方法而处于等待的线程结束等待。曾中断的线程就会从刚才的中断处继续执行这个同步方法,并遵循“先中断先执行”的原则。如果使用notify()方法,那么知识通知处于等待中的线程的某一个结束等待。

wait()、notify()、notifyAll()都是Object类的final方法,被所有类继承,不允许重写。


Main.java

//package com.synticket;

public class Main {
    public static void main(String[] args) {
        TicketHouse officer = new TicketHouse();
        Thread pangzi,xiaoxi;
        pangzi = new Thread(officer);
        xiaoxi = new Thread(officer);
        pangzi.setName("胖子");
        xiaoxi.setName("小溪");
        pangzi.start();
        xiaoxi.start();

    }

}

TicketHouse.java

//package com.synticket;

public class TicketHouse implements Runnable {
    int fiveAmount = 2, tenAmount = 0, twentyAmount = 0;

    @Override
    public void run() {
        if (Thread.currentThread().getName().equals("胖子")) {
            saleTicket(20);
        } else if (Thread.currentThread().getName().equals("小溪")) {
            saleTicket(5);
        }
    }

    private synchronized void saleTicket(int money) {
        if (money == 5) {
            fiveAmount = fiveAmount + 1;
            System.out.println(
                    "给" + Thread.currentThread().getName() + "入场券," + Thread.currentThread().getName() + "的钱刚好");
        } else if (money == 20) {
            while (fiveAmount < 3) {
                try {
                    System.out.println("\n" + Thread.currentThread().getName() + "靠边等....");
                    wait();
                    System.out.println("\n" + Thread.currentThread().getName() + "继续买票");
                } catch (InterruptedException e) {}
            }
            fiveAmount = fiveAmount - 3;
            twentyAmount = twentyAmount + 1;
            System.out.println("给" + Thread.currentThread().getName() + "入场券," + Thread.currentThread().getName()
                    + "给20元,找15元");
        }
        notifyAll();
    }
}


胖子靠边等....
给小溪入场券,小溪的钱刚好

胖子继续买票
给胖子入场券,胖子给20元,找15

线程同步的方式和机制

临界区、互斥量、事件、信号量四种方式
临界区(Critical Section)、互斥量(Mutex)、信号量(Semaphore)、事件(Event)的区别

1、临界区:通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。在任意时刻只允许一个线程对共享资源进行访问,如果有多个线程试图访问公共资源,那么在有一个线程进入后,其他试图访问公共资源的线程将被挂起,并一直等到进入临界区的线程离开,临界区在被释放后,其他线程才可以抢占。
2、互斥量:采用互斥对象机制。
只有拥有互斥对象的线程才有访问公共资源的权限,因为互斥对象只有一个,所以能保证公共资源不会同时被多个线程访问。互斥不仅能实现同一应用程序的公共资源安全共享,还能实现不同应用程序的公共资源安全共享
3、信号量:它允许多个线程在同一时刻访问同一资源,但是需要限制在同一时刻访问此资源的最大线程数目
4、事 件:通过通知操作的方式来保持线程的同步,还可以方便实现对多个线程的优先级比较的操作

43 线程死锁

死锁: 是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。

死锁的原因:一般是两个对象的锁相互等待造成的。

1.因为系统资源不足。
2.进程运行推进的顺序不合适。
3.资源分配不当。

产生死锁的条件有四个:

1.互斥条件:所谓互斥就是进程在某一时间内独占资源。
2.请求与保持条件:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
3.不剥夺条件:进程已获得资源,在末使用完之前,不能强行剥夺。
4.循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。

死锁是因为多线程访问共享资源,由于访问的顺序不当所造成的,通常是一个线程锁定了一个资源A,而又想去锁定资源B;在另一个线程中,锁定了资源B,而又想去锁定资源A以完成自身的操作,两个线程都想得到对方的资源,而不愿释放自己的资源,造成两个线程都在等待,而无法执行的情况。

ThreadDieSock.java

//package com.synchronize;

public class ThreadDieSock implements Runnable {
    private int flag = 1;
    private  static Object obj1 = new Object(), obj2 = new Object();

    public void run() {
        System.out.println("flag=" + flag);
        if (flag == 1) {
            synchronized (obj1) {
                System.out.println("我已经锁定obj1,休息0.5秒后锁定obj2去!");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj2) {
                    System.out.println("1");
                }
            }
        }
        if (flag == 0) {
            synchronized (obj2) {
                System.out.println("我已经锁定obj2,休息0.5秒后锁定obj1去!");
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (obj1) {
                    System.out.println("0");
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadDieSock run01 = new ThreadDieSock();
        ThreadDieSock run02 = new ThreadDieSock();
        run01.flag = 1;
        run02.flag = 0;
        Thread thread01 = new Thread(run01);
        Thread thread02 = new Thread(run02);
        System.out.println("线程开始喽!");
        thread01.start();
        thread02.start();
    }
}
线程开始喽!
flag=1
flag=0
我已经锁定obj2,休息0.5秒后锁定obj1去!
我已经锁定obj1,休息0.5秒后锁定obj2去!

参考

1.http://www.cnblogs.com/linjiqin/archive/2011/04/11/2013083.html
2.《java程序设计实用教程》

  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值