Android技术知识Android开发

Java之线程和线程池

2019-10-09  本文已影响0人  拨云见日aaa

一、线程和进程

(1)进程

当一个程序进入内存,即可变成一个进程。
进程包含三大特性:

*实际上对于一个cpu而言在同一时刻,只有一个程序在执行,cup是在多个进程间轮换着执行的,cpu轮换速度很快,看着很像并发的运行。

(2)线程

线程可以看作一个轻量级的进程,线程是进程的最小执行单元。线程也是并发独立的执行流。

(3)线程的优势

二、线程的创建和启动

(1)继承Thread类
(2)实现Runnable接口创建线程
(3)使用Callable和Future创建线程
public class MyThreadTest {
public static void main(String[] args) {
    
    FutureTask<Integer> task=new FutureTask<Integer>(new Callable<Integer>(){
        public Integer call() {
            int i=0;
            for(;i<100;i++) {
                System.out.println(Thread.currentThread().getName()+i);
            }
            return i;
        }
    });
    new Thread(task).start();
    for(int i=0;i<100;i++) {
        System.out.println(Thread.currentThread().getName()+i);
    }
    try {
        System.out.println("返回值"+task.get());
    }catch(Exception e) {
        e.printStackTrace();
    }
} 
}
(4)创建三种线程方式的对比

采用Runnable和Callable接口方式的优缺点

三、线程的生命周期

(1)新建和就绪状态

当使用new关键创建一个线程时,该线程就是新建状态,此时线程对象仅仅由java虚拟机分配内存初始化成员变量,没有表现出出任何动态特性,程序也不会执行执行体里的代码。
当线程对象调用start()方法以后,线程进入就绪状态,Java虚拟机会为其创建方法调用栈和程序计数器,处于此状态的线程并没有运行,而是可以运行了,何时运行取决于JVM中的线程调度器。

注意:只能对处于新建状态的线程类进行start方法

(2)运行和阻塞状态

如果处于就绪状态的线程获得了cpu,开始执行run方法里的执行体,此时线性为运行状态
发生如下情况时,线程进入阻塞状态

(3)线程死亡

线程有三种方式结束:

可以调用isAlive方法检测该线程是否死亡

四、控制线程

(1)join线程

join方法是让一个线程等待另一个线程完成的方法。当在某个线程执行流当中调用其它线程的join方法,调用的线程会阻塞起来,直到被join方法join进来的线程执行完毕。
join方法通常由使用线程的程序调用,可以将大问题划分成小问题,每个小问题都分配一个线程。当所有小问题执行完毕以后,再调用主线程进行下一步。

public class JoinTest {
public static void main(String[] args) {
    Runnable runnable=new Runnable() {
        public void run() {
            for(int i=0;i<100;i++) {
                System.out.println(Thread.currentThread().getName()+i);
            }
        }
    };
    for(int j=0;j<100;j++) {
        System.out.println(Thread.currentThread().getName()+j);
        if(j==20) {
       Thread tr=new Thread(runnable);
       tr.start();
       try {
        tr.join();
    } catch (InterruptedException e) {
        // TODO 自动生成的 catch 块
        e.printStackTrace();
    }
        }
}
}
}

join有三个重载的方法:
join();等待被join的线程执行完成
join(long millis);如果再指定秒数内被join的线程还没执行完成则不再等待
join(long millis,int nanos);等待被join的线程millis毫秒加nanos微毫秒(很少使用)

(2)后台线程

有一种线程,它在后台运行,它的任务是为其它线程提供服务,被称为后台线程。
特征:如果所有前台线程都死亡,后台线程自动死亡
调用Thread对象的setDaemon(true)方法可以将一个线程设置成后台进程。
注意:setDaemon方法必须在start方法之前调用

public class ThreadDemo1 extends Thread{
public void run() {
    for(int i=0;i<200;i++) {
        System.out.println(this.getName()+i);
    }
}
public static void main(String[] args) {
    ThreadDemo1 demo=new ThreadDemo1();
    demo.setDaemon(true);
    demo.start();
    for(int j=0;j<20;j++) {
        System.out.println(Thread.currentThread().getName()+j);
    }
}
}

Thread还提供了isDaemon()方法判断是否为后台进程

(3)线程睡眠:sleep

如果需要让线程暂停一段时间并进入阻塞状态,则可以通过调用Thread类的静态方法sleep实现

public class SleepTest {
public static void main(String[] args) {
    for(int i=0;i<=100;i++) {
        System.out.println(Thread.currentThread().getName()+i);
        if(i==20) {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
        }
        }
    }
}
}

此外还有一个方法与sleep类似,yield方法,它可以让正在执行的线程暂停,但是不会进入阻塞状态,而是直接进入就绪状态。yield只会让线程管理器重新调度一次,很有可能线程调用yield方法暂停之后,马上又被线程调度器调度出来运行。当调用了yield方法之后只有优先级相同或者更高才有可能获得运行的机会
两者的区别:

(4)改变线程优先级

每个线程都有优先级,优先级高的线程得到的执行的机会就多。
每个线程都默认与创建它的父线程一样,在默认的情况下,main线程具有普通优先级。
Thread类提供了setPriority(int newPriority)设置和返回线程的优先级,其中传进去的参数可以是1到10之间的整数,也可以是Thread下的三个静态常量。

五、线程同步

当并发线程共同访问一个数据时,有可能出现问题,出现并发安全问题

(1)同步代码块

Java多线程引入了同步监视器,使用同步监视器的通用方法就是同步代码块。语法如下:

synchronized(obj){
//此处的代码就是同步代码块
}

synchronized括号里面的就是同步监视器,线程开始执行前必须获得同步监视器的锁定。Java虽然可以使用任何对象作为同步监视器,但是推荐使用有可能并发访问的共享资源作为同步监视器

(2)同步方法

与同步代码块对应,Java多线程还提供了同步方法,使用方法就是用synchronized关键字修饰方法,同步方法的同步监视器就是this
线程安全的特征有如下几个特征:

六、释放同步监视器的锁定

程序无法显式的释放同步监视器,如下情况会释放:

七、同步锁

Lock是控制多个线程对共享资源的进行访问的工具。Java提供了ReentrantLock、ReetrantReadWriteLock、StampedLock。一般常用的就是ReetrantLock。
使用方法:

private final ReetrantLock lock=new ReetrantLock();
lock.lock();
try{
//执行的同步代码
}finally{
lock.unLock();
}

八、死锁

当两个线程互相等待对方释放同步监视器时就会发生死锁,典型的哲学家就餐问题。

九、线程通信

(1)传统的线程通信

通过使用Object的wait(),notify(),notifyAll()方法来控制,这三个方法只能由同步监视器来调用。当使用同步方法时,同步监视器就是this,可以直接调用。当同步代码块时,只能由Synchronized括号里面的同步监视器调用

(2)使用Condition控制线程通信

当使用Lock锁时,没有同步监视器,也就不能使用wait,notify,notifyAll方法,所以使用Condition对象来进行通信,可以通过Lock对象的newCondition方法获得
Condition提供了三个方法:
await:类似于隐式同步监视器上的wait方法,导致当前线程等待,直到其它线程调用该Condition的signal或者signalAll方法唤醒
signal:唤醒次Lock对象上等待的单个线程。如果所有线程都在改Lock上等待,则会选择唤醒其中一个线程。
signalAll:唤醒在此Lock对象上等待的所有线程。

(3)使用阻塞队列控制线程通信

BlockingQueue接口,它不是作为容器使用的,而是作为线程同步的工具。它具有一个特征:当生产者线程想往里放入元素时,如果队列已满,则该线程被阻塞,如果消费者想要取出元素时,如果队列已空则线程阻塞。
分别设有put(E e)方法和take()方法

十、线程池

java5以后提供了一个Executor工厂来生产线程池,该工厂类包含几个静态的方法来创建线程池:

十一、ThreadLocal类

ThreadLocal是线程局部变量的意思,它可以为每一个线程提供一个变量值的副本使每一个线程都可以独立改变副本,不会和其他线程冲突。
ThreadLocal提供了三个方法:
T get():返回此线程局部变量副本的值
void remove():删除此线程局部变量中当前的值
void set(T value):设置此线程局部变量中当前的副本值

上一篇 下一篇

猜你喜欢

热点阅读