进程是操作系统的基础,是一个程序在一个数据集上运行的过程,也是系统进行资源分配和调度的基本单位。我们可以认为一个进程就是一个应用程序。
线程是操作系统调度的最小单元,程序执行的最小单位,在一个进程中可以创建多个线程。线程拥有独立的堆栈空间,可以共享内存变量。
Java的线程在运行的生命周期中可能会处理6种不同的状态
线程创建后,调用Thread的start方法开始进入运行状态,执行wait方法后,进入等待状态,这个时候需要其他线程的通知才能重新返回运行状态,超时等待相当于在等待上加了一个时间,当时间到的时候,线程自动返回运行状态,当线程调用到同步方法时,如果没有获得锁,则进入阻塞状态,获取到锁后会进入到运行状态,当线程执行完或者遇到意外的时候都会进入到终止状态。
//定义Thread子类 public class MyThread extends Thread{//继承Thread类 public void run(){ //重写run方法 } } public class Main { public static void main(String[] args){ new MyThread().start();//创建并启动线程 } }
2.实现Runnable接口并实现run方法
public class RunnableThreadTest implements Runnable { public void run() { //集体逻辑 } public static void main(String[] args) { RunnableThreadTest runnble = new RunnableThreadTest(); new Thread(runnble,"新线程").start(); } }
3.实现Callable接口
Callable接口与Runnable接口功能类似,不过提供了更强大的功能。主要有以下几点
public class CallableTest{ //创建callable线程 public static class MyCallable implements Callable{ public String call() throws Execption{ return "hello"; } } public static void main(String args[]){ MyCallable callable = new MyCallable(); ExecutorService executorService = Executors.newSingleThreadPool(); Future future = executorService.submit(callable); try{ //等待线程结果 System.out.println(future.get()); }catch(Execption e){ e.printStackTrace } } }
线程的中断
一个线程可以调用其interrupt方法来中断线程。线程被中断了并不一定是被终止了,被终止是run方法执行完毕或者run方法中发生了异常而导致的。线程中断了还可以通过Thread.interrupted()方法进行复位。
想要安全的终止一个线程,可以在执行逻辑之前判断当前线程是不是中断的状态通过Thread.currentThread().idInterrupted()。或者通过一个boolean变量来判断,在Runnable中定义一个boolean变量使用volatile变量来修饰,vloatile可以保证这个变量的原子性,当别的线程修改这个变量的时候,所有的线程都会感受到这个变量的变化。
线程同步
在多线程应用中,如果两个或两个以上的线程同时对同一个对象进行修改的时候,就会产生问题,比如我仓库有一部手机,两个人在同一时间都下了订单并且都成功了,那手机给谁就不知道了。解决办法就是,当一个线程对此对象进行修改的时候,就给它一把锁,别的线程进不来,当它完成任务之后,在把锁给下一个进来的线程。在java中这个锁之一就是synchronized关键字。
使用synchronized可以给一个方法加上锁,wait()方法可以将一个线程添加到等待集中,notify或者notifyAll可以解除等待线程的阻塞状态。例如
public synchronized void add()throws InterruptedException{ //等待 for(int i = 0; i < 10;i++){ if(i == 3){ wait(); System.out.println("wait"); } } //进行一系列的操作 .... //唤醒 notifyAll(); System.out.println("notifyAll"); }
也可以给一个对象上锁
public class MyRunnable implements Runnable { private Object flag; private String threadName; public MyThread(Object flag,String threadName) { this.flag = flag; this.threadName = threadName; } @Override public void run(){ try{ for(int i = 0; i < 10;i++){ if(i == 3){ synchronized (this.flag){ //进入等待状态 this.flag.wait(); } } System.out.println(this.threadName + " " + i); Thread.sleep(1000); } } catch(InterruptedException e){ e.printStackTrace(); } } } public class TestMain { public static void main(String[] args) { Object object = new Object(); MyRunnable myRunnable = new MyRunnable(object,"Runnable"); Thread thread = new Thread(myRunnable1); thread.start(); try{ Thread.sleep(6000); System.out.println("6秒后唤醒线程"); synchronized (object){ //唤醒线程 object.notify(); } System.in.read(); } catch(InterruptedException e){ e.printStackTrace(); } catch(IOException e){ e.printStackTrace(); } } }
synchronized, wait() ,notify() 必须是操控的同一个对象
除了synchronized,java还提供了一个重用锁ReentrantLock。比synchronized写起来麻烦,不过功能更加强大。
ReentrantLock lock = new ReentrantLock(); lock.lock(); try { .... } finally { lock.unlock(); // 看这里就可以 }
把unlock放在finally中是很有必要的,如果发生了异常确保锁是可以被释放的。synchronized中有wait(),notigy()等方法,在ReentrantLock需要配合Conditond 使用,Conditon提供了以下方法:
public interface Condition { void await() throws InterruptedException; // 类似于Object.wait() void awaitUninterruptibly(); // 与await()相同,但不会再等待过程中响应中断 long awaitNanos(long nanosTimeout) throws InterruptedException; boolean await(long time, TimeUnit unit) throws InterruptedException; boolean awaitUntil(Date deadline) throws InterruptedException; void signal(); // 类似于Obejct.notify() void signalAll(); }
例子
public class Test { private Lock lock = new ReentrantLock(); public Condition condition = lock.newCondition(); public void await() { try { lock.lock(); System.out.println(" await时间为" + System.currentTimeMillis()); condition.await(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } public void signal() { try { lock.lock(); System.out.println("signal时间为" + System.currentTimeMillis()); condition.signal(); } finally { lock.unlock(); } } public class MyThread extends Thread { private Test test; public ThreadA(Test test) { super(); this.test = test; } @Override public void run() { //等待 test.await(); } } public static void main(String[] args) throws InterruptedException { Test test = new Test(); MyThread a = new MyThread(test); a.start(); Thread.sleep(3000); //唤醒 service.signal(); }
有时候如果只是给一个实例域使用同步的话,使用synchronize开销有点大,这个时候使用volatitle关键字修饰一个实例也可以做到线程安全。
当一个变量被volatitle关键字修饰以后,它就具备了两种含义:
Java中使用 堆内存 来存储对象的实例,堆内存是可以被所有线程共享的内存区域,因此它是内存可见的。
局部变量和方法定义的参数是不会再线程之间共享的,他们之间是内存不可见的,不受内存模型的影响。
Java的内存模型定义了线程和主存之间的抽象关系,线程之间的共享变量存储在主存当中,每个线程还有一个私有的本地内存(这是一个抽象的概念,不真实存在),本地内存中存储了该线程共享变量的副本,Java线程模型控制线程之间的通信,它决定一个线程对主存共享变量的写入何时对其他线程可见。
例如线程A和线程B之间要通信的话,需要经历两个步骤
比如 int i = 5
执行此语句的线程,必须先在自己的工作线程中对变量i所在的缓存进行赋值操作,然后在写入到主存当中,而不是直接把3写入到主存中。
volatitle关键字可以保证变量的可见性和有序性,但是不能保证变量的原子性。所以一般来说使用volatitle的时候需要具备以下条件
1 对变量的操作不依赖于当前的值
2 该变量没有包含在具有其他变量的不变式中
1 状态标记变量
volatile boolean flag = false; while(!flag){ doSomething(); } public void setFlag() { flag = true; }
2 写单例的时候 双重校验
class Singleton{ private volatile static Singleton instance = null; private Singleton() {} public static Singleton getInstance() { if(instance==null) { synchronized (Singleton.class) { if(instance==null) instance = new Singleton(); } } return instance; } }