基本概念
1.什么是进程?
一个进程对应一个应用程序,例如,在Windows下启动一个word,在java开发环境下启动JVM,就表示启动了一个进程。现代计算机都是支持多进程的,在一个操作系统下可以同时启动多个进程。
2.多进程有什么用?
单进程计算机只能做一件事
一边玩游戏(游戏进程)一边听歌(音乐进程),对于单核计算机,游戏进程和音乐进程是同时运行的吗?不是。因为CPU在某一个时间点上只能做一件事情,计算机在两个进程间频繁的切换执行,切换速度极快,让人感觉是在同时运行。
多进程不是提高执行速度,而是提高CPU的使用率。
进程和进程间的内存是独立的
3.什么是线程?
线程是一个进程中的执行场景,一个进程可以启动多个线程。
多线程不是为了提高执行速度,是为了提高应用程序的使用率
线程和线程共享“堆内存和方法区内存”,栈内存是独立的,一个线程一个栈
4.java程序的运行原理?
java命令会启动java虚拟机,等同于启动了一个应用程序,表示启动了一个进程。该进程会自动启动一个主线程,然后主线程会自动调用某个类的main方法,所以main方法运行在主线程中
线程的创建和启动
1.第一种方式
继承Thread——>重写run方法
public class ThreadTest01 { public static void main(String[] args) { Thread t = new Thread01(); //创建线程 //启动线程 t.start(); //这段代码执行瞬间结束,告诉JVM再分配一个栈给t线程 //run()不需要程序员去调用,线程启动后自动调用run,如果直接调用run则不会开启新的线程 for (int i = 0 ; i < 100 ; i++){ System.out.println("main--->" + i); } //在多线程中,main方法结束只是主线程栈中没有方法栈帧了, //但是其他线程或者其他栈中可能还有 //所以,main方法结束,程序可能还在运行 } } class Thread01 extends Thread{ @Override public void run() { for (int i = 0 ; i<100 ; i++){ System.out.println("run--->" +i); } } }
一部分结果
main--->61
run--->70
main--->62
run--->71
main--->63
run--->72
run--->73
run--->74
2.第二种方式
写一个Runnable接口的实现类——>实现run方法
public class ThreadTest02 { public static void main(String[] args) { Thread t = new Thread(new Thread02()); t.start(); } } class Thread02 implements Runnable{ @Override public void run() { //do something } }
推荐第二种方式,因为实现接口还可以保留类的继承,因为java是单继承的
线程的生命周期
1.新建。new出线程
2.就绪。t.start(),该状态表示该线程有权利去获得CPU的时间片,当拿到时间片后就马上执行run方法,这个时候就进入运行状态。
3.运行。run方法执行。
4.阻塞。运行中可能遇到了阻塞时间,进入阻塞状态,等到阻塞接触后,会回到就绪状态
5.消亡。run方法执行结束
线程的调度与控制
JVM负责线程的调度,取得CPU的使用权。目前有两种调度模型,分时调度和抢占式调度,java采用抢占式调度。
分时调度:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
抢占式:优先级高的线程获得的CPU的使用权相对多些,如果优先级相同,则随机选一个
1.优先级
从1~10,默认5
2.sleep()
是一个静态方法,阻塞当前线程,腾出CPU让给其他线程
这个方法要捕获异常,注意Thread的run方法不抛出异常,所以在复写run方法之后,在run方法的声明位置不能使用throws关键字,所以run中的异常只能try/catch
如何打断线程的休眠?
public class SleepTest { public static void main(String[] args) throws Exception{ //依靠异常处理机制来终止了一个线程的休眠 //启动线程5s后打断休眠 Thread t = new Thread(new T()); t.start(); t.setName("t"); Thread.sleep(5000); t.interrupt(); System.out.println("sleep over"); } } class T implements Runnable{ public void run(){ try{ Thread.sleep(100000); System.out.println("hello world~"); }catch (InterruptedException e){ e.printStackTrace(); } for (int i=0 ; i < 10 ; i ++){ System.out.println(Thread.currentThread().getName() + "---->" + i); } } }
依靠异常处理机制来终止了一个线程的休眠,run里的hello world不会输出,下面的for会正常输出
一个终止线程的方法?
public class SleepTest { public static void main(String[] args) throws Exception{ //线程运行5s后终止 Processor p = new Processor(); Thread t = new Thread(p); t.setName("t"); t.start(); Thread.sleep(5000); p.run = false; System.out.println("main over"); } } class Processor implements Runnable{ boolean run = true; public void run(){ for(int i = 0 ; i<10 ; i++){ if (run){ try{ Thread.sleep(1000); }catch(Exception e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "---->" + i); }else { return; } } System.out.println("for over"); } }
3.yield
静态方法,给同一个优先级的线程让位,但是让位之间不固定,和sleep不同的是不能跟指定时间。
4.join
成员方法,线程的合并。
public class JoinTest { public static void main(String[] args) throws InterruptedException{ Thread t = new Thread(new JoinThread()); t.setName("t"); t.start(); //合并线程 t.join(); //t和主线程合并,也就是说两个栈空间变成了一个栈空间,变成单线程了 for (int i = 0 ; i < 10 ; i++){ System.out.println(Thread.currentThread().getName() + "--->" + i); } } } class JoinThread implements Runnable{ @Override public void run() { for (int i = 0 ; i<5 ; i++){ try{ Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "--->" + i); } } }
t--->0
t--->1
t--->2
t--->3
t--->4
main--->0
main--->1
main--->2
……
线程的同步!!!!!
关于异步编程模型和同步编程模型的区别
异步:t1执行t1的,t2执行t2的,两个线程谁也不等谁
同步:两个线程执行,t1必须等t2执行结束才能执行
1.为什么要引入线程同步?
为了数据安全,尽管应用程序的使用率降低,线程同步机制使程序变成了单线程
2.什么时候要同步?
1.多线程环境。2.多线程环境共享同一数据。3.共享的数据设计修改操作。<三个条件缺一不可>
3.synchronized
synchronized(this){ //括号里面放同步对象 //将需要同步的代码放在这里,这块代码只会有一个线程执行 }
原理:t1、t2两个线程,当t1遇到synchronized的时候就会去找()里面对象的对象锁,任何一个java对象上都有对象锁,其实就是每个对象都有1和0这个标识,如果找到了则进入代码块执行代码,代码块中代码执行完毕时归还对象锁,如果在还没执行完毕的时候,t2也执行到这里,遇到了这个关键字,但是找不到对象锁,所以只能等待对象锁的归还。
synchronized加到成员方法上,线程拿走的也是this的对象锁。不过这种写法就意味着整个方法里的代码都需要同步,可能造成不应该同步的代码也同步了,进一步降低了执行效率,所以不如上面那种方式控制得精确。
public synchronized void fun(){ }
查看源码可以发现StringBuffer的每个方法都有synchronized,所以StringBuffer是线程安全的。Vector,HashTable都是线程安全的。
类锁:类只有一个,所以类锁也只有一个
当synchronized添加到静态方法上,线程执行到此方法的时候会找类锁。
public class ThreadTest03 { public static void main(String[] args) throws Exception{ Thread t1 = new Thread(new Thread03()); Thread t2 = new Thread(new Thread03()); t1.setName("t1"); t2.setName("t2"); t1.start(); //保证t1先执行 Thread.sleep(1000); t2.start(); } } class Thread03 implements Runnable { @Override public void run() { if (Thread.currentThread().getName().equals("t1")) MyClass.fun1(); //通过类名直接调用,当然这里如果用对象的引用来调用,还是一样的结果,因为是静态方法 if (Thread.currentThread().getName().equals("t2")) MyClass.fun2(); } } class MyClass{ public synchronized static void fun1(){//类锁 try { Thread.sleep(5000); } catch (Exception e){ e.printStackTrace(); } System.out.println("fun1---->"); } //没加synchronized,所以不需要类锁,所以t2在执行fun2是不会等fun1的锁(类锁) //但是如果加上的话,就需要等t1执行完毕才能执行,因为类锁只有一个! public static void fun2() { System.out.println("fun2---->"); } }
4.死锁
比如说有一种情况,两个线程t1,t2。两个对象o1,o2。两个线程都要锁这两个对象,t1锁掉o1后,t2把o2锁了,之后t1想继续锁o2,就得等t2归还o2的锁,但是t2也想在锁到o2后继续锁o1,在没锁到o1的的时候是不会交出o2的锁的,于是这个局面就僵持住了。
public class ThreadTest04 { public static void main(String[] args) { Object o1 = new Object(); Object o2 = new Object(); //两个线程共享两个对象 Thread t1 = new Thread(new Thread05(o1 , o2)); Thread t2 = new Thread(new Thread06(o1 , o2)); t1.start(); t2.start(); } } class Thread05 implements Runnable { Object o1 ; Object o2 ; public Thread05(Object o1 , Object o2){ this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o1){ try { Thread.sleep(1000); //休眠保证o2被锁到 } catch (Exception e){ e.printStackTrace(); } synchronized (o2){ System.out.println("no dead~"); } } } } class Thread06 implements Runnable { Object o1 ; Object o2 ; public Thread06(Object o1 , Object o2){ this.o1 = o1; this.o2 = o2; } @Override public void run() { synchronized (o2){ try {//这里如果不休眠的话,不能保证一定死锁! Thread.sleep(1000); } catch (Exception e){ e.printStackTrace(); } synchronized (o1){ System.out.println("no dead~"); } } } }
5.举个同步的例子
public class TicketDemo { public static void main(String[] args) { SaleTicket saleTicket = new SaleTicket(); Thread t1 = new Thread (saleTicket , "t1"); Thread t2 = new Thread (saleTicket , "t2"); t1.start(); t2.start(); } } class SaleTicket implements Runnable { int num = 50; @Override public void run() { while (true){ try { Thread.sleep(50); }catch (InterruptedException e){ e.printStackTrace(); } if ( num <= 0) break; System.out.println(Thread.currentThread().getName() + "---已卖出--->" + num); num--; } } }
简单实现一个卖票的程序,两个线程共享一个资源---票,会出现类似这样的输出
窗口t2---卖出一张--->26
窗口t1---卖出一张--->24
窗口t2---卖出一张--->24
窗口t1---卖出一张--->22
窗口t2---卖出一张--->21
窗口t1---卖出一张--->20
有两个24显然是不对的,这时就需要将要卖票部分的代码做一个同步
synchronized (this){ if ( num <= 0) break; System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num); num--; }
窗口t1---卖出一张--->7
窗口t1---卖出一张--->6
窗口t2---卖出一张--->5
窗口t2---卖出一张--->4
窗口t1---卖出一张--->3
窗口t1---卖出一张--->2
窗口t2---卖出一张--->1
这样就正常了,下面换一个Lock的实现方法,lock定义成SaleTicket的成员变量
lock.lock(); if ( num <= 0) break; System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num); num--; lock.unlock();
输出是正常的,但是!!!有一个严重的问题,程序没有终止!!!为什么呢?考虑这样一种情况,t1锁上后开始往下执行,t2在锁那里等着,t1发现num<=0了,跳出循环,锁呢?并没有释放掉,那t2就要等到海枯石烂了。
所以这里要保证在需要同步的代码块执行完毕之后一定释放掉锁!!!这也是和synchronized的一个区别,synchronized是默认执行完后归还锁,是一种隐式的释放,lock是显示的。所以这种写法是不行的,要用 finally!
lock.lock(); try { if ( num <= 0) break; System.out.println("窗口" + Thread.currentThread().getName() + "---卖出一张--->" + num); num--; } finally { lock.unlock(); }
这样程序就正常结束了
比较下Lock和synchronized:
synchronized有两种用法:
1.同步代码块的形式
synchronized(对象){ //锁上(隐式) //要同步的代码块 }//释放锁(隐式)
2.同步方法
public synchronized void method(){ //this锁上(隐式) //要同步的代码 }//this释放(隐式)
守护线程
线程分两种:用户线程、守护线程。通常用到的就是用户线程,而例如java的垃圾回收器就是一个守护线程。只有全部用户线程都结束了,守护线程才会结束。
JVM启动一个主线程,还有一个垃圾回收线程,两个不同的栈。
public class DaemonThread { public static void main(String[] args) throws Exception{ Thread t = new Thread(new Daemon()); t.setName("t"); t.setDaemon(true); t.start(); for(int i = 0 ; i < 10 ; i++){ System.out.println(Thread.currentThread().getName() + "--->" + i); Thread.sleep(1000); } } } class Daemon implements Runnable{ public void run(){ int i = 0; while(true){ System.out.println(Thread.currentThread().getName() + "--->" + i++); try{ Thread.sleep(1000); }catch (Exception e){ e.printStackTrace(); } } } }
上面代码中,线程t里面的run()是一个无限循环,但是这里将他设置成一个守护线程,而用户线程只有主线程,所以当主线程结束后,守护线程会自动结束,不会无限循环下去。
定时器
作用:每隔一段固定的时间执行一段代码
public class TimerTest { public static void main(String[] args) throws Exception{ Timer t = new Timer(); t.schedule( new LogTask(), new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2016-09-27 21:22:00") , 3*1000); } } class LogTask extends TimerTask{ @Override public void run() { System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date())); } }
2016-09-27 21:22:00
2016-09-27 21:22:03
2016-09-27 21:22:06
2016-09-27 21:22:09
sleep(),wait(),notify(),yield()总结
sleep不会归还对象锁,需要捕获异常,可以使低优先级的线程得到执行的机会,当然也可以让同优先级、高优先级的线程有执行的机会。
yield与sleep类似,只是不能指定时间,只能让同优先级的线程有执行的机会
wait当前线程暂停并释放对象锁,当前线程被放入对象等待池中,当调用notify()方法后,将从对象的等待池中移走一个任意的线程并放到锁标志等待池中,只有锁标志等待池中线程能够获取锁标志。如果锁标志等待池中没有线程,则notify()不起作用。
wait()和sleep()都可以通过interrupt()方法 打断线程的暂停状态 ,从而使线程立刻抛出InterruptedException,在catch() {} 中直接return即可安全地结束线程。