变量空间开辟条件:
1.数据类型
2.变量名称
3.变量的初始化值
变量的作用域
1.变量的作用域:从变量定义的位置开始到所在的那对大括号结束
2.生命周期:从定义的位置活着-》到达其所在作用域的时候在内存中消失
数据类型
byte,short,int,long,float,double,char,boolean
java内存
1.分为5块内存:寄存器,本地方法区,方法区,栈,堆
2.栈:存储的是局部变量(如函数的参数,语句中的变量,函数中定义的变量)
3.堆:用于存储实体(数组和对象),实体:封装多个数据的
4.
(1)每一个实体都有内存首地址值
(2)堆内存中的变量都有默认初始化值。数据类型不同,值夜不一样
(3)垃圾回收机制
多线程
1.线程和进程
1.线程:进程中程序的执行单元,但是线程本身依靠程序运行
2.进程:执行中的程序,其中一个进程至少包含一个线程
2.多线程的作用
1.发挥多核CPU的优势
随着工业的进步,现在的笔记本、台式机乃至商用的应用服务器至少也都是双核的,4核、8核甚至16核的也都不少见,如果是单线程的程序,那么在双核CPU上就浪费了50%,在4核CPU上就浪费了75%。单核CPU上所谓的"多线程"那是假的多线程,同一时间处理器只会处理一段逻辑,只不过线程之间切换得比较快,看着像多个线程"同时"运行罢了。多核CPU上的多线程才是真正的多线程,它能让你的多段逻辑同时工作,多线程,可以真正发挥出多核CPU的优势来,达到充分利用CPU的目的。
2.防止阻塞
从程序运行效率的角度来看,单核CPU不但不会发挥出多线程的优势,反而会因为在单核CPU上运行多线程导致线程上下文的切换,而降低程序整体的效率。但是单核CPU我们还是要应用多线程,就是为了防止阻塞。试想,如果单核CPU使用单线程,那么只要这个线程阻塞了,比方说远程读取某个数据吧,对端迟迟未返回又没有设置超时时间,那么你的整个程序在数据返回回来之前就停止运行了。多线程可以防止这个问题,多条线程同时运行,哪怕一条线程的代码执行读取数据阻塞,也不会影响其它任务的执行。
例子:java的垃圾回收是由专门的线程来处理的
3.线程的实现
1.继承Thread类
(1)继承Thread类,重写run()方法
(2)Thread的run()方法不会创建新的进程,start()会创建新的线程
(3)只有调用start()才会表现出多线程的特性,而run()还是同步去执行的
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyThread thread1 = new MyThread("thread1");
thread1.start();
MyThread thread2 = new MyThread("thread2");
thread2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name){
this.name = name;
}
@Override
public void run() {
System.out.println("name:"+name+" 子线程ID:"+Thread.currentThread().getId());
}
}
2.实现Runnable接口(常用,又称作并发编程,就是将大的任务拆成一个一个的小的任务即Runable)
(1)实现Runnable接口,重写run方法
(2)将Runnale作为参数传入Thread类中,再调用Thread.start()
public class Test {
public static void main(String[] args) {
System.out.println("主线程ID:"+Thread.currentThread().getId());
MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable);
thread.start();
}
}
class MyRunnable implements Runnable{
public MyRunnable() {
}
@Override
public void run() {
System.out.println("子线程ID:"+Thread.currentThread().getId());
}
}
3.other 使用ExecutorService,Callable,Future实现由返回结果的多线程
(1)Executors框架功能类:用ExecutorService,Callable,Future可以实现
(2)
->可返回值的任务必须实现Callable接口,无返回值的必须实现Runnable接口
class MyCallable implements Callable<Object> {
private String taskNum;
MyCallable(String taskNum) {
this.taskNum = taskNum;
}
public Object call() throws Exception {
System.out.println(">>>" + taskNum + "任务启动");
Date dateTmp1 = new Date();
Thread.sleep(1000);
Date dateTmp2 = new Date();
long time = dateTmp2.getTime() - dateTmp1.getTime();
System.out.println(">>>" + taskNum + "任务终止");
return taskNum + "任务返回运行结果,当前任务时间【" + time + "毫秒】";
}
}
->ExecutorService.newFixedThreadPool(5);//创建线程池(拥有5个线程)
注:可以创建固定数目的线程池,单线程,可缓存的线程池
->创建5个有返回值的任务
for (int i = 0; i < 5; i++) {
Callable c = new MyCallable(i + " ");
// 执行任务并获取Future对象
Future f = pool.submit(c);
list.add(f);
}
// 关闭线程池
pool.shutdown();
// 获取所有并发任务的运行结果
for (Future f : list) {
// 从Future对象上获取任务的返回值,并输出到控制台
System.out.println(">>>" + f.get().toString());
}
// 关闭线程池
pool.shutdown();
【注:ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,
返回Future。如果Executor后台线程池还没有完成Callable的计算,
这调用返回Future对象的get()方法,会阻塞直到计算完成】
4.线程的状态
(1)创建(new):准备好了一个多线程的对象
(2)就绪(Runnable):调用了start(),等待CPU分配
(3)运行(running):执行run方法
(4)阻塞(blocked):暂时停止执行,wait,sleep,IO阻塞或者被同步块阻塞
注:sleep()和wait()区别
——sleep()是Thread类的方法,wait是Object类中定义的方法
——Thread.sleep不会改便锁行为的改变,进而不会让线程释放锁
——Thread.sleep和Object.wait都会暂停当前进程,
sleep可以到了时间会自动获取到CPU执行时间
wait需要别的线程执行notify/notifyAll才能重新获得CPU执行时间
5.线程的切换(上下文切换)
(1)线程上下文切换过程中会记录程序计数器,CPU寄存器装填等数据
OR:存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。
(2)多线层的优缺点:
优点:发挥了多核CPU的作用,任务执行效率得到提升
缺点:线程的切换需要保存CPU的状态,开销代价,并且多个线程会导致系统资源的
占用增加。
6.线程间通信
1.同步
多个线程通过synchronized关键字来实现线程间的通信
public class MyObject {
synchronized public void methodA() {
//do something....
}
synchronized public void methodB() {
//do some other thing
}
}
public class ThreadA extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodA();
}
}
public class ThreadB extends Thread {
private MyObject object;
//省略构造方法
@Override
public void run() {
super.run();
object.methodB();
}
}
public class Run {
public static void main(String[] args) {
MyObject object = new MyObject();
//线程A与线程B 持有的是同一个对象:object
ThreadA a = new ThreadA(object);
ThreadB b = new ThreadB(object);
a.start();
b.start();
}
}
线程A和B持有同一个对象object,线程B需要等待线程A执行完了methodA()后才能执行
methodB(),这样就实现了线程A和B的“共享内存”式的通信。
注:多个线程需要访问同一个变量,谁拿到了锁(访问权限),谁就可以执行
2.while轮询的方式
线层A不停改变某个类里集合的条件,而线程B则是不断轮询(死循环)当条件成立时
则跳出循环
import java.util.ArrayList;
import java.util.List;
public class MyList {
private List<String> list = new ArrayList<String>();
public void add() {
list.add("elements");
}
public int size() {
return list.size();
}
}
import mylist.MyList;
public class ThreadA extends Thread {
private MyList list;
public ThreadA(MyList list) {
super();
this.list = list;
}
@Override
public void run() {
try {
for (int i = 0; i < 10; i++) {
list.add();
System.out.println("添加了" + (i + 1) + "个元素");
Thread.sleep(1000);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import mylist.MyList;
public class ThreadB extends Thread {
private MyList list;
public ThreadB(MyList list) {
super();
this.list = list;
}
@Override
public void run() {
try {
while (true) {
if (list.size() == 5) {
System.out.println("==5, 线程b准备退出了");
throw new InterruptedException();
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
import mylist.MyList;
import extthread.ThreadA;
import extthread.ThreadB;
public class Test {
public static void main(String[] args) {
MyList service = new MyList();
ThreadA a = new ThreadA(service);
a.setName("A");
a.start();
ThreadB b = new ThreadB(service);
b.setName("B");
b.start();
}
}
线程A不断地改变条件,线程ThreadB不停地通过while语句检测这个条件
(list.size()==5)是否成立 ,从而实现了线程间的通信。
浪费CPU资源,因为B线程只是在不断的检测某个条件是否成立,没做撒有用的工作
3.wait/notify机制
import java.util.ArrayList;
import java.util.List;
public class MyList {
private static List<String> list = new ArrayList<String>();
public static void add() {
list.add("anyString");
}
public static int size() {
return list.size();
}
}
public class ThreadA extends Thread {
private Object lock;
public ThreadA(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
if (MyList.size() != 5) {
System.out.println("wait begin "
+ System.currentTimeMillis());
lock.wait();
System.out.println("wait end "
+ System.currentTimeMillis());
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class ThreadB extends Thread {
private Object lock;
public ThreadB(Object lock) {
super();
this.lock = lock;
}
@Override
public void run() {
try {
synchronized (lock) {
for (int i = 0; i < 10; i++) {
MyList.add();
if (MyList.size() == 5) {
lock.notify();
System.out.println("已经发出了通知");
}
System.out.println("添加了" + (i + 1) + "个元素!");
Thread.sleep(1000);
}
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public class Run {
public static void main(String[] args) {
try {
Object lock = new Object();
ThreadA a = new ThreadA(lock);
a.start();
Thread.sleep(50);
ThreadB b = new ThreadB(lock);
b.start();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
线程A要等待某个条件满足时(list.size()==5),才执行操作。
线程B则向list中添加元素,改变list 的size。
A,B之间如何通信的呢?也就是说,线程A如何知道 list.size() 已经为5了呢?
这里用到了Object类的 wait() 和 notify() 方法。
当条件未满足时(list.size() !=5),线程A调用wait()
放弃CPU,并进入阻塞状态。---不像②while轮询那样占用CPU
当条件满足时,线程B调用 notify()通知线程A,
所谓通知线程A,就是唤醒线程A,并让它进入可运行状态。
这种方式的一个好处就是CPU的利用率提高了。
但是也有一些缺点:比如,线程B先执行,一下子添加了5个元素
并调用了notify()发送了通知,而此时线程A还执行;
当线程A执行并调用wait()时,那它永远就不可能被唤醒了。
因为,线程B已经发了通知了,以后不再发通知了。
这说明:通知过早,会打乱程序的执行逻辑。
7.线程常用方法
start():启动线程
sleep():休眠线程-交出CPU
注:sleep不会释放锁,当前线程持有某个对象的锁,其他线程不能访问该对象
join():使其他进程等待当前进程终止后再执行
currentThread():返回代码块正被哪个线程调用
Thread.getId():线程唯一标识
isAlive():线程是否处于活动状态(是否在运行)
interrupt():暂停线程
getPriority和setPriority:获取和设置线程优先级
8.多线程的原子性可见性有序性
1.原子性
cpu不可以再中途暂停然后再调度,即不被中断操作,必须执行完成.
2.可见性
详见volatile关键字解析
3.有序性
(1)如果在本线程内观察,所有操作都是有序的【线程内表现为串行语义】
如果在一个线程中观察另一个线程,所有操作都是无序的
【指令重排序”现象和“工作内存中主内存同步延迟”现象】
(2)Java语言提供了volatile和synchronized两个关键字来保证线程之间操作的有序性
(3)Java“先行发生”(Happen-Before)的原则
指的是Java内存模型中定义的两项操作之间的依序关系
如果说操作A先行发生于操作B,其实就是说发生操作B之前,操作A产生的影响能被操作B观察到,“影响”包含了修改了内存中共享变量的值、发送了消息、调用了方法等。它意味着什么呢?如下例:
//线程A中执行
i = 1;
//线程B中执行
j = i;
//线程C中执行
i = 2;
假设线程A中的操作”i=1“先行发生于线程B的操作”j=i“,那么我们就可以确定在线程B的操作执行后,变量j的值一定是等于1,结出这个结论的依据有两个,一是根据先行发生原则,”i=1“的结果可以被观察到;二是线程C登场之前,线程A操作结束之后没有其它线程会修改变量i的值。现在再来考虑线程C,我们依然保持线程A和B之间的先行发生关系,而线程C出现在线程A和B操作之间,但是C与B没有先行发生关系,那么j的值可能是1,也可能是2,因为线程C对应变量i的影响可能会被线程B观察到,也可能观察不到,这时线程B就存在读取到过期数据的风险,不具备多线程的安全性。
下面是Java内存模型下一些”天然的“先行发生关系,这些先行发生关系无须任何同步器协助就已经存在,可以在编码中直接使用。如果两个操作之间的关系不在此列,并且无法从下列规则推导出来的话,它们就没有顺序性保障,虚拟机可以对它们进行随意地重排序。
a.程序次序规则(Pragram Order Rule):在一个线程内,按照程序代码顺序,书写在前面的操作先行发生于书写在后面的操作。准确地说应该是控制流顺序而不是程序代码顺序,因为要考虑分支、循环结构。
b.管程锁定规则(Monitor Lock Rule):一个unlock操作先行发生于后面对同一个锁的lock操作。这里必须强调的是同一个锁,而”后面“是指时间上的先后顺序。
c.volatile变量规则(Volatile Variable Rule):对一个volatile变量的写操作先行发生于后面对这个变量的读取操作,这里的”后面“同样指时间上的先后顺序。
d.线程启动规则(Thread Start Rule):Thread对象的start()方法先行发生于此线程的每一个动作。
e.线程终于规则(Thread Termination Rule):线程中的所有操作都先行发生于对此线程的终止检测,我们可以通过Thread.join()方法结束,Thread.isAlive()的返回值等作段检测到线程已经终止执行。
f.线程中断规则(Thread Interruption Rule):对线程interrupt()方法的调用先行发生于被中断线程的代码检测到中断事件的发生,可以通过Thread.interrupted()方法检测是否有中断发生。
g.对象终结规则(Finalizer Rule):一个对象初始化完成(构造方法执行完成)先行发生于它的finalize()方法的开始。
g.传递性(Transitivity):如果操作A先行发生于操作B,操作B先行发生于操作C,那就可以得出操作A先行发生于操作C的结论。
一个操作”时间上的先发生“不代表这个操作会是”先行发生“,那如果一个操作”先行发生“是否就能推导出这个操作必定是”时间上的先发生“呢?也是不成立的,一个典型的例子就是指令重排序。所以时间上的先后顺序与先生发生原则之间基本没有什么关系,所以衡量并发安全问题一切必须以先行发生原则为准。
volatile关键字
1.java的内存模型
——>每一个线程都有自己的内存空间(栈空间)
——>线程执行后会读取主内存中的共享变量到自己的内存中
——>对变量操作后在某个时间刷新回主内存
2.volatile作用
使变量在多个线程间可见:强制线层从主内存中去取volatile修饰的变量
注(使用场景):多个线程间需要根据某个条件确定哪个线程可以执行,要确保整个条件
是可见的,即(这个条件是volatile修饰过的)线程从主内存中取这个条件的值
3.volatile修饰的变量并不保证对它的操作(自增)具有原子性
例子:
->public volatile static int count;
->创建100个线程,每个线程执行100次累加1的动作,但是到最后count确没达到10000
仅靠volatile不能保证线程的安全性(原子性)
4.volatile 与 synchronized 的比较
——volatile轻量级,只能修饰变量。synchronized重量级,还可修饰方法
——volatile只能保证数据的可见性,不能用来同步,
因为多个线程并发访问volatile修饰的变量不会阻塞
——synchronized不仅保证可见性,而且还保证原子性
因为,只有获得了锁的线程才能进入临界区,从而保证临界区中的所有语句都全部执行
多个线程争抢synchronized锁对象时,会出现阻塞
总结:线程安全性,①可见性。②原子性
仅仅使用volatile并不能保证线程安全性。而synchronized则可实现线程的安全性
synchronized关键字
(1)synchronized 关键字来实现,所有加上synchronized和块语句
,在多线程访问的时候,同一时刻只能有一个线程能够用.
(2)synchronized 关键字锁住的是当前对象。这也是称为对象锁的原因
public class MyObject {
synchronized public void methodA() {
//do something....
}
}
methodA()是实例方法,要想执行methodA()必须实例化MyObject才能对象.方法()调用
synchronized就是把MyObject这个对象加锁了
(3)特点
线程A和B持有同一个对象object,线程B需要等待线程A执行完了methodA()后才能执行
methodB(),这样就实现了线程A和B的“共享内存”式的通信。
注:
public class MyObject {//对象object有连个方法
synchronized public void methodA() {
//do something....
}
synchronized public void methodB() {
//do some other thing
}
}
(4)使用synchronized避免 因数据不一致性而导致读脏数据的情况
public class MyObject {
private String userName = "b";
private String passWord = "bb";
synchronized public void methodA(String userName, String passWord) {
this.userName = userName;
try{
Thread.sleep(5000);
}catch(InterruptedException e){
}
this.passWord = passWord;
}
synchronized public void methodB() {
System.out.println("userName" + userName + ": " + "passWord" + passWord);
}
}
——》methodA()负责更改用户名和密码。在现实中,一个用户名对应着一个密码
methodB()负责读取用户名和密码
——》线程A执行methodA()去更改密码,线程B执行methoB()去读取密码
未用synchronized修饰methodA和methodB,线程B读取到的用户名
——》线程A更改了的用户名("a"),但是密码却是原来的密码("bb")。
因为,线程A睡眠了,还没有来得及更改密码。
——》用synchronized修饰methodA和methodB后,就避免了数据的不一致性而导致的脏读问题