多线程

Java的特点之一就是线程的处理较为简单。

一般操作系统都支持同时运行多个任务,一个任务通常就是一个程序,每个运行中的程序被称为一个进程,每个顺序执行流就是一个线程。

进程:正在运行的程序,是程序动态的执行过程(运行内存中)。

线程:在进程内部,并发运行的过程。对于一个程序而言,可能同时运行多个任务,那么每个任务称为一个线程。

并发:进程是并发运行的,OS将时间划分为很多时间片段(时间片),尽可能均匀分配给正在运行的程序,微观上进程走走停停,宏观上都在运行,这种都运行的现象叫并发,但是不是绝对意义上的“同时发生”。


1、Thread类

Thread类代表线程类型。

我们调用start()方法时候,该线程会想线程调度注册当前线程。只有注册了,才有机会被分配时间片进行并发运行。

不能调用run方法,如果调用了,就不是并发运行了,就变成同步了。有先后顺序执行。


时间片是不均匀的,每个线程分配时间不是相等的。线程调度机制尽可能均匀的将时间片段分配给不同线程,让他们都有机会的到cpu的运行。分配给谁的时间片段是不可控的。

stop()方法,关闭线程,由于具有不安全性,不建议使用。

正常的线程停止方法是让run()方法正常的执行完毕。

线程运行过程中,若出现该类未捕获异常,该线程立刻终止。但是不会影响当前进程中其他线程的工作。

若当前进程中所有线程都终止了,那么进程终止。


创建线程的另一种方式将线程与线程体分开,作为线程体就是线程要并发执行的逻辑。最好的模式应该是线程只关心并发操作,不关心具体并发做的事情,我们应该将线程与线程体本身解耦。


例子:

public static void main(String[] args) {
    /**
    * Thread 类
    * Thread类的一个实例代表一个线程
    * 我们可以通过继承Thread并重写run方法,来完成我们想并发的代码。
    */
    Thread thread1 = new NumThread();
    Thread thread2 = new HelloWordThread();
    /**
    * 不能调用run方法,
    */
    thread1.start();
    thread2.start();
}
/**
* 创建一个并发任务
* 重写run方法来编写并发任务代码。
*/
public static class NumThread extends Thread{
    @Override
    public void run() {
        for (int i = 0; i < 10000; i++) {
        System.out.println(i);
    }
}



2、Runnable实现线程

创建一个实现Runnable接口,重写run方法,以实现了Runnable接口的类的实例对象作为创建Thread类对象的构造函数的参数。

public static void main(String[] args) {
    //创建要并发执行的逻辑再交给线程去
    Thread th = new Thread(new HooThread());
    th.start();
}
public static class HooThread implements Runnable{
    public void run(){
        for (int i = 0; i < 10000; i++) {
            System.out.println("how are you");
        }
    }
}

但是这里常常使用内部类。

new Thread(new Runnable(){

   public void run(){...}

}).start();


这种方式将线程体和线程分离开,我们就可以最大程度的利用线程,有了这个思想,就有了后面的线程池的应用。


3、线程的生命周期

线程一旦结束,不能再次调用start()方法。、

相关方法:

static void yield()当前现横让出处理器(离开Running状态),使当前线程进入Runnable状态等待,等待下次分配时间片。

static void sleep(times)让线程从Running状态进入block状态,阻塞times好秒后自动回到Runnable状态。

如果在block时,其他线程打断当前线程的Block(sleep)就会发生异常InterruptedException.

void interrupt()中断线程。

final void setPriority(int)改变线程的优先级。

final void join()等待该线程终止。

/**
* 电子表功能
* 思路:
* 1.获取系统时间
* 2.每秒输出一次
*/
SimpleDateFormat format=new SimpleDateFormat("hh:mm:ss");
while(true){
    Date date = new Date();
    String s = format.format(date);
    System.out.println(s);
    Thread.sleep(1000);
}


一个例子:模拟一个节目的砸墙

public static void main(String[] args) {
    /**
    * 砸墙
    * t1线程:林永健 处理睡眠阻塞的线程
    * t2线程:黄宏 用于打断t1睡眠阻塞的线程
    */
    final Thread lin = new Thread(){
        public void run() {
            System.out.println("林:睡觉去了");
            try {
                Thread.sleep(10000000);
            } catch (InterruptedException e) {
                //e.printStackTrace();
                System.out.println("林:干嘛呢?都破了相了!");
            }
        }
    };
    lin.start();
    Thread hang = new Thread(){
        public void run() {
            System.out.println("黄:准备砸墙!");
            for (int i = 9; i >0; i--) {
                System.out.println("黄:"+(10*i));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.println("黄:搞定!");
            /**
            * 打断t1的睡眠阻塞
            */
            lin.interrupt();//中断线程
        }
    };
    hang.start();
}


4、线程的优先级

线程的优先级根据数字划分为10个等级。

   1最小 对应的常量是 MIN_PRIORIITY

   10最大 对应的常量是 MAX_PRIORIITY

   默认是5 对应的常量是 NORM_PRIORIITY

   //设置优先级

   t1.setPriority(Thread.MAX_PRIORITY);

   t2.setPriority(1);

不是绝对的,但大多情况是这样,毕竟线程分配调度的时间片不可控。


5、守护线程

又叫后台线程,特点:当进程中所有前台线程都终止后,守护线程强制终止。

当进程中只有守护线程时,Java虚拟机退出,进程也终止了。

后台线程是在线程启动前通过方法设置的。

setDaemo(boolean) 当参数是true时,该线程是后台线程。必须在线程启动之前设置,否则没有用了。

//守护线程,要在start之前设置。

jack.setDaemon(true);


6、线程同步的概念

线程同步,可以理解为线程A和B是一块配合,A执行到一定程度时要依靠B的某个结果,于是停下来,示意B执行;B依言执行,在将结果给A;A再继续操作。

所谓同步,就是在发出一个功能调用时,在没有得到结果之前,该调用就不返回,同时其它线程也不能调用这个方法。

   1)异步 并发,各干自己的。

   2)同步 步调一致的处理,多个线程不能同时访问。


这里就引发出一个关键字 Synchronized

多线程并发读写同一个临界资源时发生“线程并发安全问题”

可以使用同步代码快解决线程并发安全问题。

synchronized(同步监视器){}


可以修饰一个方法也可以以独立的代码快存在。


同步监视器:是一个任意对象实例,是一个多个线程之间的互斥的锁机制,多个线程要使用同一个“监视器”对象实现同步互斥。

常见的写法:synchronized(this){} 尽量减少同步范围,提高并发效率。


synchronized修饰的方法,当一个线程进入到该方法后,会对当前方法所属的对象枷锁。其它线程在访问这个方法时候,发现被锁上了,就在方法外等待,直到对象上的锁被释放为止。


7、线程安全类与线程不安全类

   StringBuffer是同步的 synchronized append();

   StringBuilder不是同步的 append();

   Vector/HashTable是同步的。

   ArrayList/HashMap不是同步的。

   如果把一个不是同不的集合变成同步的集,可以使用

   Collections.synchronizedList();

   Collections.synchronizedMap();

具体例子如下:

ArrayList list = new ArrayList();

List syncList = Collections.synchronizedList(list);

这样就得到线程安全的集合


也可以直接用

Collections.synchronizedList();

Collections.synchronizedMap();

Collections.synchronizedSet();


得到线程安全的集合。


8、wait和notify简介

属于Object中的方法,用于多线程之间需要协同工作(等待和唤醒)。

就是说:如果条件不满足时,则等待。当条件满足时,等待该条件的线程将被唤醒。在Java中,这个自己的实现依赖于wait/notify.等待机制与锁的机制是密切关联的。

当线程A调用了B对象的wait方法,那么该线程就在B对象上等待。A线程就进入了wait阻塞。如果线程C也调用了B的wait方法,那么该线程也在B对象上等待。当B调用了notify方法,那个A或者B随机一个进入了Runnable状态开始运行,另一个任然处于wait阻塞。

notifyAll()方法可以让等待的所有线程进入Runnable。