多线程
大纲
1、多线程的创建方法
a.继承Thraad
b、实现Runnable接口
c、实现Callable接口
2、线程中的常见方法
3、线程的并发问题
4、Volatiel的作用及用法
5、synchronized的多种写法
a、public synchronized void test()
b、public synchronized static void test()
c、直接锁执行的任务内容
6、线程死锁
7、 wait() notify() notifyAll()的作用及用法
8、 线程的状态
9、如何让线程以固定顺序运行
10、java.util.concurrent.* java并发工具包
a、 线程池的4中创建
(1) 创建固定大小的线程池:Executors.newFixedThreadPool(2);
(2)创建缓冲线程池:Executors.newCachedThreadPool()
(3)创建单线程线程池:Executors.newSingleThreadExecutor()
(4)带有日程安排功能的线程池: Executors.newScheduledThreadPool(5);
b、原子操作类
c、线程安全集合类
d、ThreadLocal
应用程序以进程为单位运行,一个进程之内可以分为一到多个线程
操作系统中有一个组件叫做任务调度器,将cpu的时间片分给不同的程序使用, 微观串行(单核),宏观并行.(多核cpu可以并行)
好处:
1) 多进程,多线程可以让程序不被阻塞.
2) 充分利用多核cpu的优势,提高运行效率
多线程的创建方法
Thread 类
创建线程方法1:
Thread t = new Thread() {
public void run() {
// 线程需要执行的代码
}
};
启动线程 t.start()
该方法不能抛出检查异常,因为父类没有。
并行(parallel)和并发(concurrent)
创建线程的方法2:
实现Runnable接口,重写Run方法
Runnable runnable = new Runnable() {
public void run(){
}
};
Thread t = new Thread( runnable );
t.start();
//将接口子类对象当成参数传给Thread,然后进行启动
例子:
/*Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("hello");
}
};*/
Runnable r = () -> System.out.println("hello");
// () -> System.out.println("hello");
Thread t = new Thread( r );
t.start();
2. 线程中的常见方法
Thread.sleep(long n); // 让当前线程休眠n毫秒
Thread.currentThread(); // 找到当前线程
main 方法实际是由主线程(main)来调用的
注意 可以使用jconsle 来查看某个java进程中线程的运行情况
实例方法:
start() 让线程启动, 只能调用一次,如果调用了多次会出现IllegalThreadStateException
直接调用run和使用start间接调用run的区别:
(1)直接调用run是在主线程中执行了run,没有启动新的线程
(2)使用start是启动新的线程,通过新的线程间接执行run
join() 等待某个线程执行结束,例:
static int r = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1= new Thread(()->{
System.out.println("t1运行开始");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("t1运行结束");
r = 100;
});
t1.start();
/*System.out.println("main开始");
t1.join();
System.out.println("main结束");*/
t1.join();
System.out.println(r);
}
join(long n) 表示等待线程结束,但是最多等待n毫秒
其它方法:
getName() 得到线程的名称
yield() 谦让
不推荐使用的方法
.stop() 让线程停止
.suspend() 让线程暂停
.resume() 让线程继续
默认情况下,java进程需要等待所有线程都运行结束,才会结束.
有一种特殊的线程叫做守护(守护的是主线程)线程,只要主线程运行结束,即使守护线程的代码没有执行完,也会跟着主线程一起结束,例:
Thread t1= new Thread(()->{
System.out.println("守护线程开始执行...");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("守护线程结束");
});
t1.setDaemon(true); // 设置该线程为守护线程
t1.start();
Thread.sleep(1000);
interrupt() 可以打断正在等待的线程(包括sleep, join的等待)例如:
Thread t = new Thread(()->{
try {
Thread.sleep(5000); // 被打断线程会抛出InterruptedException
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("结束 。。。");
});
t.start();
Thread.sleep(1000);
System.out.println("打断t线程。。。");
t.interrupt();
3. 线程的并发(Concurrent)
synchronized (同步关键字)
语法
synchronized(对象也可以理解为锁) {
要作为原子操作代码
}
用synchronized 解决并发问题:
static int i = 0;
static Object obj = new Object(); // 房间,能容纳一个人
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> { // 甲
for (int j = 0; j < 5000; j++) {
synchronized (obj) { // 甲会锁住这个房间
i++;
} // 甲从房间出来解开了锁
}
});
Thread t2 = new Thread(() -> { // 乙
for (int j = 0; j < 5000; j++) {
synchronized (obj) { // 乙 在门外等待
i--;
} //
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
每个对象都有一个自己的monitor(监视器),当一个线程调用synchronized(对象),就相当于进入了这个对象的监视器。要检查有没有owner,如果没有,此线程成为owner; 但如果已经有owner了,这个线程在entryset的区域等待owner的位置空出来。
成为owner可以理解为获得了对象的锁
在竞争的时候,是非公平的
synchronized必须是进入同一个对象的monitor 才有上述的效果
4. volatile 易变的
注意:每个线程都存在一个高速缓存区
Volatile可以用来修饰成员变量和静态成员变量,他可以防止线程从自己的高速缓存中查找变量的值,必须到主存中获取它的值。
它保证的是变量在多个线程之间的可见性, 不能保证原子性
static boolean run = true;
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(()->{
while(run){
// ....
}
});
t.start();
Thread.sleep(1000);
run = false;
}
一个线程对run变量的修改对于另一个线程不可见,导致了另一个线程无法停止;
当run变量前有Volatiel修饰时,就不会出现这种情况了。
synchronized 语句块既可以保证代码块的原子性,也可以保证代码块内变量的可见性。但缺点是synchronized是属于重量级操作,性能会受到影响。
5. synchronized的另外两种写法
public synchronized void test() {
}
等价于
public void test() {
synchronized(this) {
}
}//锁的是调用该方法的对象
class Test{
public synchronized static void test() {
}
}
等价于
public static void test() {
synchronized(Test.class) {
}
}
//锁的是字节码文件.class
6. 线程死锁
a 线程 获得 A 对象 锁
接下来获取B对象的锁
b 线程获得 B对象 锁
接下来获取A对象的锁
例:
Object A = new Object();
Object B = new Object();
Thread a = new Thread(()->{
synchronized (A) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (B) {
System.out.println("操作...");
}
}
});
Thread b = new Thread(()->{
synchronized (B) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (A) {
System.out.println("操作...");
}
}
});
a.start();
b.start();
检测死锁可以使用 jconsole工具
7. wait() notify() notifyAll()
都属于Object对象的方法
wait() 等待
notify() 唤醒
它们都是线程之间进行协作的手段
obj.wait(); 让object监视器的线程等待
obj.notify(); 让object上正在等待的线程中挑一个唤醒
obj.notifyAll(); 让object上正在等待的线程全部唤醒
必须获得此对象的锁,才能调用这几个方法
Object obj = new Object();
new Thread(()-> {
synchronized (obj) {
System.out.println("thread-0线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-0其它代码....");
}
}).start();
new Thread(()-> {
synchronized (obj) {
System.out.println("thread-1线程执行....");
try {
obj.wait(); // 让线程在obj上一直等待下去
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("thread-1其它代码....");
}
}).start();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("唤醒obj上其它线程");
synchronized (obj){
// obj.notify();
obj.notifyAll();
}
}
wait() 方法实际是会释放对象的锁,进入WaitSet等待区,从而让其他线程就机会获取对象的锁。无限制等待,直到notify为止
wait(long n) 有时限的等待, 到n毫秒后结束等待,或是被notify
面试题:sleep(long n) 睡眠n毫秒, wait(long n) 等待n毫秒
1) sleep是Thread方法,而wait是Object的方法
2) sleep不需要强制和synchronized配合使用,但wait需要和synchronized一起用
3) sleep 在睡眠的同时,不会释放对象锁的,但wait在等待的时候会释放对象锁。
sleep及前者得等候一个线程执行完后才能执行后续线程;wait及后者无需等待,可以先执行后续线程。
8. 线程的状态
NEW(新建) 线程刚被创建,但是还没有调用 start方法
RUNNABLE(可运行) 当调用了start() 方法之后
BLOCKED(阻塞) 当线程进入了monitor监视器区,处于entrySet里准备竞争锁的时候,处于阻塞状态
WAITING(等待) 当调用了对象的wait方法,或调用了线程对象的join方法,进入了WaitSet,处于等待状态
TIMED_WAITING 当调用wait(long n) join(long n) 进入了WaitSet,处于有限时的等待状态
当调用sleep(long n) 是让当前线程放弃cpu的时间片,睡眠一会
TERMINATED (终止)当线程代码运行结束
五种状态:
NEW(新建), RUNNABLE(可运行) , RUNNING(正在运行), 阻塞(BLOCKED,WAITING, TIMED_WAITING )TERMINATED(终止)
9. 如何让两个线程以固定的顺序运行
static Object obj = new Object();
static boolean t2runed = false;// t2是否执行过
// 打印 2, 1
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (obj) {
while(!t2runed) { // 如果t2没有执行过
try {
obj.wait(); // 线程t1 先等一会
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
System.out.println(1);
});
Thread t2 = new Thread(()->{
System.out.println(2);
synchronized (obj) {
t2runed = true;
obj.notify();
}
});
t1.start();
t2.start();
}
java.util.concurrent.* java并发工具包
1. 创建线程的第三种方式
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
与Runnable的区别,1)有返回结果,2)可以抛出检查异常
创建代码:
// 代表一个任务对象
// Callable代表线程中要执行的代码
FutureTask task = new FutureTask(new Callable() {
@Override
public Object call() throws Exception {
System.out.println(Thread.currentThread().getName()+"开始执行");
Thread.sleep(2000);
return "ok";
}
});
// 创建和启动新线程
new Thread(task).start();
// 获取返回结果
System.out.println(task.get());
2. 线程池
创建有限的线程资源为更多的任务提供服务。享元模式
// java中对线程池的抽象:ExecutorService 创建一个固定大小的线程池
ExecutorService threadPool = Executors.newFixedThreadPool(2);
/*for (int i = 0; i < 10; i++) {
threadPool.submit(()->{
System.out.println(Thread.currentThread().getName()+"任务执行");
});
}*/
// 执行带有返回结果的任务
Future future = threadPool.submit(() -> {
System.out.println(Thread.currentThread().getName()+"执行计算...");
Thread.sleep(1000);
return 10;
});
System.out.println(future.get());
threadPool.shutdown(); // 不接收新任务,当所有任务运行结束,整个线程池关闭
一个核心的ExecutorService的实现类:ThreadPoolExecutor
ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue)
corePoolSize 核心线程数目 (最多保留的线程数)
5
maximumPoolSize
10
workQueue 阻塞队列 如果任务超过了核心线程数,进入队列进行排队,直到有空闲的线程
10
如果任务过多,阻塞队列都放不下了,还会创建新的线程来救急
corePoolSize+救急的线程 <= maximumPoolSize(最大线程数)
21 会抛出拒绝提交任务异常
keepAliveTime 生存时间- 针对救急线程
60
unit 时间单位 秒
// 创建固定大小的线程池
Executors.newFixedThreadPool(2);
核心线程数=最大线程数(没有救急线程被创建)
阻塞队列 无界,可以放任意数量的任务,
适合执行数量有限,长时间运行的任务
// 创建缓冲线程池
Executors.newCachedThreadPool()
核心线程数是0, 最大线程数是Integer的最大值(救急线程可以无限创建)
生存时间是60s
适合任务数比较密集,但每个任务执行时间较短的情况
// 创建单线程线程池
Executors.newSingleThreadExecutor()
使用场景:希望多个任务排队执行
区别:
Executors.newSingleThreadExecutor() 线程个数始终为1,不能修改
Executors.newFixedThreadPool(1) 初始时为1,以后还可以修改
// 带有日程安排功能的线程池
ScheduledExecutorService service = Executors.newScheduledThreadPool(5);
// 让任务推迟一段时间执行
// 参数1.任务对象, 参数2,3 推迟的时间
/*service.schedule(()->{
System.out.println("执行任务...");
}, 10L, TimeUnit.SECONDS);*/
// 以一定的频率反复执行任务(任务不会重叠)
// 参数1,任务对象, 参数2,初始推迟时间, 参数3,4 时间间隔和单位
/*service.scheduleAtFixedRate(()->{
try {
Thread.sleep(1200);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);*/
// delay表示从上一个任务结束,到下一个任务开始之间的时间
service.scheduleWithFixedDelay(()->{
System.out.println("hello");
}, 0, 1, TimeUnit.SECONDS);
// service.shutdown();
3. 原子操作类
AtomicInteger
AtomicBoolean
...
// 创建原子整数类
private static AtomicInteger i = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndIncrement(); // 获取并且自增 i++
// i.incrementAndGet(); // 自增并且获取 ++i
}
});
Thread t2 = new Thread(() -> {
for (int j = 0; j < 5000; j++) {
i.getAndDecrement(); // 获取并且自减 i--
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
4. 线程安全集合类
StringBuffer 线程安全
String 不可变类 , 都是线程安全的
Random 线程安全
Vector 实现了List,并且线程安全
Hashtable 实现了Map,并且线程安全
5.0新增的线程安全集合类
ConcurrentHashMap 实现了Map,并且线程安全
ConcurrentSkipListMap 实现了Map(可排序),并且线程安全
CopyOnWriteArrayList 实现了List,并且线程安全
BlockingQueue 阻塞队列
队列 FIFO , first in first out
Queue --> LinkedList
private static BlockingQueue<Product> queue = new ArrayBlockingQueue<>(5);
public static void main(String[] args) {
// 生产者线程
new Thread(()->{
for (int i = 0; i < 10; i++) {
Product p = new Product(i);
System.out.println(Thread.currentThread().getName()+"生产了:"+p);
try {
queue.put(p);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
// 消费者线程
for (int j = 0; j < 5; j++) {
new Thread(()->{
for (int i = 0; i < 2; i++) {
try {
Product p = queue.take();
System.out.println(Thread.currentThread().getName()+"消费了:"+p);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
5. ThreadLocal
// 线程局部变量
private static ThreadLocal<SimpleDateFormat> local = new ThreadLocal() {
@Override // 初始值
protected Object initialValue() {
return new SimpleDateFormat("yyyy-MM-dd"); // 存入当前线程
}
};
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
SimpleDateFormat sdf = local.get(); // 获取本线程自己的局部变量
Date date = sdf.parse("1951-10-09"); // 每个线程使用的是自己的SimpleDateFormat因此没有争用
System.out.println(Thread.currentThread().getName() + " " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}
/*for (int i = 0; i < 10; i++) {
new Thread(()->{
try {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
Date date = sdf.parse("1951-10-09");
System.out.println(Thread.currentThread().getName() + " " + date);
} catch (ParseException e) {
e.printStackTrace();
}
}).start();
}*/