1.进程和线程(java线程内存模型,线程、工作内存、主内存)
进程:系统调度程序的过程。每个进程有自己内存空间
线程:在进程中程序的执行路径。cup的最小执行单位。只能cup为线程分配一些属于进程的内存空间(线程的内存是:cpu的寄存器和高速缓存的抽象描述),错误的理解线程未执行完的他的内存一直存在,应该是cpu赋予它的内存,重复收回和分配的过程。
2.线程创建的2种方式:继承thread或者实现runnable接口。都有重写run()方法。(thread实现了runnable接口,相当于代理runnable)
因为单继承原则,继承没有实现接口方式灵活。
3.线程第三种创建方式(并发包下)实现callable接口线程可以有返回值,可抛出异常。(简介:)
class Race implements Callable<Integer> { int n; boolean flag = true; @Override public Integer call() throws Exception { while (flag) { n += 10; } return n; }
ExecutorService exe = Executors.newFixedThreadPool(1); Race tortoise = new Race(); // 获取返回值 Future<Integer> submit = exe.submit(tortoise); Thread.sleep(300); //结束循环 tortoise.flag = false; Integer i = submit.get(); System.out.println(i); exe.shutdown();
4.线程的状态:
新生状态—就绪状态--运行状态--阻塞状态--结束状态。
新生:线程创建(new)分配内存空间。调用start()进入就绪状态。
就绪:拥有运行的条件,等待cpu的执行。处于就绪队列
运行:运行run(),运行完任务结束,或者等待其他资源阻塞状态,或者分配时间未执行完,就绪状态下次执行。
阻塞:运行时执行sleep()或者等待i/o等其他资源,释放cpu,阻塞条件结束,进入就绪状态
结束:任务结束,或者被强制结束线程(strop,destroy,会出现异常,并且不会释放锁)
阻塞:
5.合并线程:
join();等待该线程结束。(在线程加入主线程(或者是所在外线程)位置(join()),阻塞主线程之后的执行,等该线程结束主线程再执行。(join位置之前的部分与线程竞争执行)
join(int t):只等待该线程t时间。
class JoinTest extends Thread { public void run() { //Thread.sleep(3333); int i = 0; while (i < 11111) { System.out.println(i++); }} public static void main(String[] args) throws InterruptedException { JoinTest j = new JoinTest(); j.start(); for (int i = 0; i < 11; i++) { if (i == 4) { j.join(); } System.out.println("main()" + i); }
输出:
... 11 main()3 .. 11110 main()4 main()5 ... 也可能是://并不能保证谁先执行谁后执行。只是在外线程一直执行到join()位置如果该线程未执行完外线程才阻塞,让该线程先执行完。(个人理解) ... 11110 main()2 main()3 ...
6.Thread.yield():静态方法,暂停当前线程当次执行,让出cpu给其他线程,下次继续执行之后的。
i=0;
while(i<100){
i++;
if (i == 4) { Thread.yield(); }
7.Thread.sleep(int s):不释放cpu,暂停执行。一般用于时间相关的操作(延迟/定时)
7.1.wait(),释放锁等待。notify()/notifyall()唤醒。
8.常见的方法:
JoinTest j = new JoinTest();//已经继承Thread j.isAlive();//线程是是否是活得 j.getName();//得到线程名字 j.setName("ThreadTest"); //优先级高并不保证先执行,只是提高执行概率。 j.setPriority(Thread.MAX_PRIORITY);//设置优先级 j.getPriority();//优先级
Thread currentThread = Thread.currentThread();//获取当前线程
9.同步:sychronized,同步必须保证共享数据是在同一个对象锁中。
有如下说法(Synchronized的内存可见性)
在单一线程中,只要重排序不会影响到程序的执行结果,那么就不能保证其中的操作一定按照程序写定的顺序执行,即使重排序可能会对其它线程产生明显的影响。
在Java内存模型下,每个线程都有它自己的工作内存(主要是CPU的cache或寄存器),它对变量的操作都在自己的工作内存中进行,而线程之间的通信则是通过主存和线程的工作内存之间的同步来实现的。
导致共享数据出错的两个问题:重排序;共享数据及时更新。
synchronized(M){//}的使用: 当ThreadA释放锁M时,它所写过的变量(存在它工作内存中的)都会同步到主存中,而当ThreadB在申请同一个锁M时,ThreadB的工作内存会被设置为无效,然 后ThreadB会重新从主存中加载它要访问的变量到它的工作内存中(是ThreadA中修改过的最新的值)。通过这样的方式来实现ThreadA到ThreadB的线程间的通信。
可以用于单例模式保证对象的唯一:分为懒汉和恶汉模式
对于静态内部类调用其关联的静态方法时候才加载。(可以实现延迟加载的效果)
9.1 synchiromized同步的两种方式:
class A{ int i; Object o=new Object(); //同步方法: void synchironized increase(){ i++;//这个操作其实有三步,从内存中获取,在cpu的寄存器中计算,然后放到cpu的缓存或者写回内存。
//多线程的问题也出在此处,可能获取到相同值;可能计算后没有及时写回去。 //同步块 void f(){ synchronized(o){ i++; //线程方法 public void run() { for (int i = 0; i < 10; i++) { a.increase(); }
9.2.1 同步方法的锁地其实就所对应的对象(this):
synchromized f(){...} //等价于: f(){ synchronized(this){...} }
9.2.2 静态同步方法锁地其实就是所对应的字节码对象(.class)
static void synchromized f(){...} //等价于: f(){ synchronized(.class){...} }
9.3 注意:一些错误的理解:
1.class只看成一份字节码。其实.class因该是包含字节码的对象,字节码(代码)只是其中部分。 2.为了同步静态变量,锁.class,是因为静态成员需要从字节码中取出来的,类的创建也需要字节码。其实两者没有必然联系。之所以锁字节码对象关键是只有该类的字节码对象必然优先存在于这些静态成员。并且是最接近的。别的对象或者字节码并不能保证存在时间。当然也可以用一些系统对象,只是这样就增大了锁的范围,并不能保证别的地方对系统对象锁的使用。而且需要再次保证这些共享静态变量同步的时候,别的人员需要知道上次是锁定是哪个对象。
9.4 这就有个疑问了该锁什么对象好(Object? this? class?)
因为所有对象都可以成为锁。该锁哪个对象。一般不建议锁Object对象(局限是:需要明确所有共享数据修改的地方,多人开发中,实际却是后续出现再次共享数据修改,别人未必知道锁的是Obect).所以通常将关系最近的对象设置成锁,成员数据就该对象this.静态就是字节码对象(.class);
9.5用同步构建一种有序的重复读写
public class A { int i=0; boolean flag; void add() {i++;} int get() {return i;}
public static void main(String[] args) throws InterruptedException { A a = new A(); Thread t = new Thread(new ReadRun(a)); Thread t2 = new Thread(new WriteRun(a)); Thread t3 = new Thread(new ReadRun(a)); Thread t4 = new Thread(new WriteRun(a)); t.start(); t2.start(); t3.start(); t4.start(); } } class WriteRun implements Runnable { A a; int n;//无用的执行次数 public WriteRun(A a) { this.a = a; } @Override public void run() { while (true) { synchronized (a) { if (a.flag) { a.add(); System.out.println("add" + a.i + "---" + Thread.currentThread().getName()+"---无用"+n); a.flag = false; } else { n++; } } } } } class ReadRun implements Runnable { A a; int n;//无用的执行次数 public ReadRun(A a) { this.a = a; } @Override public void run() { while (true) {// 通过循环被动执行满足条件的代码 synchronized (a) { if (!a.flag) { int i = a.get(); System.out.println("get----" + i + "---" + Thread.currentThread().getName()+"---无用"+n); a.flag = true; } else { n++; } } } } }
//截取一段输出可以看出,没必要的执行占了大多数
add48765---Thread-1---无用2018186 get----48765---Thread-0---无用1989833 add48766---Thread-3---无用1960252 get----48766---Thread-2---无用2001570
缺点是:线程如果没有阻塞,一直在运行,重复地先获取锁,然后被动的判断标识,反复是放弃执行,还是满足条件执行。
下面wait,notify实现同样的功能,优点是线程不满足条件判断时候,变成等待线程(也就不需反复被动判断)不再竞争cpu资源和锁。
10.通知唤醒机制:(Object类的wait() notify/notifyall)
wait/notify/notifyAll都必须在同步中使用,因为都是对持有对象锁(监视器)的线程操作。
10.1为什么都是Object的方法?
因为这些方法在操作同步中的线程时,都必须要标识所操作线程持有的锁。只有同一个锁上等待的线程,被同一个锁上的notify唤醒,不可以对不同锁中线程唤醒,也就是等待唤醒是同一个锁。
而锁可以是任意对象,任意对象可调用的方法当然在object中。
10.2为何需要配合while使用?
保证唤醒后继续执行条件判断:(比如读的一方被读的另外一个线程唤醒了,或者是读的一方好几个线程都被写的一方唤醒了。
while(flag){ this.wait();//唤醒后先判断循环 } .... //do somethiing 如果是if if(flag){ this.wait(); //唤醒后不会执行判断 } //do something }
10.2.1几种写法:
synchromized f(){ ... wait()/notify()//省略对象的调用,即是当前对象 } //等价于: f(){ synchronized(this){ ... wait()/notify()//省略对象的调用,即是当前对象 } //等价于: f(){...} run(){ synchronized(a){ ... a.wait()/a.notify()//不能省略对象的调用 f();
10.3特点:
(对象.wait()-->释放cup和锁-->得到唤醒-->抢到获取cup,获取锁,恢复到之前位置,才开始继续执行。)
(必须配合synchronized使用,可以用于有序的修改然后获取。)
10.4 下例不采用唤醒机制的对比,用两个线程两次执行对比:(如9.6中通过判断标识的方式无用的执行太多)
Thread t = new Thread(new WNReadRun(a)); Thread t2 = new Thread(new WNWriteRun(a)); A a2 = new A(); Thread t3 = new Thread(new ReadRun(a2)); Thread t4 = new Thread(new WriteRun(a2)); t.start(); t2.start(); t3.start(); t4.start(); class WriteRun implements Runnable { A a; int n;//无用的执行次数 public WriteRun(A a) { this.a = a; } @Override public void run() { while (true) { synchronized (a) { if (a.flag) { a.add(); System.out.println("add" + a.i + "---" + Thread.currentThread().getName()+"---无用"+n+"---耗时"+(new Date().getTime()-a.time.getTime())); a.flag = false; } else { n++; } } } } } class ReadRun implements Runnable { A a; int n;//无用的执行次数 public ReadRun(A a) { this.a = a; } @Override public void run() { while (true) {// 通过循环被动执行满足条件的代码 synchronized (a) { if (!a.flag) { int i = a.get(); System.out.println("get----" + i + "---" + Thread.currentThread().getName()+"---无用"+n+"---耗时"+(new Date().compareTo(a.time))+"---耗时"+(new Date().getTime()-a.time.getTime())); a.flag = true; } else { n++; } } } } } class WNWriteRun implements Runnable { A a; public WNWriteRun(A a) { this.a = a; } @Override public void run() { synchronized (a) { while (true) {//while循环在同步里面和外面的区别是:不需要再次得到锁再来判断是否释放资源 if(a.flag){ try{a.wait();}catch(Exception e){} } a.add(); System.out.println("add" + a.i + "---" + Thread.currentThread().getName()+"---耗时"+(new Date().getTime()-a.time.getTime())); a.flag=true; a.notify(); } } } } class WNReadRun implements Runnable { A a; public WNReadRun(A a) { this.a = a; } @Override public void run() { synchronized (a) { while (true) {//while循环在同步里面和外面的区别是:不需要再次得到锁再来判断是否释放资源 if(!a.flag){ try{a.wait();}catch(Exception e){} } int i = a.get(); System.out.println("get----" + i + "---" + Thread.currentThread().getName()+"---耗时"+(new Date().getTime()-a.time.getTime())); a.flag=false; a.notify(); } } } }
get----51581---Thread-0---耗时2416 add51582---Thread-1---耗时2416 add36438---Thread-3---无用518215---耗时2490 get----36438---Thread-2---无用534042---耗时1---耗时2490
10.5 多个线程重复有序读写的唤醒机制:
class WNWriteRun implements Runnable { A a; public WNWriteRun(A a) { this.a = a; } @Override public void run() { synchronized (a) { while (true) {//while循环在同步里面和外面的区别是:不需要再次得到锁再来判断是否释放资源 //if(a.flag) 多个线程访问需要将if换成while,以确保多个线程唤醒后能经过判断再执行 while(a.flag){ try{a.wait();}catch(Exception e){} //多个线程在此处唤醒并不会一起执行。因为获取到cpu资源后,还需要获得锁才能执行,等到一个执行完, //别的线程得到资源和锁后,再进行判断(上个线程已经修改标识了)又将进入等待 } a.add(); System.out.println("add" + a.i + "---" + Thread.currentThread().getName()+"---耗时"+(new Date().getTime()-a.time.getTime())); a.flag=true; a.notifyAll(); } } } } class WNReadRun implements Runnable { A a; public WNReadRun(A a) { this.a = a; } @Override public void run() { synchronized (a) { while (true) {//while循环在同步里面和外面的区别是:不需要再次得到锁再来判断是否释放资源 //if(!a.flag) 多个线程访问需要将if换成while,以确保多个线程唤醒后能经过判断再执行 while(!a.flag){ try{a.wait();}catch(Exception e){} //多个线程在此处唤醒并不会一起执行。因为获取到cpu资源后,还需要获得锁才能执行,等到一个执行完, //别的线程得到资源和锁后,再进行判断(上个线程已经修改标识了)又将进入等待 } int i = a.get(); System.out.println("get----" + i + "---" + Thread.currentThread().getName()+"---耗时"+(new Date().getTime()-a.time.getTime())); a.flag=false; a.notifyAll(); } } } }
A a = new A(); Thread t = new Thread(new WNReadRun(a)); Thread t2 = new Thread(new WNWriteRun(a)); Thread t3 = new Thread(new WNReadRun(a)); Thread t4 = new Thread(new WNWriteRun(a)); t.start(); t2.start(); t3.start(); t4.start();
缺点:在唤醒中的将读写双方所有线程都唤醒了,然后获取资源执行后又睡眠。下面一种更优方式是lock ,condition 的方式可以唤醒单方面的线程。
11另一种await,signal唤醒机制
lock接口,子类ReentrantLock
condition的await(),signal(),signalAll()同样需要在锁内部执行等待和唤醒。
是synchronized的升级,lock替代了同步块,condition的方法替代了锁的等待唤醒方法。
下例子只需要signal(),而不需要signalAll()(前面例子需要notifyAll(),是因为没有唤醒对方线程的方法只能全部唤醒)此例只需要唤醒对方一个线程,如果多个唤醒了判断后还是会继续等待。
public class A { Date time; int i = 0; boolean flag; int tem; Object o = new Object(); Lock lock; Condition readCondition; Condition writeCondition; public A() { time = new Date(); System.out.println("A"); lock = new ReentrantLock(); readCondition = lock.newCondition(); writeCondition = lock.newCondition(); } void add() { i++; } int get() { return i; }
class LockWriteRun implements Runnable { A a; public LockWriteRun(A a) { this.a = a; } @Override public void run() { //a.lock.lock();锁放在while循环外面,释放锁后再调用等待,再被唤醒就出错了。 while (true) {//while循环在同步里面和外面的区别是:不需要再次得到锁再来判断是否释放资源 a.lock.lock(); //if(a.flag) 多个线程访问需要将if换成while,以确保多个线程唤醒后能经过判断再执行
//可能出现被唤醒不会立即执行,一线程刚执行才获得资源如果不判断会继续执行的情况 while(a.flag){ try{a.writeCondition.await();}catch(Exception e){} //多个线程在此处唤醒并不会一起执行。因为获取到cpu资源后,还需要获得锁才能执行,等到一个执行完, //别的线程得到资源和锁后,再进行判断(上个线程已经修改标识了)又将进入等待 } a.add(); System.out.println("add" + a.i + "---" + Thread.currentThread().getName()+"---耗时"+(new Date().getTime()-a.time.getTime())); a.flag=true; a.readCondition.signal(); a.lock.unlock();//(省略try)解锁需要在finally中执行,防止异常一直占用锁。造成别的地方一直等待。
}
class LockReadRun implements Runnable { A a; public LockReadRun(A a) { this.a = a; } @Override public void run() { //a.lock.lock();锁放在while循环外面,释放锁后再调用等待,再被唤醒就出错了。 while (true) {//while循环在同步里面和外面的区别是:不需要再次得到锁再来判断是否释放资源 a.lock.lock(); //if(!a.flag) 多个线程访问需要将if换成while,以确保多个线程唤醒后能经过判断再执行
//可能出现被唤醒不会立即执行,一线程刚执行才获得资源如果不判断会继续执行的情况
while(!a.flag){ try{a.readCondition.await();}catch(Exception e){} //多个线程在此处唤醒并不会一起执行。因为获取到cpu资源后,还需要获得锁才能执行,等到一个执行完, //别的线程得到资源和锁后,再进行判断(上个线程已经修改标识了)又将进入等待 } int i = a.get(); long times = new Date().getTime()-a.time.getTime(); System.out.println("get----" + i + "---" + Thread.currentThread().getName()+"---耗时"+times); a.flag=false; a.writeCondition.signal(); a.lock.unlock();//(省略try)解锁需要在finally中执行,防止异常一直占用锁。造成别的地方一直等待。 }}}
Thread t = new Thread(new LockReadRun(a)); Thread t2 = new Thread(new LockWriteRun(a)); Thread t3 = new Thread(new LockReadRun(a)); Thread t4 = new Thread(new LockWriteRun(a)); t.start(); t2.start(); t3.start(); t4.start();
注意:不管notify,signal 两种中的哪种唤醒的线程不一定马上执行。只有抢到到资源和锁才会执行。
12.线程死锁:(互相拥有对方即将需要的锁)
//1线程执行
synchronized (a) {
// 得到a锁,等待o锁,1,2线程执行到类似位置就会出现死锁
synchronized (o) {
}
}
//2线程执行
synchronized (o) {
// 得到a锁,等待o锁 ,1,2线程执行到类似位置就会出现死锁
synchronized (a) {
}
}
13.interrupt(),中断方法,清除中断状态即:并不是停止线程的执行,而是强制从冻结状态(wait/sleep/join)中恢复过来并且抛出异常InterruptedException
。
一般情况线程总是循环调用,通过增加循环标记,停止线程。但是有时候,当线程处于冻结状态时候,更改标记后并不能起作用,依然在等待中。
public static void main(String[] args) { InterruptRun interruptRun = new InterruptRun(); Thread thread = new Thread(interruptRun); Thread thread2 = new Thread(interruptRun); thread.start(); thread2.start(); for (int i = 0; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + "-----" + i); } interruptRun.change(); } } class InterruptRun implements Runnable { private boolean flag; private int i; Object o = new Object(); void change() { flag = !flag; } @Override public void run() { // TODO Auto-generated method stub while (!flag) { synchronized (o) { try { o.wait();//当线程在此等待,flag被改变时候并没有作用。 } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "-----run-----" + i); } } }
可以通过Thread的对象.interrrupt(),从等待中回复过来,捕获异常在异常处理中修改标记,达到终止的目的。
public static void main(String[] args) { InterruptRun interruptRun = new InterruptRun(); Thread thread = new Thread(interruptRun); Thread thread2 = new Thread(interruptRun); thread.start(); thread2.start(); for (int i = 0; i < 10000; i++) { System.out.println(Thread.currentThread().getName() + "-----" + i); } thread.interrupt(); thread2.interrupt(); } } class InterruptRun implements Runnable { private boolean flag; private int i; Object o = new Object(); void change() { flag = !flag; } @Override public void run() { // TODO Auto-generated method stub while (!flag) { synchronized (o) { try { o.wait();// 当线程在此等待,flag被改变时候并没有作用。 } catch (InterruptedException e) { flag = true;// 发生中断时候修改标记,达到停止的目的 } System.out.println(Thread.currentThread().getName() + "-----run-----" + i); } } }
14.守护线程,或者叫后台线程。特点是:当所有前台线程结束后台线程自动结束。(默认创建的都是前台线程)在结束前和前台线程一起竞争资源运行。
设置成守护线程:
Thread thread = new Thread(interruptRun); thread.setDaemon(true);//在线程启动前调用此方法 thread.start();
15.线程组,一般由哪个线程启动其他属于这个线程组。有个ThreadGroup线程组类,可以将现场加入自定义组中。(略)
16.线程toString(),重写后,包括线程的名称,所属优先级,所在组
17.任务调度:Timer/TimerTast,定时器,定时调度。(TimerTast 实现了runnable接口)
Timer timer = new Timer(); timer.schedule(TimerTask task, Date time); //执行一次 timer.schedule(TimerTask task, long delay, long period);间隔多久重复执行