1、并发与并行编程
并发是同一时间段,多个任务都在执行(单位时间内不必定同时执行);多线程
并行是单位时间内,多个任务同时执行。并发
并发的关键是你有处理多个任务的能力,不必定要同时。 而并行的关键是你有同时处理多个任务的能力。 ide
2、线程与进程函数
线程是程序执行中一个单一的顺序控制流程,是程序执行流的最小单元,是处理器调度和分派的基本单位。一个进程能够有一个或多个线程,各个线程之间共享程序的内存空间(也就是所在进程的内存空间) ;测试
进程 是一个具备必定独立功能的程序在一个数据集上的一次动态执行的过程,是操做系统进行资源分配和调度的一个独立单位,是应用程序运行的载体。this
3、建立线程spa
一、继承Thread类操作系统
重写run方法:使用继承方式的好处是,在run()方法内获取当前线程直接使用this就能够了,无须使用Thread.currentThread()方法;很差的地方是Java不支持多继承,若是继承了Thread类,那么就不能再继承其余类。另外任务与代码没有分离,当多个线程执行同样的任务时须要多份任务代码。.net
public class ThreadRuning extends Thread{
public ThreadRuning(String name){
//重写构造,能够对线程添加名字
super(name);
}
@Override
public void run() {
while(true){
System.out.println("good time");
//在run方法里,this表明当前线程
System.out.println(this);
}
}
public static void main(String[] args){
ThreadRuning threadRuning = new ThreadRuning("1111");
threadRuning.start();
}
}
二、实现Runable接口
实现run方法:解决继承Thread的缺点,没有返回值
public class RunableTest implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("good time");
}
}
public static void main(String[] args) {
RunableTest runableTest1 = new RunableTest();
RunableTest runableTest2 = new RunableTest();
new Thread(runableTest1).start();
new Thread(runableTest2).start();
}
}
三、实现Callable接口
实现call方法:
public class CallTest implements Callable {
@Override
public Object call() throws Exception {
return "hello world";
}
public static void main(String[] args){
FutureTask<String> futureTask = new FutureTask<String>(new CallTest());
new Thread(futureTask).start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
使用继承方式的好处是方便传参,你能够在子类里面添加成员变量,经过set方法设置参数或者经过构造函数进行传递,而若是使用Runnable方式,则只能使用主线程里面被声明为final的变量。很差的地方是Java不支持多继承,若是继承了Thread类,那么子类不能再继承其余类,而Runable则没有这个限制。前两种方式都没办法拿到任务的返回结果,可是Callable方式能够
4、线程的生命周期和状态
Java 线程在运行的生命周期中的指定时刻只可能指定处于下面几种不一样状态的其中一个状态:
新建状态:
使用 new 关键字和 Thread 类或其子类创建一个线程对象后,该线程对象就处于新建状态。它保持这个状态直到程序 start() 这个线程。
就绪状态:
当线程对象调用了start()方法以后,该线程就进入就绪状态。就绪状态的线程处于就绪队列中,要等待JVM里线程调度器的调度。
运行状态:
若是就绪状态的线程获取 CPU 资源,就能够执行 run(),此时线程便处于运行状态。处于运行状态的线程最为复杂,它能够变为阻塞状态、就绪状态和死亡状态。
阻塞状态:
若是一个线程执行了sleep(睡眠)、suspend(挂起)等方法,失去所占用资源以后,该线程就从运行状态进入阻塞状态。在睡眠时间已到或得到设备资源后能够从新进入就绪状态。能够分为三种:
等待阻塞:运行状态中的线程执行 wait() 方法,使线程进入到等待阻塞状态。
同步阻塞:线程在获取 synchronized 同步锁失败(由于同步锁被其余线程占用)。
其余阻塞:经过调用线程的 sleep() 或 join() 发出了 I/O 请求时,线程就会进入到阻塞状态。当sleep() 状态超时,join() 等待线程终止或超时,或者 I/O 处理完毕,线程从新转入就绪状态。
死亡状态:
一个运行状态的线程完成任务或者其余终止条件发生时,该线程就切换到终止状态。
5、线程死锁
死锁:两个或者两个以上的线程在执行的过程当中,因争夺资源产生的一种互相等待的现象。
以下代码(代码源自《Java多线程编程核心技术》):
public class DeadThreadDemo implements Runnable{
public String username;
public Object lock1 = new Object();
public Object lock2 = new Object();
public void setFlag(String username) {
this.username = username;
}
@Override
public void run(){
if(username.equals("a")) {
synchronized (lock1) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
}catch(InterruptedException e) {
e.printStackTrace();
}
synchronized (lock2) {
System.out.println("按 lock1->lock2代码 顺序执行了");
}
}
}
if(username.equals("b")) {
synchronized (lock2) {
try {
System.out.println("username = " + username);
Thread.sleep(3000);
}catch(InterruptedException e) {
e.printStackTrace();
}
synchronized (lock1) {
System.out.println("按lock2->lock1代码顺序执行了");
}
}
}
}
}
测试类:
public class DeadThreadTest {
public static void main(String[] args) {
try {
DeadThreadDemo dtd1 = new DeadThreadDemo();
dtd1.setFlag("a");
Thread thread1 = new Thread(dtd1);
thread1.start();
Thread.sleep(100);
dtd1.setFlag("b");
Thread thread2 = new Thread(dtd1);
thread2.start();
}catch(InterruptedException e) {
e.printStackTrace();
}
}
}
输出:
username = a
username = b
线程 a 经过 synchronized (lock1) 得到 lock1 的监视器锁,而后经过thread.sleap(3000); 让线程 a 休眠 3s 为的是让线程 b 获得执行而后获取到 lock2 的监视器锁。线程 a 和线程 b 休眠结束了都开始企图请求获取对方的资源,而后这两个线程就会陷入互相等待的状态,这也就产生了死锁。上面的例子符合产生死锁的四个必要条件。
死锁产生的四个条件:
互斥条件: 该资源任意一个时刻只由一个线程占用;
请求与保持条件:一个线程因请求资源而阻塞,对已得到的资源保持不放;
不剥夺条件:线程已经得到的资源在未使用完以前不能被其余线程强行剥夺,只由本身使用完毕后才释放资源;
循环等待条件:若干线程之间造成一种头尾相接的循环等待资源关系。
避免死锁的四个方法:
破坏互斥条件:这个条件咱们没有办法破坏,由于咱们用锁自己就是想让他们互斥的(临界资源须要互斥访问)。 破坏请求与保持条件:一次性申请全部的资源 破坏不剥夺条件:占用部分资源的线程进一步申请其余资源时,若是申请不到,能够主动释放它占有的资源。 破坏循环等待条件:靠按顺序申请资源来预防。按照某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。