复习
1.synchronized关键字
a.作用: 控制多行代码的原子性
b.用法:
同步代码块:
synchronized(锁对象){
需要同步的代码(需要保证原子性的代码)
}
同步方法:
public synchronized void 方法名(){
需要同步的代码(需要保证原子性的代码)
}
Lock锁:
Lock lock = new ReentrantLock();
lock.lock();
需要同步的代码(需要保证原子性的代码)
lock.unlock();
2.各种高并发情况下使用的线程安全的类
ArrayList线程不安全-->CopyOnWriteArrayList线程安全
HashSet线程不安全 --> CopyOnWriteArraySet线程安全
HashMap线程不安全--> HashTable(全局锁,性能较低),线程安全
ConcurrentHashMap(局部锁+CAS,性能较高)线程安全
Semaphore: 控制线程最多的并发数量
CountDownLatch: 可以运行一个线程等待另外一个线程执行完毕后再继续执行
CyclicBarrier: 可以让多个线程到达某种条件之后,再执行其他任务(五个人都到了,再开会)
Exchanger: 用于两个线程之间数据交换
今日内容
- 线程池[重点]
- 死锁
- 线程的状态[非常重要]
- 定时器[重点]
线程池方式
线程池的思想
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为频繁创建线程和销毁线程需要时间。
线程池的概念
- 线程池:其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
线程池的使用
-
线程池的顶级接口是:
java.util.concurrent.Executor
-
线程池的子接口:
java.util.concurrent.ExecutorService
-
线程池的工具类:
java.util.concurrent.Executors
建议使用Executors工程类来创建线程池对象。 -
Executors类中有个创建线程池的方法如下:
-
public static ExecutorService newFixedThreadPool(int nThreads)
:返回线程池对象。(创建的是有界线程池,也就是池中的线程个数可以指定最大数量)
-
-
获取到了一个线程池ExecutorService 对象,在这里定义了一个使用线程池对象的方法如下(提交任务):
-
public Future<?> submit(Runnable task)
:(向线程池提交无返回值的任务,返回值可以看做void)获取线程池中的某一个线程对象,并执行
Future接口:用来记录线程任务执行完毕后产生的结果。 Future接口中的方法为get() -
public Future<T> submit(Callable<T> task)
(向线程池中提交有返回值的任务),返回Future类型,表示返回封装线程执行完毕之后结果的对象
-
线程池代码实现
- 无返回值的
public class MyRunnable implements Runnable {
@Override
public void run() {
System.out.println("我要一个教练");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("教练来了: " + Thread.currentThread().getName());
System.out.println("教我游泳,教完后,教练回到了游泳池");
}
}
public class ThreadPoolDemo {
public static void main(String[] args) {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
MyRunnable r = new MyRunnable();
//自己创建线程对象的方式
// Thread t = new Thread(r);
// t.start(); ---> 调用MyRunnable中的run()
// 从线程池中获取线程对象,然后调用MyRunnable中的run()
service.submit(r);
// 再获取个线程对象,调用MyRunnable中的run()
service.submit(r);
service.submit(r);
// 注意:submit方法调用结束后,程序并不终止,是因为线程池控制了线程的关闭。
// 将使用完的线程又归还到了线程池中
// 关闭线程池
//service.shutdown();
}
}
- 使用Callable 有返回值的
public class ThreadPoolDemo2 {
public static void main(String[] args) throws Exception {
// 创建线程池对象
ExecutorService service = Executors.newFixedThreadPool(2);//包含2个线程对象
// 创建Runnable实例对象
Callable<Double> c = new Callable<Double>() {
@Override
public Double call() throws Exception {
return Math.random();
}
};
// 从线程池中获取线程对象,然后调用Callable中的call()
Future<Double> f1 = service.submit(c);
// Futur 调用get() 获取运算结果
System.out.println(f1.get());
Future<Double> f2 = service.submit(c);
System.out.println(f2.get());
Future<Double> f3 = service.submit(c);
System.out.println(f3.get());
}
}
死锁
死锁概念
在多线程程序中,使用了多把锁,造成线程之间相互等待.程序不往下走了。
应该尽量避免死锁
产生死锁的条件
1.有多把锁 2.有多个线程 3.有同步代码块synchronized嵌套
synchronized 获取不到锁, 就会一直等待
避免死锁
- 加锁顺序保持一致
- 使用ReentrantLock, 加锁方式可以设置时间,如果在时间内没有获得锁,返回false,可以避免死锁
死锁代码演示
public class Demo05 {
public static void main(String[] args) {
MyRunnable mr = new MyRunnable();
new Thread(mr).start();
new Thread(mr).start();
}
}
class MyRunnable implements Runnable {
Object objA = new Object();
Object objB = new Object();
/*
嵌套1 objA
嵌套1 objB
嵌套2 objB
嵌套1 objA
*/
@Override
public void run() {
synchronized (objA) {
System.out.println("嵌套1 objA");
synchronized (objB) {// t2, objA, 拿不到B锁,等待
System.out.println("嵌套1 objB");
}
}
synchronized (objB) {
System.out.println("嵌套2 objB");
synchronized (objA) {// t1 , objB, 拿不到A锁,等待
System.out.println("嵌套2 objA");
}
}
}
}
线程的状态[非常重点]
线程的六种状态
-
New(新建状态)
线程刚被创建,但是并未启动。还没调用start方法。MyThread t = new MyThread只有线程对象,没有线程特征。只有New状态的线程才能开始调用 start(). -
Runnable(可运行)
线程可以在java虚拟机中运行的状态,可能正在运行自己代码,也可能没有,这取决于操作系统处理器。处于新建状态的线程调用了t.start()方法 -
Blocked(锁阻塞)
当一个线程试图获取一个对象锁(线程运行的过程中遇到了同步方法,同步代码块,Lock锁),而该对象锁被其他的线程持有,则该线程进入Blocked状态;当该线程持有锁时,该线程将变Runnable状态。 -
Waiting(无限等待)
一个线程在等待另一个线程执行一个(唤醒)动作时,该线程进入Waiting状态。进入这个状态后是不能自动唤醒的,必须等待另一个线程调用notify或者notifyAll方法才能够唤醒。- 线程进入Waiting(无限等待状态)条件
- 该线程必须先持有锁对象
- 调用锁对象的wait方法,进入无线等待
- 进入无线等待之前,会自动释放锁对象
- 其他线程如何唤醒Waiting状态的线程条件
- 其他线程先持有锁对象(就是进入无限等待线程释放的那个锁对象)
- 调用锁对象的notify()方法,唤醒无限等待的线程
- 被唤醒的无限等待线程,先进入锁阻塞,直到再次持有锁对象才能进入可运行状态
- 线程进入Waiting(无限等待状态)条件
-
TimedWaiting(计时等待)
同waiting状态,有几个方法有超时参数,调用他们将进入Timed Waiting状态。这一状态将一直保持到超时期满或者接收到唤醒通知。带有超时参数的常用方法有Thread.sleep 、Object.wait。 -
Teminated(被终止)
因为run方法正常退出而死亡,或者因为没有捕获的异常终止了run方法而死亡。
等待和唤醒代码实现
Object类的方法
public void wait()
: 让当前线程进入到等待状态 此方法必须锁对象调用.此方法包含自动释放锁功能
public class Demo1_wait {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
new Thread(() -> {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();
}
System.out.println("over");
} catch (Exception e) {
}
}).start();
}
public void notify()
:(只能唤醒一个线程) 唤醒当前锁对象上等待状态的线程 此方法必须之前等待的锁对象调用.此方法不包含释放锁对象功能, 需要该线程结束后, 才能释放锁对象给唤醒的线程, 唤醒的线程才能继续执行锁对象内部的代码
public class Demo2_notify {
public static void main(String[] args) throws InterruptedException {
// 步骤1 : 子线程开启,进入无限等待状态, 没有被唤醒,无法继续运行.
new Thread(() -> {
try {
System.out.println("begin wait ....");
synchronized ("") {
"".wait();//进入无限等待之前,会自动释放锁对象
}
System.out.println("over");
} catch (Exception e) {
}
}).start();
//步骤2: 加入如下代码后, 3秒后,会执行notify方法, 唤醒wait中线程.
Thread.sleep(3000);
new Thread(() -> {
try {
synchronized ("") {
System.out.println("唤醒");
"".notify();
}
} catch (Exception e) {
}
}).start();
}
}
等待和唤醒注意事项
- 只有线程进入了无限等待,其他线程调用锁对象.notify() 才有作用,
- 锁对象.notify() 只能唤醒一个线程(因为该锁对象而进入无限等待的多个线程,当其他线程执行唤醒该线程方法并且释放该锁随想时, 会随机唤醒无限等待的线程中的一个)
- 锁对象.nofityAll方法可以唤醒多个线程, 谁抢到锁谁执行
定时器
定时器,可以设置线程在某个时间执行某件事情,或者某个时间开始,每间隔指定的时间反复的做某件事情
定时器的使用
java.util.Timer类:线程调度任务以供将来在后台线程中执行的功能。任务可以安排一次执行,或者定期重复执行。
-
构造方法
public Timer();
构造一个定时器 -
成员方法
public void schedule(TimerTask task, long delay);
在指定的延迟之后安排指定的任务执行。TimerTask是个抽象类,实现的是Runnable接口, 需要重写run();delay是指定的时间后.
public void schedule(TimerTask task, long delay, long period);
在指定的延迟之后开始该任务,重新执行固定延迟执行的指定任务。period是再过该时间段为周期
public void schedule(TimerTask task, Date time)
在指定的时间安排指定的任务执行。
public void schedule(TimerTask task, Date firstTime,long period);
从指定的时间开始,对指定的任务执行重复的 固定延迟执行该任务 。 -
定时器代码演示
public class Test{
public static void main(String[] args){
//1.设置一个定时器,2秒后启动,只执行一次
Timer t = new Timer();
t.schedule(new TimerTask(){
@Override
public void run(){
for(int i = 10;i >= 0 ; i--){
System.out.println("倒数:" + i);
try{
Thread.sleep(1000);
}catch(Exception e){}
}
System.out.println("嘭......嘭......");
//任务执行完毕,终止计时器
t.cancel();
}
},2000);
//2.设置一个定时器,5秒后开始执行,每一秒执行一次
Timer t2 = new Timer();
t2.schedule(new TimerTask(){
@Override
public void run(){
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
System.out.println(sdf.format(new Date()));
}
},5000,1000);
//3.设置一个定时器,在2030年01月01日零时开始执行,每隔24小时执行一次
Timer t3 = new Timer();
Calendar c = new GregorianCalendar(2030, 1-1, 1, 0, 0, 0);
Date d = c.getTime();
t3.schedule(new TimerTask(){
@Override
public void run(){
System.out.println("搜索全盘......");
}
},d,1000 * 3600 * 24);
}
}
今日小结
1.线程池【理解】
a.怎么创建???
ExecutorService service = Executors.newFixedThreadPool(int 线程个数);
b.提交任务
service.submit(Runnable 任务); //提交无返回值任务
Future<T> future = service.submit(Callable<T> 任务);//提交有返回值任务
通过 future.get() 该方法会阻塞,直到线程执行完毕,返回结果
c.关闭线程池
service.shutDown();
2.死锁【了解】
a.多个线程
b.多把锁
c.嵌套获取锁
死锁只能尽量避免
3.线程的状态(等待和唤醒机制) 【掌握】
a.NEW(新建状态)
b.RUNNABLE(可运行状态)
c.TERMINATED(消亡状态)
d.BLOCKED(锁阻塞状态)
e.TIMED_WAITING(限时等待状态)
f.WAITING(无限等待状态)
怎么进入WAITING状态??
a.当前线程获取锁对象
b.调用锁对象.wait()方法
c.进入WAITING之前自动释放锁对象
其他线程怎么唤醒WAITING的线程??
a.其他线程持有锁对象
b.调用锁对象.notify()方法
c.WAITING的线程就会醒来,先进入BLOCKED状态,直到再次获取到锁对象
需要练习两个相关案例demo05和demo06
4.Timer【理解】
四个方法
public void schedule(TimerTask task, long delay);
public void schedule(TimerTask task, long delay, long period);
public void schedule(TimerTask task, Date time);
public void schedule(TimerTask task, Date firstTime,long period);
网友评论