Java 多线程




1、进程与线程


了解进程与线程

一、进程
    在多任务系统中,每个独立执行的程序称为进程。也就“正在进行的程序”。我们现在使用的操作系统一般是多任务的,即能够同时执行多个应用程序,实际情况是,操作系统负责对CPU等设备的资源进行分配和管理,虽然这些设备某一时刻只能做一件事,但以非常小的时间间隔交替执行多个程序,就可以给人以同时执行多个程序的感觉。

二、一个进程中又可以包含一个或多个线程,一个线程就是一个程序内部的一条执行线索,如果要一程序中实现多段代码同时交替运行,就需产生多个线程,并指定每个线程上所要运行的程序代码段,这就是多线程。

二、多线程与单线程的对比
    单线程:只有一个线程执行
    多线程:多个线程同时执行
    示例:参见代码


1.1线程的状态


线程状态
 参考线程的状态转换

    * isAlive():判断线程是否还“活”着,即线程是否还未终止
    * getPriority():获得线程的优先级
    * setPriority():设置线程的优先级
    * Thread.sleep():使用当前线程睡眠,时间为指定的毫秒数
    * join():调用某线程的该方法,将当前线程与该线程“合并”,该线程结束,再恢复当前线程的运行。
    * yield():让出CPU,当前线程进入就绪队列等待调度。
    * wait():当前线程进入对象的wait pool
    * notify()/notifyAll():唤醒对象的wait pool中的一个/所有等待线程。


线程的优先级
    java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照线程的优先级决定应调度哪个线程来执行

线程的优先级用数字表示,范围从1到10,一个线程的缺省优先级是5
    * Thread.MIN_PRIORITY = 1
    * Thread.MAX_PRIORITY = 10
    * Thread.NORM_PRIORITY = 5

使用getPriority()和setPriority()来获得和设置线程的优先级别。



2、Thread类创建线程

实现多线程
一、我们要实现多线程,必须编写一个类继承了Thread类的子类或实现Runnable接口,子类要覆盖Tread类中的run函数,在子类的run函数中调用想在新线程上运行的程序代码。
    也就是说,要将一段代码在新的线程上运行,该代码应该在一类的run函数中,并且run函数所在的类是Thread类的子类。

二、启动一个新的线程,我们不是直接调用Thread的子类对象的run方法,而是调用Thread子类对象的start(从Thread类的继承)的方法,Thread类对象的start方法将产生一个新的线程,并在该线程上运行该Thread类对象中的run方法。

三、由于线程的代码段在run方法中,那么该方法执行完成以后线程也就相应的结束了。因而我们可以通过控制run方法中循环的条件来控制线程的结束。



后台线程与联合线程
一、如果我们对某个线程对象在启动之前调用了setDaemon(true)方法,这个线程就变成了后台线程。setDaemon(false)为前台线程。默认为flase。

二、对java程序来说,只要还有一个前台线程在运行,这个进程就不会结束,如果一个进程中只有后台线程运行,这个进程就会结束。
    示例:ThreadDemo02.java

三、pp.join()的作用是把pp对应的线程合并到调用pp.join()语句的线程中。成为一个线程
    pp.join(毫秒)表示将合并指定毫秒之后再将二个线程分开。
    示例:ThreadDemo03.java

四、继承Thread与实现Runnable接口的区别
    示例:ThreadDemo04.java




3、使用Runnable接口创建多线程

Runnable接口创建多线程

一、方式
    new Thread(Runnable接口实现类);
二、适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想。

二、可以避免由于Java的单继承特性带来的局限。我们经常碰到这样的一种情况,即当我们要将已经继承了某一个类的子类放入多线程中,由于一个类不能同时有两个父类,所以不能用继承Thread类的方式,那么,这个类就只能采用实现Runable接口的方式。

三、当线程被构造时,需要的代码的数据通过一个对象作为构造函数实参传递进去,这个对象就是一个实现在Runnable接口的类的实例。

四、事实上,几乎所有的多线程应用用Runnable接口方式。

五、实际应用
    如:
    1、网络聊天程序
    2、WWW服务器为每个请求都建立一个服务
    3、拷贝、取消




4、线程的同步

线程的同步
    同步以性能为代价换来了线程的安全
一、什么是线程安全
    分析前面的售票代码,指出线程安全问题    
    Thread.sleep(毫秒):当前暂停执行指定时间,让出CPU

二、同步代码块
    CPU在执行同步代码块时,不让其它的线程占用CPU。
    
    synchronized(任意类型的对象)
    {
    }

    任意一个类型的对象都有一个标志位,可以为0或1二种状态。开始状态为1。当CPU执行到同步代码时会检查对象的标志位,为1就可以执行同步代码块中代码,进入同步代码块中后将对象的标志位变为0,执行完后再将标志位变为1。如果检查到标志位为0,线程会阻塞,让出CPU,进入到与对象相关的线程池中等待,直到标志位为1再继续执行。
    标志位我们可以称为锁。
    线程并不能调度CPU,CPU由操作系统调度。
    示例:ThreadDemo05.java

三、同步函数
    使用synchronized修饰方法。使用的锁对象为this
    
    当一个线程进入同步方法中时,将持有方法锁,其它线程将不能再进入这一同步方法中,直到线程将同步方法执行完毕。
    示例:ThreadDemo06.java

四、代码块与函数间的同步
    线程的start方法被调用之后,并不一定会立即执行。只是说明产生了一个线程,这个线程处于就绪状态,等待CPU的调度。

    1、不同步:
    示例:ThreadDemo07.java
    改进:
    示例:ThreadDemo08.java


    2、同步:使用同一个对象锁
    示例:ThreadDemo09.java    
五、死锁
    程序无法执行。互相等待对方所持有的锁。
    多个线程访问同一对象或数据时,会产生线程安全问题。
    死锁示例:ThreadDemo010.java



5、线程间的通信


线程间的通信Thread Communications    

    wait():告诉当前线程放弃锁并进入睡眠状态直到其它线程调用notify()将其唤醒。
    
    notify():唤醒同一对象锁中调用wait()的第一个线程。用于类似饭馆有一个空位后通知所有等候就餐的顾客中的第一位可以入座的情况。
    
    notifyAll():唤醒同一对象锁中调用wait()的所有线程,具有最高优先级的线程首先被唤醒并执行。用于类似某个不定期的培训班终于招生满额后,通知所有学员来上课的情况。


wait()和sleep()的区别

1、wait()是Object类的方法,sleep()是Thread类的静态方法
2、wait()时别的线程可以访问锁定对象,而调用wait()方法时必需锁定该对象,即使用synchronized,即不再持有对象锁
   sleep()时别的线程也不可以访问锁定对象,一直持有对象锁

生产者(Producer)与消费者(Consumer)问题

    注意:调用wait()与notify()方法时,应该调用同一对象的方法,这样才能实现同步。否则可能抛出 java.lang.IllegalMonitorStateException: current thread not owner异常
    
    示例:threaddemo
   



java模拟死锁(DeadLock)

死锁(DeadLock):是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象
例子中使用线程r1和r2在争夺资源o1和o2模拟了一下死锁

public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object(); // 资源1
        Object o2 = new Object(); // 资源2
        Runner r1 = new Runner(1, o1, o2); // 线程1
        Runner r2 = new Runner(2, o2, o1); // 线程2
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

class Runner implements Runnable {
    private int id;
    private Object o1; // 所需的资源1
    private Object o2; // 所需的资源2

    public Runner(int id, Object o1, Object o2) {
        this.id = id;
        this.o1 = o1;
        this.o2 = o2;
    }

    public synchronized void run() {
        System.out.println(id);
        // 锁定资源1
        synchronized (o1) {
            try {
                Thread.sleep(500); // 这里主要是放大效果,让其他线程获取到时间片
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 锁定资源2
            synchronized (o2) {
                System.out.println("Runner " + id);
            }
        }
    }
}



如果交换顺序效果执行效果就不一样了
我那例子程序要真想避免的话就要牺牲一下效率了,一上来就把需要的资源都锁住,像下面这样,因为r1直接把需要的两个资源都锁住了,r2不得不等到r1执行完才能获取资源继续执行

[Copy to clipboard] [ - ]CODE:
public class DeadLock {
    public static void main(String[] args) {
        Object o1 = new Object(); // 资源1
        Object o2 = new Object(); // 资源2
        Runner r1 = new Runner(1, o1, o2, 5000); // 线程1
        Runner r2 = new Runner(2, o2, o1, 500); // 线程2
        new Thread(r1).start();
        new Thread(r2).start();
    }
}

class Runner implements Runnable {
    private int id;
    private Object o1; // 所需的资源1
    private Object o2; // 所需的资源2
    private int sleep;

    public Runner(int id, Object o1, Object o2, int sleep) {
        this.id = id;
        this.o1 = o1;
        this.o2 = o2;
        this.sleep = sleep;
    }

    public void run() {
        System.out.println(id);
        // 锁定资源1
        synchronized (o1) {
            // 锁定资源2
            synchronized (o2) {
                try {
                    Thread.sleep(sleep); // 这里主要是放大效果,让其他线程获取到时间片
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Runner " + id);
            }
        }
    }
}










评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值