目录
一、线程与多线程
1、什么是线程?
线程是操作系统中能够进行运算调度的最小单位,它被包含在进程之中,是进程中实际的运作单位,一个java程序最少就有两个线程在同时执行:main和GC
2.什么是多线程?
在程序中不同的线程完不同的任务,称为多线程
3.程序、进程与线程的关系
程序与进程:
①一个程序运行产生一个或多个进程
② 程序是静态指令集合,进程是动态的,进程的生命周期是程序代码完整的执行过程
进程与线程:
①一个进程可以有多个线程,进程分配CPU,内存等等系统资源
② 线程是进程的子集,使用资源执行任务
③不同的进程使用不同的内存空间,同一个进程中多个线程共享一片内存空间
二、java创建线程的方式
1、继承Thread类
Thread th = new Thread();
th.start();
2.实现Runnable接口
Thread th = new Thread(new Runnable());
th.start();
注意:
1、Thread 类继承了Runnable接口,Thread 有start() 方法和 run()方法
2、Runnable 接口只有run() 方法,实现接口需要借用 Thread类构造
3、当需要多继承 其他类的时候选用Runnable 接口
问题:调用start() 方法会执行run方法,为什么不直接执行run方法?
线程调用start() 方法后进入就绪状态,执行线程准备工作,分配到时间片后才能运行
而直接执行run方法会把run方法当成main线程下的普通方法执行,并不是多线程工作
三、线程状态
1、New(初始化状态):线程被创建
2、Runnable(可运行状态)
1)Runnable_READY(就绪状态),线程创建后调用start()方法,线程处于就绪状态,线程获得可运行的资格,等待被调配
2) Runnable_RUNNING(运行状态):线程运行中
3、BLOCKED(阻塞状态):只有一种情况会导致线程阻塞,synchronized关键字修饰的方法或代码块
4、WAITING(无时间限制等待状态):
1)调用无参的Object.wait()方法,直到调用notify()或notifyAll()显示唤醒,回到可运行状态
2)调用Thread.join()方法,例如创建线程A,调用A.join(), 那么此时主线程就是等待状态
5、TIMED_WAITING(有时间限制等待状态)
处于这种状态的线程不会被分配CPU执行时间,不过无须无限期等待被其他线程显示地唤醒,在达到一定时间后它们会自动唤醒。
1)Object.wait(long timeout)
2) Thread.join(long millis)
3) Thread.sleep(long millis)
6. TERMINATED(终止状态)
当线程的run()方法完成时,或者主线程的main()方法完成时,我们就认为它终止了。这个线程对象也许是活的,但是它已经不是一个单独执行的线程。线程一旦终止了,就不能复生。
在一个终止的线程上调用start()方法,会抛出java.lang.IllegalThreadStateException异常。
四、线程安全与线程不安全
导致线程不安全的原因
- 原子性:一个或者多个操作在 CPU 执行的过程中被中断
- 可见性:一个线程对共享变量的修改,另外一个线程不能立刻看到
- 有序性:程序执行的顺序没有按照代码的先后顺序执行
五、线程同步七种方式
使用线程同步原因
java允许多线程并发控制,当多个线程同时操作一个可共享的资源变量时(如数据的增删改查),将会导致数据不准确,相互之间产生冲突
因此加入同步锁以避免在该线程没有完成操作之前,被其他线程的调用,从而保证了该变量的唯一性和准确性
1、同步方法
即有synchronized关键字修饰的方法。
public synchronized void save(){}
注: synchronized关键字也可以修饰静态方法,此时如果调用该静态方法,将会锁住整个类
2、同步代码块
即有synchronized关键字修饰的语句块。
被该关键字修饰的语句块会自动被加上内置锁,从而实现同步
synchronized(object){
}
注:同步是一种高开销的操作,因此应该尽量减少同步的内容。
通常没有必要同步整个方法,使用synchronized代码块同步关键代码即可
3、使用特殊域变量(volatile)实现线程同步
a.volatile关键字为域变量的访问提供了一种免锁机制,
b.使用volatile修饰域相当于告诉虚拟机该域可能会被其他线程更新,
c.因此每次使用该域就要重新计算,而不是使用寄存器中的值
d.volatile不会提供任何原子操作,它也不能用来修饰final类型的变量
private volatile int account = 100;
注:多线程中的非同步问题主要出现在对域的读写上,如果让域自身避免这个问题,则就不需要修改操作该域的方法。
用final域,有锁保护的域和volatile域可以避免非同步的问题。
4、使用重入锁 ReentrantLock类来支持同步。
ReentrantLock类是可重入、互斥、实现了Lock接口的锁,
它与使用synchronized方法和快具有相同的基本行为和语义,并且扩展了其能力
ReenreantLock类的常用方法有:
ReentrantLock() : 创建一个ReentrantLock实例
lock() : 获得锁
unlock() : 释放锁
class Bank {
private int account = 100;
//需要声明这个锁
private Lock lock = new ReentrantLock();
public int getAccount() {
return account;
}
//这里不再需要synchronized
public void save(int money) {
lock.lock();
try{
account += money;
}finally{
lock.unlock();
}
}
}
注:ReentrantLock()还有一个可以创建公平锁的构造方法,但由于能大幅度降低程序运行效率,不推荐使用
5、使用局部变量ThreadLocal实现线程同步
使用ThreadLocal管理变量,则每一个使用该变量的线程都获得该变量的副本, 副本之间相互独立,这样每一个线程都可以随意修改自己的变量副本,而不会对其他线程产生影响
ThreadLocal 类的常用方法
ThreadLocal() : 创建一个线程本地变量
get() : 返回此线程局部变量的当前线程副本中的值
initialValue() : 返回此线程局部变量的当前线程的"初始值"
set(T value) : 将此线程局部变量的当前线程副本中的值设置为value
public class Bank{
//使用ThreadLocal类管理共享变量account
private static ThreadLocal<Integer> account = new ThreadLocal<Integer>(){
@Override
protected Integer initialValue(){
return 100;
}
};
public void save(int money){
account.set(account.get()+money);
}
public int getAccount(){
return account.get();
}
}
6、使用阻塞队列LinkedBlockingQueue<E>
实际开发当中,应当尽量远离底层结构。
使用javaSE5.0版本中新增的java.util.concurrent包将有助于简化开发。
本小节主要是使用LinkedBlockingQueue<E>来实现线程的同步
LinkedBlockingQueue<E>是一个基于已连接节点的,范围任意的blocking queue。
队列是先进先出的顺序(FIFO),关于队列以后会详细讲解~
LinkedBlockingQueue 类常用方法
LinkedBlockingQueue() : 创建一个容量为Integer.MAX_VALUE的LinkedBlockingQueue
put(E e) : 在队尾添加一个元素,如果队列满则阻塞
size() : 返回队列中的元素个数
take() : 移除并返回队头元素,如果队列空则阻塞
7、使用原子变量实现线程同步
需要使用线程同步的根本原因在于对普通变量的操作不是原子的。
问题:什么是原子操作呢?
原子操作就是指将读取变量值、修改变量值、保存变量值看成一个整体来操作
即-这几种行为要么同时完成,要么都不完成。
在java的util.concurrent.atomic包中提供了创建了原子类型变量的工具类,
使用该类可以简化线程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在应用程序中(如以原子方式增加的计数器),但不能用于替换Integer;可扩展Number,允许那些处理机遇数字类的工具和实用工具进行统一访问。
public class BlockingSynchronizedThread {
13 /**
14 * 定义一个阻塞队列用来存储生产出来的商品
15 */
16 private LinkedBlockingQueue<Integer> queue = new LinkedBlockingQueue<Integer>();
17 /**
18 * 定义生产商品个数
19 */
20 private static final int size = 10;
21 /**
22 * 定义启动线程的标志,为0时,启动生产商品的线程;为1时,启动消费商品的线程
23 */
24 private int flag = 0;
25
26 private class LinkBlockThread implements Runnable {
27 @Override
28 public void run() {
29 int new_flag = flag++;
30 System.out.println("启动线程 " + new_flag);
31 if (new_flag == 0) {
32 for (int i = 0; i < size; i++) {
33 int b = new Random().nextInt(255);
34 System.out.println("生产商品:" + b + "号");
35 try {
36 queue.put(b);
37 } catch (InterruptedException e) {
38 // TODO Auto-generated catch block
39 e.printStackTrace();
40 }
41 System.out.println("仓库中还有商品:" + queue.size() + "个");
42 try {
43 Thread.sleep(100);
44 } catch (InterruptedException e) {
45 // TODO Auto-generated catch block
46 e.printStackTrace();
47 }
48 }
49 } else {
50 for (int i = 0; i < size / 2; i++) {
51 try {
52 int n = queue.take();
53 System.out.println("消费者买去了" + n + "号商品");
54 } catch (InterruptedException e) {
55 // TODO Auto-generated catch block
56 e.printStackTrace();
57 }
58 System.out.println("仓库中还有商品:" + queue.size() + "个");
59 try {
60 Thread.sleep(100);
61 } catch (Exception e) {
62 // TODO: handle exception
63 }
64 }
65 }
66 }
67 }
68
69 public static void main(String[] args) {
70 BlockingSynchronizedThread bst = new BlockingSynchronizedThread();
71 LinkBlockThread lbt = bst.new LinkBlockThread();
72 Thread thread1 = new Thread(lbt);
73 Thread thread2 = new Thread(lbt);
74 thread1.start();
75 thread2.start();
76
77 }
78
79 }
AtomicInteger类常用方法:
AtomicInteger(int initialValue) : 创建具有给定初始值的新的AtomicInteger
addAddGet(int dalta) : 以原子方式将给定值与当前值相加
get() : 获取当前值
class Bank {
2 private AtomicInteger account = new AtomicInteger(100);
3
4 public AtomicInteger getAccount() {
5 return account;
6 }
7
8 public void save(int money) {
9 account.addAndGet(money);
10 }
11 }
六、守护线程
1、什么是守护线程?
在java中线程分两种
1)守护线程,如:垃圾回收线程
2)用户线程,应用程序中的自定义线程
区别:
1)main 方法中启动用户线程:
主线程执行完毕,用户线程没有执行完毕JVM无法退出,直到用户线程执行完毕
2) main 方法中启动守护线程
主线程执行完毕,用户线程没有执行完毕JVM正常退出
3)守护线程具有自动结束生命周期的特性,用户线程没有
在start() 方法前 线程调用setDaemon(true)方法,表示一个守护线程
七、五种单例模式
一个类始终只有一个对象,这种模式称为单例模式
优点:不需要反复创建对象,回收对象,节约内存空间,调用执行效率
缺点:并发操作导致线程不安全
1、懒汉模式
public class SingleOne{
private static SingleOne single;
private SingleOne(){};
public static syschronized SingleOne getInstance(){
if(single == null){
single = new SingleOne ();
}
return single;
}
}
2、饿汉模式
public class SingleTwo{
private static SingleTwo single= new SingleTwo();
private SingleTwo(){};
public static SingleTwo getInstance(){
return single;
}
}
3、枚举模式
public enum SingleThree{
INSTANCE;
}
4、静态内部类模式
public class SingleFour{
private SingleFour(){};
private static class getSingle(){
private static final SingleFour SINGLE = new SingleFour();
}
public static SingleFour getInstance(){
return getSingle.SINGLE;
}
}
5、双重检查模式
public class SingleFive{
private static volatile SingleFive single;
private SingleFive(){};
public static SingleFive getInstance(){
//多线程进入 th1 th2 th3 如果 th2 发现th1 被实例化,th3 不再等待
if(single == null){
//如果对象为空锁住代码块 th1 进入,th2、th3 等待
syschronized (SingleFive.class){
//双重检查 防止多次实例化
if(single == null){
//th1实例化
single = new SingleFive();
}
}
}
return single;
}
}