进程:正在运行的程序。也就是表明了程序锁占用的内存区域。
线程:线程(thread)是操做系统可以进行运算调度的最小单位;一个进程能够开启多个线程。
进程和线程的区别:
一个软件运行至少依赖一个进程。
一个进程的运行至少依赖一个线程。面试
多线程:多线程扩展了多进程的概念,使得同一个进程能够同时并发处理多个任务。编程
线程生命周期,总共有五种状态:
1) 新建状态(New):当线程对象对建立后,即进入了新建状态,如:Thread t = new MyThread();
2) 就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经作好了准备,随时等待CPU调度执行,并非说执行了t.start()此线程当即就会执行;
3) 运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的惟一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;
4) 阻塞状态(Blocked):处于运行状态中的线程因为某种缘由,暂时放弃对CPU的使用权,中止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;
5) 根据阻塞产生的缘由不一样,阻塞状态又能够分为三种:设计模式
a) 等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态; b) 同步阻塞:线程在获取synchronized同步锁失败(由于锁被其它线程所占用),它会进入同步阻塞状态; c) 其余阻塞:经过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程从新转入就绪状态。
6) 死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。安全
建立对象:多线程
Thread() 分配新的 Thread 对象。 Thread(Runnable target) 分配新的 Thread 对象。 Thread(Runnable target, String name) 分配新的 Thread 对象。 Thread(String name) 分配新的 Thread 对象。
经常使用方法:并发
long getId() 返回该线程的标识符。 String getName() 返回该线程的名称。 void run() 若是该线程是使用独立的 Runnable 运行对象构造的,则调用该 Runnable 对象的 run 方法;不然,该方法不执行任何操做并返回。 void setName(String name) 改变线程名称,使之与参数 name 相同。 static void sleep(long millis) 在指定的毫秒数内让当前正在执行的线程休眠(暂停执行), void start() 使该线程开始执行;Java 虚拟机调用该线程的 run 方法。 void stop() 终止线程。
测试:app
package cn.tedu.thread; //测试 Thread类 public class Test2_Thread { public static void main(String[] args) { //1,建立对象 MyThread t = new MyThread();//新建状态 //2,开启线程 // t.run(); t.setName("线程1");//修改线程名 t.start();//可运行状态 //3,run()和start()有什么区别? //run()和start()都能执行任务,可是run()执行时就只是一个普通的方法调用没有多线程并发抢占的效果 //--模拟多线程-- MyThread t2 = new MyThread(); // t2.run(); t.setName("线程2");//修改线程名 t2.start(); /* 4,多线程的执行结果具备随机性 Thread-0---0 Thread-1---0 Thread-0---1 Thread-1---1 Thread-0---2 Thread-1---2 Thread-1---3*/ } } //1,继承Thread类,实现多线程编程 class MyThread extends Thread { //2,在多线程编程里,把全部的业务放入--重写的run()里--generate--override methods.. //--需求:重复的打印10次线程名称 @Override public void run() {//运行状态 for (int i = 0; i < 10; i++) {//fori System.out.println(super.getName()+"---"+i);//getName()--获取线程名称 } }//结束状态 }
概述:若是本身的类已经extends另外一个类,就没法多继承,此时,能够实现一个Runnable接口。ide
经常使用方法:性能
void run() 使用实现接口 Runnable 的对象建立一个线程时,启动该线程将致使在独立执行的线程中调用对象的 run 方法。
测试:测试
package cn.tedu.thread; //测试 Runnable接口 public class Test3_Runnable { public static void main(String[] args) { //1,建立目标对象 MyRunnable target = new MyRunnable(); //2,把要执行的目标 和Thread绑定关系 Thread t = new Thread(target); t.setName("猪猪侠"); //3,启动线程 t.start(); //4,模拟多线程 Thread t2 = new Thread(target); t2.setName("蜘蛛侠"); t2.start(); /* 5,多线程的随机性 Thread-0===0 Thread-0===1 Thread-1===0 Thread-1===1 Thread-1===2 Thread-1===3 Thread-1===4 Thread-0===2 Thread-0===3 */ } } //1,实现Runnable接口,实现多线程编程 class MyRunnable implements Runnable{ //2,若是有本身的业务,须要把业务 -- 放在重写的run()里 //--需求:打印10次线程名称 @Override public void run() { for (int i = 0; i < 20; i++) { //Thread.currentThread()--获取正在执行任务的线程对象 //getName()--获取线程的名字 //3,Thread.currentThread().getName() -- 获取当前执行任务的线程对象的 名字 System.out.println( Thread.currentThread().getName() +"==="+i ); } } }
建立步骤:
1.建立Callable接口的实现类,并重写call()方法,该方法做为线程的执行体,并有返回值。
2.建立Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。
3.建立Thread,调用start()方法。
4.但是有FutureTask.get()获取call()的返回值。
public class TestCallable { public static void main(String[] args) throws InterruptedException, ExecutionException { CallableDemo callableDemo = new CallableDemo(); FutureTask futureTask = new FutureTask(callableDemo); new Thread(futureTask).start();//启动线程对象,并启动线程 //Integer a = (Integer) futureTask.get(); //经过get()获取返回值 for (int j = 0; j < 10; j++) { System.out.println("main:"+j); } } } class CallableDemo implements Callable{ @Override public Integer call() throws Exception { int i =0; for (; i < 10; i++) { System.out.println("线程:"+i); } return i; } }
结果:
main:0 线程:0 main:1 线程:1 main:2 线程:2 main:3 线程:3 线程:4 线程:5 线程:6 线程:7 线程:8 线程:9 main:4 main:5 main:6 main:7 main:8 main:9
线程池是池化思想的一种应用,都是经过复用对象,以减小因建立和释放对象所带来的资源消耗,进而来提高系统性能。
ThreadPoolExecutor、AbstractExecutorService、ExecutorService和Executor之间的关系:
推荐使用ThreadPoolExecutor建立线程
// 建立线程池 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(CORE_POOL_SIZE, MAXIMUM_POOL_SIZE, KEEP_ALIVE, TimeUnit.SECONDS, sPoolWorkQueue, sThreadFactory); // 向线程池提交任务 threadPool.execute(new Runnable() { @Override public void run() { ... // 线程执行的任务 } }); // 关闭线程池 threadPool.shutdown(); // 设置线程池的状态为SHUTDOWN,而后中断全部没有正在执行任务的线程 threadPool.shutdownNow(); // 设置线程池的状态为 STOP,而后尝试中止全部的正在执行或暂停任务的线程,并返回等待执行任务的列表
4种线程池的建立方法:
线程通讯的两种简单模型,共享内存和消息传递。
使用volatile关键字定义全局变量,当变量发生变化时,线程可以感应并执行相应的业务。采用了共享内存的思想。
在synchronized修饰的同步方法或代码块中使用Object类提供的wait(),notify()和notifyAll()3个方法进行线程通讯。
1.wait():致使当前线程等待,直到其余线程调用该同步监视器的notify()方法或notifyAll()方法来唤醒该线程。wait()释放锁。 2.notify():唤醒在此同步监视器上等待的单个线程。notify()不释放锁。 3.notifyAll():唤醒在此同步监视器上等待的全部线程。
悲观锁:仍是像它的名字同样,对于并发间操做产生的线程安全问题持悲观状态,悲观锁认为竞争老是会发生,所以每次对某资源进行操做时,都会持有一个独占的锁,就像synchronized,无论三七二十一,直接上了锁就操做资源了。
乐观锁:就像它的名字同样,对于并发间操做产生的线程安全问题持乐观状态,乐观锁认为竞争不老是会发生,所以它不须要持有锁,将比较-替换这两个动做做为一个原子操做尝试去修改内存中的变量,若是失败则表示发生冲突,那么就应该有相应的重试逻辑。
synchronized 互斥锁(悲观锁,有罪假设)
采用synchronized修饰符实现的同步机制叫作互斥锁机制,它所得到的锁叫作互斥锁。每一个对象都有一个monitor(锁标记),当线程拥有这个锁标记时才能访问这个资源,没有锁标记便进入锁池。任何一个对象系统都会为其建立一个互斥锁,这个锁是为了分配给线程的,防止打断原子操做。每一个对象的锁只能分配给一个线程,所以叫作互斥锁。
ReentrantReadWriteLock 读写锁(乐观锁,无罪假设)
ReentrantLock是排他锁,排他锁在同一时刻仅有一个线程能够进行访问,实际上独占锁是一种相对比较保守的锁策略,在这种状况下任何“读/读”、“读/写”、“写/写”操做都不能同时发生,这在必定程度上下降了吞吐量。然而读操做之间不存在数据竞争问题,若是”读/读”操做可以以共享锁的方式进行,那会进一步提高性能。所以引入了ReentrantReadWriteLock,顾名思义,ReentrantReadWriteLock是Reentrant(可重入)Read(读)Write(写)Lock(锁),咱们下面称它为读写锁。
读写锁内部又分为读锁和写锁,读锁能够在没有写锁的时候被多个线程同时持有,写锁是独占的。读锁和写锁分离从而提高程序性能,读写锁主要应用于读多写少的场景。
把共享资源,用锁锁起来,谁有钥匙,谁去用共享资源,没钥匙的就排队等待。
用法:
经过synchronized关键字实现同步锁
用在方法上:synchronized public StringBuffer append(String str) 用在代码块上:synchronized( 锁对象 ){ 须要同步的代码;}
用在方法上,使用的锁对象是默认的this.;
用在代码块上,锁的对象是任意的。
public class Test6_Singleton { public static void main(String[] args) { MySingle m = MySingle.getSingle(); MySingle m2 = MySingle.getSingle(); //==若是比较的是两个引用类型的变量?比较的是地址值 System.out.println(m==m2); } } //建立本身的单例程序 class MySingle { //1,私有化构造方法,不让外界随意new private MySingle() {} //2,在类的内部,提供一个已经建立好的对象 //static是由于 静态资源getSingle()只能调用静态 static private MySingle single = new MySingle(); //3,对外提供一个全局访问点 //static 是由于,想要访问get()能够建立对象访问,可是目前已经不容许建立对象了。只能经过类名调用,就得修饰。 static public MySingle getSingle(){ //把内部建立好的对象返回调用位置 return single; } }
//面试重点: //一、延迟加载思想:是指不会第一时间就把对象建立好来占用内存,而是何时用何时建立 //二、线程安全问题:是指共享资源有线程并发的数据隐患,加同步锁,锁方法,也能够锁代码块 public class Test7_Single2 { public static void main(String[] args) { MySingleton my = MySingleton.getMy(); MySingleton my2 = MySingleton.getMy(); System.out.println(my==my2); } } // 建立类 class MySingleton { // 1,私有化构造方法 -- 控制外界new的权利 private MySingleton() { } // 2,在类的内部建立好对象 -- 延迟加载!! static private MySingleton my; // static Object obj = new Object(); // 3,提供全局访问点 // 问题:程序中有共享资源my,而且有多条语句(3条)操做了共享资源,此时,my共享资源必定会存在多线程编程的数据安全隐患 // 解决方案就是加 同步锁 。 //若是用同步代码块须要肯定锁的位置? 锁的对象?因为方法中的全部代码都被同步了,能够直接变成同步方法。 synchronized static public MySingleton getMy() { // synchronized (obj) {//同步代码块:静态区域内,不能是用this关键字?由于加载的前后顺序问题 if (my == null) {// 说明没new过,保存的是默认值null my = new MySingleton();// 须要时才会建立对象,不须要也不会提早就开始占用内存。 } return my; // } } }