Java学习笔记—浅谈多线程

来源:互联网 发布:windows脚本编程格式 编辑:程序博客网 时间:2024/05/21 14:52

涉及的基类:主要涉及的基类是Java.lang.Thread类,该类实现了Runnable接口

一、Java多线程实现的第一种方式:继承Thread类

第一步:编写继承了Thread类的子类
第二步:子类中重写run()方法
第三步:创建子类
第四步:调用start()方法

如下例模拟了龟兔赛跑:

/** * 模拟龟兔赛跑 * 第一步:编写继承了Thread类的子类 * 第二步:子类中重写run方法 * */public class Rabbit extends Thread{    @Override    public void run()    {        //线程体        for(int i=0;i<100;i++){            System.out.println("兔子跑了"+i+"步");        }    }}
public class Test{    public static void main(String[] args)    {        // 第三步:创建子类对象        Rabbit rab = new Rabbit();        // 第四步:调用start()方法        rab.start();// 强调:不是调用run()方法        for(int i=0;i<100;i++){            System.out.println("乌龟跑了"+i+"步");        }    }}

二、Java多线程实现的第二种方式:实现Runnable接口

主要应用了静态代理设计模式
第一步:编写实现了Runnable接口的子类
第二步:子类中重写run()方法
第三步:创建子类(相当于静态代理中创建真实角色)
第四部:创建Thread类,传入子类的引用(相当于静态代理中创建代理角色)
第五步:调用Thread类的start()方法

/** * 模拟龟兔赛跑 * 第一步:编写实现了Runnable接口的子类 * 第二步:子类中重写run()方法 * */public class Rabbit implements Runnable{    @Override    public void run()    {        //线程体        for(int i=0;i<100;i++){            System.out.println("兔子跑了"+i+"步");        }    }}
public class Test{    public static void main(String[] args)    {        // 第三步:创建子类(相当于静态代理中创建真实角色)        Rabbit rab = new Rabbit();        // 第四步:创建Thread类,传入子类的引用(相当于静态代理中创建代理角色)        Thread proxy = new Thread(rab);        // 第五步:调用Thread类的start()方法        proxy.start();        for (int i = 0; i < 100; i++)        {            System.out.println("乌龟跑了" + i + "步");        }    }}

三、线程的生命周期

  • 新建:线程被new出来
  • 准备就绪:线程具有执行的资格,即线程调用了start(),没有执行的权利
  • 运行:具备执行的资格和具备执行的权利
  • 阻塞:没有执行的资格和执行的权利
  • 销毁:线程的对象变成垃圾,释放资源
    这里写图片描述

四、线程的并发安全性问题

案例:模拟抢票,火车站有100张票,4个窗口同时卖票

public class Test {    public static void main(String[] args) {        // 创建线程        SaleTicket st1 = new SaleTicket();        SaleTicket st2 = new SaleTicket();        SaleTicket st3 = new SaleTicket();        SaleTicket st4 = new SaleTicket();        // 线程指定名称        st1.setName("窗口1");        st2.setName("窗口2");        st3.setName("窗口3");        st4.setName("窗口4");        // 线程启动        st1.start();        st2.start();        st3.start();        st4.start();    }}/** * 卖票的窗口 *  * */class SaleTicket extends Thread {    //总票数    private static int tickets = 100;    @Override    public void run() {        while (true) {            if (tickets > 0) {                try {                    Thread.sleep(300);                } catch (InterruptedException e) {                    // TODO 自动生成的 catch 块                    e.printStackTrace();                }                System.out.println(this.getName() + "正在卖" + tickets-- + "张票");            } else {                System.out.println("票已经售完");                break;            }        }    }}

可以观察到运行结果中偶尔会出现多个窗口卖同一张票的情况:
这里写图片描述

五、synchronized(同步锁)解决并发安全性问题

(一)语法:
synchronized(锁对象){
//操作共享资源的代码
}
(二)同步块加在什么地方:

  • 代码被多个线程访问
  • 代码中有共享的资源
  • 共享数据被多条语句操作

(三)上述抢票问题的解决示例:

public class Test {    public static void main(String[] args) {        // 创建线程        SaleTicket st1 = new SaleTicket();        SaleTicket st2 = new SaleTicket();        SaleTicket st3 = new SaleTicket();        SaleTicket st4 = new SaleTicket();        // 线程指定名称        st1.setName("窗口1");        st2.setName("窗口2");        st3.setName("窗口3");        st4.setName("窗口4");        // 线程启动        st1.start();        st2.start();        st3.start();        st4.start();    }}/** * 卖票的窗口 *  * */class SaleTicket extends Thread {    // 总票数    private static int tickets = 100;    //同步锁对象    private static Object obj=new Object();    @Override    public void run() {        while (true) {            synchronized(obj){                if (tickets > 0) {                    try {                        Thread.sleep(100);                    } catch (InterruptedException e) {                        // TODO 自动生成的 catch 块                        e.printStackTrace();                    }                    System.out.println(this.getName() + "正在卖" + tickets-- + "张票");                } else {                    System.out.println("票已经售完");                    break;                }            }        }    }}

六、synchronized(同步锁)对象剖析

  • synchronized同步块的锁对象可以是任意类对象(前提是:线程的实现方式是使用继承于Thread的方式),这个对象必须是线程类共享的(静态的)
  • synchronized是可以加在方法上的,如果是静态方法,则synchronized的锁对象就是类的类对象,如果不是静态方法,则synchronized的锁对象就是当前对象(this)

七、线程的休眠/阻塞(使用sleep)

public class Test1 {    public static void main(String[] args) {        SleepDemo sd=new SleepDemo();        Thread t=new Thread(sd);        t.start();    }}class SleepDemo implements Runnable{    @Override    public void run() {        while(true){            try {                Thread.sleep(1000);            } catch (InterruptedException e) {                // TODO 自动生成的 catch 块                e.printStackTrace();            }            System.out.println(new Date());        }    }}

注意

  • sleep的异常不能够被抛出,必须进行catch处理
  • 如果sleep()方法的调用在同步块中,线程的休眠不会让出锁对象(同步依然有效)

八、线程间的通信

生成者和消费者模式
这里写图片描述

示例:

public class Test3 {    public static void main(String[] args) {        Fruit f = new Fruit();        f.setName("苹果");        f.setExsit(false);        ProductFruit pf = new ProductFruit(f);        BuyFruit bf = new BuyFruit(f);        Thread t1 = new Thread(pf);        Thread t2 = new Thread(bf);        t1.start();        t2.start();    }}/** * 水果 *  */class Fruit {    private String name;    private boolean isExsit;    public String getName() {        return name;    }    public void setName(String name) {        this.name = name;    }    public boolean isExsit() {        return isExsit;    }    public void setExsit(boolean isExsit) {        this.isExsit = isExsit;    }}/** * 生产者线程 * */class ProductFruit implements Runnable {    private Fruit fruit;    public ProductFruit(Fruit fruit) {        super();        this.fruit = fruit;    }    @Override    public void run() {        while (true) {            // 有共享的数据,多个线程操作共享的数据,必须使用锁            synchronized (fruit) {                // 如果水果已经存在那么生产者就不生产,等待着消费者买走水果再生产                if (fruit.isExsit()) {                    try {                        // 当前生产水果的线程被挂起变成阻塞状态                        fruit.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    // TODO 自动生成的 catch 块                    e.printStackTrace();                }                System.out.println(fruit.getName() + "水果被生产出来");                // 把水果的状态变成存在                fruit.setExsit(true);                // 唤醒等待买水果的线程                fruit.notify();            }        }    }}/** * 消费者线程 * */class BuyFruit implements Runnable {    private Fruit fruit;    public BuyFruit(Fruit fruit) {        super();        this.fruit = fruit;    }    @Override    public void run() {        while (true) {            // 有共享的数据,多个线程操作共享的数据,必须使用锁            synchronized (fruit) {                if (!fruit.isExsit()) {                    try {                        fruit.wait();                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }                try {                    Thread.sleep(100);                } catch (InterruptedException e) {                    // TODO 自动生成的 catch 块                    e.printStackTrace();                }                System.out.println(fruit.getName() + "水果被买走");                // 把水果的状态变成不存在                fruit.setExsit(false);                // 唤醒等待生产水果的线程                fruit.notify();            }        }    }}