多线程

什么是线程?
  1. 线程(thread)是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务。

多线程的优点

1.使用线程可以把占据时间长的程序中的任务放到后台去处理
2.用户界面更加吸引人,这样比如用户点击了一个按钮去触发某件事件的处理,可以弹出一个进度条来显示处理的进度
3.程序的运行效率可能会提高
4.在一些等待的任务实现上如用户输入,文件读取和网络收发数据等,线程就比较有用了.

多线程的缺点

1.如果有大量的线程,会影响性能,因为操作系统需要在它们之间切换.
2.更多的线程需要更多的内存空间
3.线程中止需要考虑对程序运行的影响.
4.通常块模型数据是在多个线程间共享的,需要防止线程死锁情况的发生
什么时候会用到线程?
当系统中或者开发中。遇到高并发 并行的情况下为了解决负载均衡的问题,就会使用到线程。线程可以提高cpu的利用率。
Thread里面一个特殊的方法run();这个方法就是为执行一个线程而做准备的(当你创建了一个新的线程以后,所有实现的业务逻辑全部在run()方法里面),也就是说在run()方法里面写什么业务。线程就执行实现什么业务。
启动一个线程用start()方法,也就是说当调用Start()方法线程准备就绪以后。才能去启动执行run()方法里面的所有业务逻辑。
Therad线程类提供了好多方法。最常用的有sleep()方法,调用此方法是让一个线程处于睡眠状态。它是Thread类的一个静态方法。sleep()在使用过程中会抛出异常。当在处理异常的时候用try{}catch{}。当重写的方法无法用throws来处理异常时。就必须用try{}catch{}来处理异常。
线程的优先级:setPriority();用此方法就是设置线程的优先级。
修改线程优先级
public final static int MIN_PRIORITY = 1;
public final static int NORM_PRIORITY = 5;
public final static int MAX_PRIORITY = 10;
setPriority的参数在1 - 10 之间就可以, 否则会抛异常

线程调度
  1. 计算机通常只有一个CPU,在任意时刻只能执行一条机器指令,每个线程只有获得CPU的使用权才能执行指令。所谓多线程的并发运行,其实是指从宏观上看,各个线程轮流获得CPU的使用权,分别执行各自的任务。在运行池中,会有多个处于就绪状态的线程在等待CPUJAVA虚拟机的一项任务就是负责线程的调度,线程调度是指按照特定机制为多个线程分配CPU的使用权。<br />1.分时调度<br />所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。<br />2.抢占式调度<br />优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

Java实现多线程的方式

1、继承Thread类创建线程Thread类本质上是实现了Runnable接口的一个实例,代表一个线程的实例。启动线程的唯一方法就是通过Thread类的start()实例方法。start()方法是一个native方法,它将启动一个新线程,并执行run()方法。这种方式实现多线程很简单,通过自己的类直接extend Thread,并复写run()方法,就可以启动新线程并执行自己定义的run()方法。
public class MyThread extends Thread {
public void run() {
System.out.println(“MyThread.run()”);
}
}

MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread();
myThread1.start();
myThread2.start();
2、实现Runnable接口创建线程如果自己的类已经extends另一个类,就无法直接extends Thread,此时,可以实现一个Runnable接口
public class MyThread extends OtherClass implements Runnable {
public void run() {
System.out.println(“MyThread.run()”);
}
}
为了启动MyThread,需要首先实例化一个Thread,并传入自己的MyThread实例:
MyThread myThread = new MyThread();
Thread thread = new Thread(myThread);
thread.start();
3、实现Callable接口通过FutureTask包装器来创建Thread线程
public interface Callable {
V call() throws Exception;
}

——————————————————————————————————————-
public class SomeCallable extends OtherClass implements Callable {
@Override
public V call() throws Exception {
return null;
}
}
——————————————————————————————————————-
Callable oneCallable = new SomeCallable();
//由Callable创建一个FutureTask对象:
FutureTask oneTask = new FutureTask(oneCallable);
//注释:FutureTask是一个包装器,它通过接受Callable来创建,它同时实现了Future和Runnable接口。
//由FutureTask创建一个Thread对象:
Thread oneThread = new Thread(oneTask);
oneThread.start();
//至此,一个线程就创建完成了。
4、使用ExecutorService、Callable、Future实现有返回结果的线程
ExecutorService、Callable、Future三个接口实际上都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,有了这种特征就不需要再为了得到返回值而大费周折了。而且自己实现了也可能漏洞百出。
可返回值的任务必须实现Callable接口。类似的,无返回值的任务必须实现Runnable接口。
执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。
注意:get方法是阻塞的,即:线程无返回结果,get方法会一直等待。
再结合线程池接口ExecutorService就可以实现有返回结果的多线程了。
import java.util.concurrent.;
import java.util.Date;
import java.util.List;
import java.util.ArrayList;

/**
有返回值的线程
*/
@SuppressWarnings(“unchecked”)
public class Test {
public static void main(String[] args) throws ExecutionException,
InterruptedException {
System.out.println(“——程序开始运行——“);
Date date1 = new Date();

int taskSize = 5;
// 创建一个线程池
ExecutorService pool = Executors.newFixedThreadPool(taskSize);
// 创建多个有返回值的任务
List list = new ArrayList();
for (int i = 0; i < taskSize; i++) {
Callable c = new MyCallable(i + “ “);
// 执行任务并获取Future对象
Future f = pool.submit(c);
// System.out.println(“>>>” + f.get().toString());
list.add(f);
}
// 关闭线程池
pool.shutdown();

// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(“>>>” + f.get().toString());
}

Date date2 = new Date();
System.out.println(“——程序结束运行——,程序运行时间【”
+ (date2.getTime() - date1.getTime()) + “毫秒】”);
}
}

class MyCallable implements Callable {
private String taskNum;

MyCallable(String taskNum) {
this.taskNum = taskNum;
}

public Object call() throws Exception {
System.out.println(“>>>” + taskNum + “任务启动”);
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(“>>>” + taskNum + “任务终止”);
return taskNum + “任务返回运行结果,当前任务时间【” + time + “毫秒】”;
}
}
线程阻塞、中断、死锁

线程阻塞

通常是指一个线程在执行过程中暂停,以等待某个条件的触发。
为什么会出现线程阻塞?
睡眠状态
当一个线程执行代码的时候调用了sleep方法后,线程处于睡眠状态,需要设置一个睡眠时间,此时有其他线程需要执行时就会造成线程阻塞,而且sleep方法被调用之后,线程不会释放锁对象
等待状态
当一个线程正在运行时,调用了wait方法,此时该线程需要交出CPU执行权,也就是将锁释放出去,交给另一个线程,该线程进入等待状态,但与睡眠状态不一样的是,进入等待状态的线程不需要设置睡眠时间,但是需要执行notify方法或者notifyall方法来对其唤醒
礼让状态
当一个线程正在运行时,调用了yield方法之后,该线程会将执行权礼让给同等级的线程或者比它高一级的线程优先执行,此时该线程有可能只执行了一部分而此时把执行权礼让给了其他线程,这个时候也会进入阻塞状态
自闭状态
当一个线程正在运行时,调用了一个join方法,此时该线程会进入阻塞状态,另一个线程会运行,直到运行结束后,原线程才会进入就绪状态
suspend() 和 resume()
这两个方法是配套使用的,suspend() 是让线程进入阻塞状态,它的解药就是resume(),没有resume()它自己是不会恢复的,由于这种比较容易出现死锁现象,所以jdk1.5之后就已经被废除

线程中断

线程的thread.interrupt()方法是中断线程,将会设置该线程的中断状态位,即设置为true,中断的结果线程是死亡、还是等待新的任务或是继续运行至下一步,就取决于这个程序本身。线程会不时地检测这个中断标示位,以判断线程是否应该被中断(中断标示值是否为true)。它并不像stop方法那样会中断一个正在运行的线程。
线程在不同状态下对于中断所产生的反应
NEW和TERMINATED
线程的new状态表示还未调用start方法,还未真正启动。线程的terminated状态表示线程已经运行终止。这两个状态下调用中断方法来中断线程的时候,Java认为毫无意义,所以并不会设置线程的中断标识位,什么事也不会发生。
RUNNABLE
如果线程处于运行状态,那么该线程的状态就是RUNNABLE,但是不一定所有处于RUNNABLE状态的线程都能获得CPU运行,在某个时间段,只能由一个线程占用CPU,那么其余的线程虽然状态是RUNNABLE,但是都没有处于运行状态。而我们处于RUNNABLE状态的线程在遭遇中断操作的时候只会设置该线程的中断标志位,并不会让线程实际中断,想要发现本线程已经被要求中断了则需要用程序去判断。 线程一旦发现自己的中断标志为被设置了,立马跳出死循环。这样的设计好处就在于给了我们程序更大的灵活性。
BLOCKED
当线程处于BLOCKED状态说明该线程由于竞争某个对象的锁失败而被挂在了该对象的阻塞队列上了。那么此时发起中断操作不会对该线程产生任何影响,依然只是设置中断标志位。这种状态下的线程和处于RUNNABLE状态下的线程是类似的,给了我们程序更大的灵活性去判断和处理中断。
WAITING/TIMED_WAITING
这两种状态本质上是同一种状态,只不过TIMED_WAITING在等待一段时间后会自动释放自己,而WAITING则是无限期等待,需要其他线程调用notify方法释放自己。但是他们都是线程在运行的过程中由于缺少某些条件而被挂起在某个对象的等待队列上。当这些线程遇到中断操作的时候,会抛出一个InterruptedException异常,并清空中断标志位。
总结:
NEW和TERMINATED对于中断操作几乎是屏蔽的
RUNNABLE和BLOCKED类似,对于中断操作只是设置中断标志位并没有强制终止线程,对于线程的终止权利依然在程序手中。
WAITING/TIMED_WAITING状态下的线程对于中断操作是敏感的,他们会抛出异常并清空中断标志位。

线程死锁

public class Demo10 {
public static void main(String[] args) {
//线程死锁
Culprit c = new Culprit();
Police p = new Police();
new MyThread(c,p).start();
/*
什么时候能不锁?
主线程在执行时调用方法时子线程还未启动,子线程没启动就不会锁住此方法
等执行完以后子线程再启动就不会导致阻塞
*/
c.say(p);
//罪犯等待警察回应,警察回应这方法必须得执行完say,但警察要等罪犯执行完它的say。
//罪犯在等警察执行完say以后调用警察的fun方法/回应方法,警察在等待罪犯执行完say方法后调用罪犯的fun方法
//两人都卡在这,出现线程死锁的情况 在开发时要尽量避免此类情况发生
//在任何有可能导致锁产生的方法里不要再调用另一个方法让另外一个锁产生。否则极有可能造成死锁问题(从根源上)
}
static class MyThread extends Thread{
private Culprit c;
private Police p;
public MyThread(Culprit c,Police p){
this.c=c;
this.p=p;
}
@Override
public void run() {
p.say(c);
}
}
//罪犯
static class Culprit{
//当罪犯说话时传一个警察对象进来,对象说完这话后调用警察的方法
public synchronized void say(Police p){
System.out.println(“罪犯:你放了我,我放人质”);
p.fun();
}
public synchronized void fun(){
System.out.println(“罪犯被放走了,罪犯也放了人质”);
}
}
//警察
static class Police{
public synchronized void say(Culprit c){
System.out.println(“警察:你放人质,我放过你”);
c.fun();
}
public synchronized void fun(){
System.out.println(“警察救了人质,但是罪犯跑了”);
}
}
}
特殊实现结果:
罪犯:你放了我,我放人质
警察救了人质,但是罪犯跑了
警察:你放人质,我放过你
罪犯被放走了,罪犯也放了人质

一般实现结果:
罪犯:你放了我,我放人质
警察:你放人质,我放过你
线程死锁是指两个或两个以上的线程互相持有对方所需要的资源,由于synchronized的特性,一个线程持有一个资源,或者说获得一个锁,在该线程释放这个锁之前,其它线程是获取不到这个锁的,而且会一直死等下去,因此这便造成了死锁。

  • 互斥条件:一个资源,或者说一个锁只能被一个线程所占用,当一个线程首先获取到这个锁之后,在该线程释放这个锁之前,其它线程均是无法获取到这个锁的。
  • 占有且等待:一个线程已经获取到一个锁,再获取另一个锁的过程中,即使获取不到也不会释放已经获得的锁。
  • 不可剥夺条件:任何一个线程都无法强制获取别的线程已经占有的锁
  • 循环等待条件:线程A拿着线程B的锁,线程B拿着线程A的锁

如何避免死锁

  • 加锁顺序:线程按照相同的顺序加锁。
  • 加锁时限,线程获取锁的过程中限制一定的时间,如果给定时间内获取不到,就算了,别勉强自己。这需要用到Lock的一些API。

解决方案:
打破互斥条件,允许进程同时访问某些资源。
打破不可抢占条件,占有资源的进程不能再申请占有其他资源,必须释放手头上的资源后才能发起申请。
进程在运行前申请得到所有的资源,否则该进程不能进入准备执行状态,但可能会导致资源利用率或进程并发性降低
对资源实现分类编号,按号分配。避免出现资源申请环路。但会增大进程对资源的占用时间。

显式锁与隐式锁

Synchronized是Java的关键字,当它用来修饰一个方法或一个代码块时,能够保证在同一时刻最多只有一个线程执行该代码。因为当调用Synchronized修饰的代码时,并不需要显示的加锁和解锁的过程,所以称之为隐式锁。
Sychronized的用法:
1、同步方法体,在方法声明中使用,如下:
public synchronized void method(){
//方法体
}
2、同步代码块,修饰在代码块外层,指定加锁对象,如下:
public void method2(){
synchronized (this) {
//一次只能有一个线程进入
}
}
Lock
Lock是一个接口,提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有的加锁和解锁操作方法都是显示的,因而称为显示锁。
ReentrantLock(可重入锁),是一个互斥的同步器,用ReentrantLock实现同步机制比sychronized实现更具伸缩性。在使用ReentrantLock时,一定要有释放锁的操作。
private final ReentrantLock lock = new ReentrantLock();
public void m(){
lock.lock();//获得锁
try{
//方法体
}finally{
lock.unlock();//务必释放锁
}
}
ReadWriteLock(读写锁)是一个接口,提供了readLock和writeLock两种锁的操作,也就是说一个资源能够被多个读线程访问,或者被一个写线程访问,但是不能同时存在读写线程。也就是说读写锁应用的场景是一个资源被大量读取操作,而只有少量的写操作。
ReadWriteLock借助Lock来实现读写两个锁并存、互斥的机制。每次读取共享数据就需要读取锁,需要修改共享数据就需要写入锁。
读写锁的机制:1、读-读不互斥,读线程可以并发执行;2、读-写互斥,有写线程时,读线程会堵塞;3、写-写互斥,写线程都是互斥的。
private ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
//抽取读写锁
private Lock readLock = rwl.readLock();
private Lock writeLock = rwl.writeLock();
public int getXXX(){
readLock.lock();
try{
//执行操作
}finally{
readLock.unlock();
}
}
public void setXXX(){
writeLock.lock();
try{
//执行操作
}finally{
writeLock.unlock();
}
}
对比Synchronized、ReentrantLock和ReentrantReadWriteLock:

  1. Synchronized是在JVM层面上实现的,无需显示的加解锁,而ReentrantLock和ReentrantReadWriteLock需显示的加解锁,一定要保证锁资源被释放;
  2. Synchronized是针对一个对象的,而ReentrantLock和ReentrantReadWriteLock是代码块层面的锁定;
  3. ReentrantReadWriteLock引入了读写和并发机制,可以实现更复杂的锁机制,并发性相对于ReentrantLock和Synchronized更高。
    线程池
    线程池就是首先创建一些线程,它们的集合称为线程池。使用线程池可以很好地提高性能,线程池在系统启动时即创建大量空闲的线程,程序将一个任务传给线程池,线程池就会启动一条线程来执行这个任务,执行结束以后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个任务。
    工作机制:
    在线程池的编程模式下,任务是提交给整个线程池,而不是直接提交给某个线程,线程池在拿到任务后,就在内部寻找是否有空闲的线程,如果有,则将任务交给某个空闲的线程。 一个线程同时只能执行一个任务,但可以同时向一个线程池提交多个任务。
    四种常见的线程池
    缓存线程池
    public class Demo13 {
    / 缓存线程池.
    (长度无限制)
    执行流程:
    1. 判断线程池是否存在空闲线程
    2. 存在则使用
    3. 不存在,则创建线程 并放入线程池, 然后使用
    /
    public static void main(String[] args) {
    //向线程池中加入新的任务(指挥线程执行新的任务)
    ExecutorService service = Executors.newCachedThreadPool();//创建缓存线程池
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    try {
    Thread.sleep(100);//让主线程休眠一秒钟 作用是为了让我们感受到上面三个线程已经执行完,缓存池里拥有了三个线程
    //此时再执行此任务,按理说有空闲的线程了,会使用这三个空闲的线程的其中一个来执行任务
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    }
    }
    定长线程池
    public class Demo14 {
    /*
    定长线程池.
    (长度是指定的数值)
    执行流程:
    1. 判断线程池是否存在空闲线程
    2. 存在则使用
    3. 不存在空闲线程,且线程池未满的情况下,则创建线程 并放入线程池, 然后使用
    4. 不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程
    /
    public static void main(String[] args) {
    ExecutorService service = Executors.newFixedThreadPool(2);//给定参数 固定长度 长度不允许扩容
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    try {
    Thread.sleep(3000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    // System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    try {
    Thread.sleep(3000);
    } catch (InterruptedException e) {
    e.printStackTrace();
    }
    // System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    service.execute(new Runnable() {
    @Override
    public void run() {
    //等待线程空闲以后才能继续执行
    //3s后再执行此
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    }
    }
    单线程线程池
    每一次执行都是单个线程,没有出现其它线程
    任务在执行完毕时代码并没有结束,因为线程池等着你给它传递任务。
    相当于你有一个用户线程没有关闭,那么你的用户程序就不会被关闭。一段时间以后也会自动关闭。
    public class Demo15 {
    /
    单线程线程池。
    执行流程:
    1.判断线程池的那个线程是否空闲
    2.空闲则使用
    3.不空闲则等待池中的单个线程池空闲后使用
    /
    public static void main(String[] args) {
    ExecutorService service = Executors.newSingleThreadExecutor();
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    service.execute(new Runnable() {
    @Override
    public void run() {
    System.out.println(Thread.currentThread().getName()+”锄禾日当午”);
    }
    });
    }
    }
    实现结果:
    pool-1-thread-1锄禾日当午
    pool-1-thread-1锄禾日当午
    pool-1-thread-1锄禾日当午
    pool-1-thread-1锄禾日当午
    周期定长线程池
    public class Demo16 {
    /

    周期任务 定长线程池。(特性与流程与定长线程池一样)
    执行流程:
    1.判断线程池是否存在空闲线程
    2.存在则使用
    3.不存在空闲线程,且线程池未满的情况下,则创建线程并放入线程池,然后使用。
    4.不存在空闲线程,且线程池已满的情况下,则等待线程池存在空闲线程

    周期性任务执行时:
    定时执行,当某个时机触发时,自动执行某任务。
    /
    public static void main(String[] args) {
    ScheduledExecutorService service = Executors.newScheduledThreadPool(2);
    // 1.定时执行一次
    /
    参数1.定时执行的任务
    参数2.时长数字(单位由参数3决定)
    参数3.时长数字的时间单位,TimeUnit的常量指定
    /
    // service.schedule(new Runnable() {
    // @Override
    // public void run() {
    // System.out.println(“锄禾日当午,此任务五秒后执行”);
    // }
    // },5, TimeUnit.SECONDS);
    /

    周期性执行任务
    参数1.任务
    参数2.延迟时长数字(第一次执行在什么时间以后)
    参数3.周期时长数字(往后每隔多久执行一次)
    参数4.时长数字的单位
    /
    service.scheduleAtFixedRate(new Runnable() {
    @Override
    public void run() {
    System.out.println(“汗滴禾下土”);
    }
    },5,1,TimeUnit.SECONDS);
    }
    }
    实现结果:
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    汗滴禾下土
    (每隔5s执行一次)……

    Lambda表达式

    public class Demo18 {
    public static void main(String[] args) {
    / //匿名内部类
    print(new MyMath() {
    @Override
    public int sum(int x, int y) {
    return x+y;
    }
    },100,200);
    /
    //lambda实现,就是把一个匿名内部类的类的部分都删掉就保留那一个抽象的方法,保留方法的参数部分,保留方法体
    print((int x,int y) -> {return x+y;},100,200);
    }
    //(抽象)接口不可以传对象,可以把此接口实现后,把实现类的对象传递过来
    public static void print(MyMath m,int x,int y){
    int num = m.sum(x,y);
    System.out.println(num);
    }
    static interface MyMath{//实现Lambda表达式需要注意此接口一定只有一个抽象方法
    //因为它只包含了一个方法,那么在实现此接口时一定是为了实现这个方法,才可以替换成一个函数传递式的lambda表达式
    int sum(int x,int y);
    }
    }