线程Thread初识

来源:互联网 发布:斗鱼诸葛网络吃蛇视频 编辑:程序博客网 时间:2024/06/08 04:39

线程Thread初识

1. 理解线程、创建线程
2. 线程同步、线程安全
3. 线程通信
4. 线程的生命控制
5. 避免无谓的线程控制


1.理解线程、创建线程
1.1 线程:程序中某一条执行线索
1.2 创建线程的方式
继承Thread和实现Runnale接口

/** * Description:两种创建线程的方法,extends Thread和 implements Runnable */// 1. 通过extends继承Threadclass CreateThread1 extends Thread {    @Override    public void run() {        while(true) {            System.out.println("This is " +                               Thread.currentThread().getName());         }    }}// 2. 通过implements实现runnable接口,创建资源对象class CreateThread2 implements Runnable {    @Override    public void run() {        while(true) {            System.out.println("This is " +                              Thread.currentThread().getName());        }    }} public class TestCreateThread {    public static void main(String[] args) {        //以第一种方式创建线程并启动 (extends Thread)        CreateThread1 thread1 = new CreateThread1();        thread1.start();        //以第二种方式创建线程并启动 (implements Runnale)         CreateThread2 t = new CreateThread2();        Thread thread2 = new Thread(t);        thread2.start();        while(true) {            System.out.println("This is " +                               Thread.currentThread().getName());        }    }}

1.3 两种创建线程方法的对比
1.3.1 Extends创建对象:
通过new Thread()方式直接创建线程对象,会直接产生一个该线程访问的资源对象

1. 3.2 Implements创建对象:
只能通过new Thread(Runnable target)方式创建对象,只要target对象是同一个,那么创建出来的线程访问的资源对象都相同

1.3.3 相比之下,实现Runnale接口比继承Thread类会有以下几点优势:
1.3.3.1适合多个相同程序代码的线程去处理同一资源的情况,把虚拟CPU(线程)同程序的代码、数据有效分离,较好地体现了面向对象的设计思想
1.3.3.2避免了Java单继承机制带来的局限,可以实现“把继承了某一父类的子类放入线程中”
1.3.3.3有利于程序的健壮性,某一资源代码可以被多个线程共享,代码与数据是独立的。多个线程可以操作相同的数据,与它们的代码无关。

1.4 后台线程、联合线程(了解)
1.4.1 后台线程:后台线程是相对前台线程而言的,正常创建并启动的是前台线程,如果在某一线程调用start()启动前,调用了setDaemon(true)方法,这个线程就变成了后台线程
1.4.2 联合线程:在某一线程T在执行时,调用另一个正在执行的线程otherThread的join()方法,将另外一个线程合并到自己的线程上,otherThread会合并到T线程上,并在该线程上执行,这时候T线程会暂停执行,这就是联合线程
T线程什么时候恢复执行?
(1) 当otherThread执行完毕并终止,T线程才会继续执行
(2) 指定的join(time)中的time时间到,会自动分离两个线程,T线程继续执行

2.线程同步
线程安全:多个线程共享同一个资源,在对必须看成整体的某个代码块(具有原子性的代码块),进行操作时,操作还未完成就被打断,会造成数据的前后不一致,因此造成线程的不安全
解决方法:线程同步——同步代码块、同步函数 关键字Synchronized
在这里必须引入某些概念:
1. java的每个对象都具有一个锁旗标(俗:锁标记)
2. Synchronized检查的对象被称为监视器
3. 某线程要进入Synchronized修饰的同步代码块或同步函数,必须持有监视器的锁旗标:
(1) 如果经检查该线程拥有监视器的锁旗标,该线程可以进入同步代码块或同步函数并执行,当执行完毕,会自动释放本监视器的锁旗标,同时,有可能再次获取到本监视器的锁旗标
(2) 如果经检查该线程没有监视器的锁旗标,该线程不被允许进入同步代码块或同步函数,会被分配到与该监视器相关联的锁池中,并等待机会获取锁旗标,执行代码
(3) 如果某线程本身持有其他对象的锁旗标,但并不具备本监视器的锁旗标,同样会被加入本监视器的锁池,并且其自身拥有的锁旗标不会释放
4. 死锁:
(1) A线程拥有T1监视器的锁旗标,在执行T1监视器中的代码时候,企图获取B线程所拥有的T2监视器的锁旗标
(2) B线程拥有T2监视器的锁旗标,在执行T2监视器中的代码时候,企图获取A线程所拥有的T1监视器的锁旗标
(3)结果:双方都抱着自己所拥有的锁不放,从而线程无法继续

Exp:/** * DeadLock: * (1)  A线程拥有T1监视器的锁旗标,在执行T1监视器中的代码时候,企图获取B线程所拥有的T2监视器的锁旗标 * (2)  B线程拥有T2监视器的锁旗标,在执行T2监视器中的代码时候,企图获取A线程所拥有的T1监视器的锁旗标 * res: *      双方都抱着自己所拥有的锁不放,从而线程无法继续 */class A implements Runnable {    static byte[] t1 = new byte[0];    @Override    public void run() {        Thread.currentThread().setName("Thread A");        //(1)A线程拥有T1监视器的锁旗标        synchronized(t1) {            try {                Thread.currentThread().sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() +                                " attend to call B.t2");            //企图获取B线程所拥有的T2监视器的锁旗标            synchronized(B.t2) {                System.out.println(B.t2);            }        }    }} class B implements Runnable {    static byte[] t2 = new byte[0];    @Override    public void run() {        Thread.currentThread().setName("Thread B");        //B线程拥有T2监视器的锁旗标        synchronized(t2) {            try {                Thread.currentThread().sleep(1000);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println(Thread.currentThread().getName() +                                " attend to call A.t1");            //企图获取B线程所拥有的T2监视器的锁旗标            synchronized(A.t1) {                System.out.println(A.t1);            }        }    }}public class TestDeadLock {    public static void main(String[] args) {        new Thread(new A()).start();        new Thread(new B()).start();    }}

3.线程通信
生产者消费者问题:有什么问题?
3.1线程安全
问题描述:若生产过程或消费过程被打断,数据会产生不一致
解决方法:所以必须对生产过程和消费过程加锁
3.2并发执行
问题描述:
(1)若生产过快,消费会漏掉某些生产品
(2)若消费过快,会产生某一产品被消费多次(现实是不可能事件)
解决方法:
现实生活中,如果要做到生产1个消费一个,那么
(1) 当生产过快,那么生产者每生产一个就必须停下来,等消费者消费完一个,再继续执行生产
(2) 当消费过快,那么消费者每消费一个就必须停下来,等生产者生产完一个,再继续执行消费(不得不这样)
程序中,可通过wait(),notify,notifyAll,模拟实现
3.3 模拟生产者消费者问题

Exp:/** *生产者消费者问题 **/class Product {    private int pno = 0;    private String pname = null;    private boolean isfull = false;     public synchronized void putProduct(int pno, String pname) {        //若生产满了、即生产过快,那么使生产者暂停生产        if(isfull) {            try {                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        this.pno = pno;        //人为制造意外,生产过程未完成就被打断        try {            Thread.currentThread().sleep(1000);        } catch (InterruptedException e) {            e.printStackTrace();        }        this.pname = pname;        //每生产一个就显示出来        System.out.println("Producer make " + pname);        isfull = true;        notify();    }    public synchronized void getProduct() {        if(!isfull) {            try {                wait();            } catch (InterruptedException e) {                e.printStackTrace();            }        }        System.out.println("Consumer pick " + pname);        isfull = false;        notify();    }}class Producer implements Runnable {    private Product p = null;    private int i = 1;    public Producer(Product p) {        this.p = p;     }    @Override    public void run() {        while(true) {            if(1 == i % 2) {                i++;                p.putProduct(1, "Product1");            } else {                i++;                p.putProduct(2, "Product2");            }        }    }}class Consumer implements Runnable {    Product p = null;    public Consumer(Product p) {        this.p = p;    }    @Override    public void run() {        while(true) {            p.getProduct();        }    }}public class TestProCon {    public static void main(String[] args) {        Product p = new Product();        Producer producer = new Producer(p);        Consumer consumer = new Consumer(p);        Thread t1 = new Thread(producer);        Thread t2 = new Thread(consumer);        t1.start();        t2.start();    }}Res:Producer make Product1Consumer pick Product1Producer make Product2Consumer pick Product2……

3.4 wait(),notify,notifyAll
wait()方法,谁调用它,谁就沉默,沉默就放弃自己拥有的锁旗标
notify()方法,唤醒同一对象监视器中调用wait()的第一个线程
notifyAll()方法,唤醒同一对象监视器中调用 wait()的所有线程
被唤醒的线程会加入锁池中,等待锁旗标,一旦拥有锁旗标就进入Runnable状态

4.线程的生命控制
4.1线程的生命周期
这里写图片描述

4.1.1 初始状态,线程创建,线程对象调用 start() 方法。
4.1.2 可运行状态,也就是等待 Cpu 资源(os调度),等待运行的状态。
4.1.3 运行状态,获得了 cpu 资源,正在运行状态。
4.1.4 阻塞状态,也就是让出 cpu 资源,进入一种等待状态,而且不是可运行状态,有三种情况会进入阻塞状态。
(1)如等待输入(输入设备进行处理,而 CPU 不处理),则放入阻塞,直到输入完毕,阻塞结束后会进入可运行状态。
(2) 线程休眠,线程对象调用 sleep() 方法,阻塞结束后会进入可运行状态。
(3)线程对象 2 调用线程对象 1 的 join() 方法,那么线程对象 2 进入阻塞状态,直到线程对象 1 中止。
4.1.5 中止状态,也就是执行结束。
4.1.6 锁池状态
4.1.7 等待队列
4.2 控制线程的生命(用boolean型的flag)
(略)

5.避免无谓的线程控制

Exp:class Test {    private byte[] resource1 = new byte[0];    private byte[] resource2 = new byte[0];    public synchronized void method1() {        //处理resource1    }    public synchronized void method2() {        //处理resource1           }    public synchronized void method3() {        //处理resource2    }    public synchronized void method4() {        //处理resource2    }}分析:    已知:    method1和method2都是对resource1进行处理,需要同步控制    method3和method4都是对resource1进行处理,需要同步控制    method1和method3、4,method2和method3、4处理的对象不同,它们之间本可以一起进行,是互不影响    但是此程序将监视器对象设置为调用对象this本身,那么在执行1的时候,34也是无法拿到监视器的锁,造    成34无法进行、这就是无谓的同步控制Alter:class Test {    private byte[] resource1 = new byte[0];    private byte[] resource2 = new byte[0];    private byte[] lock1 = new byte[0];    private byte[] lock2 = new byte[0];    public void method1() {        //处理resource1        synchronized(lock1) {        }    }    public void method2() {        //处理resource1               synchronized(lock1) {        }    }    public void method3() {        //处理resource2        synchronized(lock2) {        }    }    public void method4() {        //处理resource2        synchronized(lock2) {        }    }}分析:    通过创建两个不同的对象当监视器,使method1和method2关联lock1,method3和method4关联lock2    从而避免了无谓的同步控制,使程序性能得以提升

总结

1. 理解线程、创建线程
2. 线程同步、线程安全
3. 线程通信
4. 线程的生命控制
5. 避免无谓的线程控制

1 0
原创粉丝点击