Java重点知识回顾(值传递,多线程,Map)
2017-12-11 21:03
405 查看
一. 引用传递和值传递
值传递:值传递是将变量的一个副本传递到方法中,方法中如何操作该变量副本,都不会改变原变量的值。
引用传递:引用传递是将变量的内存地址传递给方法,方法操作变量时会找到保存在该地址的变量,对其进行操作。
会对原变量造成影响。
二. 多线程
1.多线程生命周期
新建(New),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)
(1). 新建状态[b](new Thread):[/b]
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值。
(2). 就绪状态(Runnable):
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
(3). 运行状态 (Running):
如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
(4). 阻塞状态 (Blocked):
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,便进入阻塞状态
① 线程调用sleep()方法主动放弃所占用的处理器资源
② 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
③ 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识、后面将存更深入的介绍
④ 线程在等待某个通知(notify)
⑤ 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法
(5) 线程死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
2.实现方法:
void run() 创建该类的子类时必须实现的方法
void start() 开启线程的方法
static void sleep(long t) 释放CPU的执行权,不释放锁
static void sleep(long millis,int nanos)
final void wait()释放CPU的执行权,释放锁
final void notify() / final void notifyAll() 唤醒单个线程 / 唤醒所有线程
static void yied()可以对当前线程进行临时暂停(让线程将资源释放出来)
3.多线程同步方式
(1).同步方法
即有synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
(2) 同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:
synchronized(object){
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
[b](3).使用特殊域变量(volatile)实现线程同步
[/b]
A . volatile关键字为域变量的访问提供了一种免锁机制,
B. 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
C. 因此每次使用该域就要重新计算,而不是使用寄存器中的值
D. volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
例如:
在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。
class Bucket {
//需要同步的变量加上volatile
private volatile int contains = 100;
public int getContains() {
return contains;
}
//这里不再需要synchronized
public void save(int apple) {
contains += apple;
}
}
注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
用final域,有锁保护的域和volatile域可以避免非同步的问题。
[b](4).使用重入锁实现线程同步[/b]
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,
它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
class Bucket {
//需要同步的变量加上volatile
private volatile int contains = 100;
public int getContains() {
return contains;
}
//这里不再需要synchronized
public void save(int apple) {
contains += apple;
}
}
注:关于Lock对象和synchronized关键字的选择:
a.最好两个都不用,使用一种java.util.concurrent包提供的机制,
能够帮助用户处理所有与锁相关的代码。
b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
[b]5.使用局部变量实现线程同步
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,
副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。[/b]
ThreadLocal 类的常用方法
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
public class Bucket{
//使用ThreadLocal类管理共享变量contains
private static ThreadLocal<Integer> contains = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
public void save(int apple){
contains.set(contains.get()+apple);
}
public int getContains(){
return contains.get();
}
}
注:ThreadLocal与同步机制
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式
[b]6.使用阻塞队列实现线程同步
[/b]
前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。
使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。
本小节主要是使用[b]LinkedBlockingQueue<E>来实现线程的同步
LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。
队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~
LinkedBlockingQueue 类常用方法
LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue
put(E e) : 在队尾添加一个元素,如果队列满则阻塞
size() : 返回队列中的元素个数
take() : 移除并返回队头元素,如果队列空则阻塞
[/b]
注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:
add()方法会抛出异常
offer()方法返回false
put()方法会阻塞
7.使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
public class Bucket{
private AtomicInteger contains = new AtomicInteger(100);
public AtomicInteger getContains() {
return contains;
}
public void save(int apple) {
contains.addAndGet(apple);
}
}
三。ConcurrentHashMap 和 HashMap的区别
从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,
具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放
到哪个HashTable中。
在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()
算出放到哪个Segment中:
以上就是ConcurrentHashMap的工作机制,通过把整个Map分为N个Segment(类似HashTable),
可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
值传递:值传递是将变量的一个副本传递到方法中,方法中如何操作该变量副本,都不会改变原变量的值。
引用传递:引用传递是将变量的内存地址传递给方法,方法操作变量时会找到保存在该地址的变量,对其进行操作。
会对原变量造成影响。
二. 多线程
1.多线程生命周期
新建(New),就绪(Runnable),运行(Running),阻塞(Blocked),死亡(Dead)
(1). 新建状态[b](new Thread):[/b]
当创建Thread类的一个实例(对象)时,此线程进入新建状态(未被启动)。
例如:Thread t1=new Thread();
当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时仅由JVM为其分配内存,并初始化其成员变量的值。
(2). 就绪状态(Runnable):
线程已经被启动,正在等待被分配给CPU时间片,也就是说此时线程正在就绪队列中排队等候得到CPU资源。例如:t1.start();
当线程对象调用了start()方法之后,该线程处于就绪状态。Java虚拟机会为其创建方法调用栈和程序计数器,等待调度运行
(3). 运行状态 (Running):
如果处于就绪状态的线程获得了CPU,开始执行run()方法的线程执行体,则该线程处于运行状态
线程获得CPU资源正在执行任务(run()方法),此时除非此线程自动放弃CPU资源或者有优先级更高的线程进入,线程将一直运行到结束。
(4). 阻塞状态 (Blocked):
由于某种原因导致正在运行的线程让出CPU并暂停自己的执行,便进入阻塞状态
① 线程调用sleep()方法主动放弃所占用的处理器资源
② 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
③ 线程试图获得一个同步监视器,但该同步监视器正被其他线程所持有。关于同步监视器的知识、后面将存更深入的介绍
④ 线程在等待某个通知(notify)
⑤ 程序调用了线程的suspend()方法将该线程挂起。但这个方法容易导致死锁,所以应该尽量避免使用该方法
(5) 线程死亡(dead)
当线程执行完毕或被其它线程杀死,线程就进入死亡状态,这时线程不可能再进入就绪状态等待执行。
自然终止:正常运行run()方法后终止
异常终止:调用stop()方法让一个线程终止运行
2.实现方法:
void run() 创建该类的子类时必须实现的方法
void start() 开启线程的方法
static void sleep(long t) 释放CPU的执行权,不释放锁
static void sleep(long millis,int nanos)
final void wait()释放CPU的执行权,释放锁
final void notify() / final void notifyAll() 唤醒单个线程 / 唤醒所有线程
static void yied()可以对当前线程进行临时暂停(让线程将资源释放出来)
3.多线程同步方式
(1).同步方法
即有synchronized关键字修饰的方法。
由于java的每个对象都有一个内置锁,当用此关键字修饰方法时,
内置锁会保护整个方法。在调用该方法前,需要获得内置锁,否则就处于阻塞状态。
代码如: public synchronized void put(){} 修饰非静态方法只锁住方法 public static synchronized void put(){} 修饰静态方法将会锁住整个类
(2) 同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
代码如:
synchronized(object){
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可。
[b](3).使用特殊域变量(volatile)实现线程同步
[/b]
A . volatile关键字为域变量的访问提供了一种免锁机制,
B. 使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
C. 因此每次使用该域就要重新计算,而不是使用寄存器中的值
D. volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
例如:
在上面的例子当中,只需在account前面加上volatile修饰,即可实现线程同步。
class Bucket {
//需要同步的变量加上volatile
private volatile int contains = 100;
public int getContains() {
return contains;
}
//这里不再需要synchronized
public void save(int apple) {
contains += apple;
}
}
注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
用final域,有锁保护的域和volatile域可以避免非同步的问题。
[b](4).使用重入锁实现线程同步[/b]
在JavaSE5.0中新增了一个java.util.concurrent包来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,
它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力。
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
class Bucket {
//需要同步的变量加上volatile
private volatile int contains = 100;
public int getContains() {
return contains;
}
//这里不再需要synchronized
public void save(int apple) {
contains += apple;
}
}
注:关于Lock对象和synchronized关键字的选择:
a.最好两个都不用,使用一种java.util.concurrent包提供的机制,
能够帮助用户处理所有与锁相关的代码。
b.如果synchronized关键字能满足用户的需求,就用synchronized,因为它能简化代码
c.如果需要更高级的功能,就用ReentrantLock类,此时要注意及时释放锁,否则会出现死锁,通常在finally代码释放锁
[b]5.使用局部变量实现线程同步
如果使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本,
副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响。[/b]
ThreadLocal 类的常用方法
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
public class Bucket{
//使用ThreadLocal类管理共享变量contains
private static ThreadLocal<Integer> contains = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
public void save(int apple){
contains.set(contains.get()+apple);
}
public int getContains(){
return contains.get();
}
}
注:ThreadLocal与同步机制
a.ThreadLocal与同步机制都是为了解决多线程中相同变量的访问冲突问题。
b.前者采用以"空间换时间"的方法,后者采用以"时间换空间"的方式
[b]6.使用阻塞队列实现线程同步
[/b]
前面5种同步方式都是在底层实现的线程同步,但是我们在实际开发当中,应当尽量远离底层结构。
使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。
本小节主要是使用[b]LinkedBlockingQueue<E>来实现线程的同步
LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。
队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~
LinkedBlockingQueue 类常用方法
LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue
put(E e) : 在队尾添加一个元素,如果队列满则阻塞
size() : 返回队列中的元素个数
take() : 移除并返回队头元素,如果队列空则阻塞
[/b]
/** * 定义一个阻塞队列用来存储生产出来的商品 */ private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>(); /** * 定义生产商品个数 */ private static final int size = 10; /** * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程 */ private int flag = 0; private class LinkBlockThread implements Runnable { @Override public void run() { int new_flag = flag++; System.out.println("启动线程 " + new_flag); if (new_flag == 0) { for (int i = 0; i < size; i++) { int b = new Random().nextInt(255); System.out.println("生产商品:" + b + "号"); try { queue.put(b); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("仓库中还有商品:" + queue.size() + "个"); try { Thread.sleep(100); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } else { for (int i = 0; i < size / 2; i++) { try { int n = queue.take(); System.out.println("消费者买去了" + n + "号商品"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("仓库中还有商品:" + queue.size() + "个"); try { Thread.sleep(100); } catch (Exception e) { // TODO: handle exception } } } } }
注:BlockingQueue<E>定义了阻塞队列的常用方法,尤其是三种添加元素的方法,我们要多加注意,当队列满时:
add()方法会抛出异常
offer()方法返回false
put()方法会阻塞
7.使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
那么什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),
但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
public class Bucket{
private AtomicInteger contains = new AtomicInteger(100);
public AtomicInteger getContains() {
return contains;
}
public void save(int apple) {
contains.addAndGet(apple);
}
}
三。ConcurrentHashMap 和 HashMap的区别
从ConcurrentHashMap代码中可以看出,它引入了一个“分段锁”的概念,
具体可以理解为把一个大的Map拆分成N个小的HashTable,根据key.hashCode()来决定把key放
到哪个HashTable中。
在ConcurrentHashMap中,就是把Map分成了N个Segment,put和get的时候,都是现根据key.hashCode()
算出放到哪个Segment中:
以上就是ConcurrentHashMap的工作机制,通过把整个Map分为N个Segment(类似HashTable),
可以提供相同的线程安全,但是效率提升N倍,默认提升16倍。
相关文章推荐
- 黑马程序员--java 知识回顾--多线程
- Java多线程基础知识回顾与总结;
- Java基础重点知识回顾9.06
- Java基础知识回顾之四 ----- 集合List、Map和Set
- java基础知识回顾之java Thread类学习(四)--java多线程安全问题(锁)
- java基础知识回顾之java Thread类学习(五)--java多线程安全问题(锁)同步的前提
- JAVA基础知识回顾之---方法中的参数传递
- java基础知识回顾之java Thread类学习(七)--java多线程安全问题(死锁)
- JAVA基础知识回顾-----多线程基础-----随想随写
- java基础知识回顾---List,set,Map 的用法和区别
- Java基础知识回顾之五 ----- 多线程
- Java 多线程(一) 基础知识与概念
- 初学Java多线程:向线程传递数据的三种方法
- java中如何给多线程中子线程传递参数?
- 黑马程序员学习log第四篇基础知识:JAVA的面向对象之多线程总结
- Java基础知识Set、List、Map的区别
- Java基础知识强化之集合框架笔记53:Map集合之Map集合的遍历 键值对对象找键和值
- java基础知识回顾之抽象类和接口的区别
- 基础知识《七》---Java多线程详解
- java多线程相关知识