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;}