1、lock锁的概述和使用
a、概述:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK1.5以后提供了一个新的锁对象Lock
b、Lock和ReentrantLock
- ReentrantLock:一个可重入的互斥锁 Lock,它具有与使用 synchronized 方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。
- Lock:实现提供了比使用 synchronized 方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关的 Condition 对象。
c、Lock锁的使用
public class MyThread implements Runnable {
private static int tickets = 100 ; // 定义票数
private static final Lock lock = new ReentrantLock() ; // 创建Lock锁对象
@Override
public void run() {
while(true){
lock.lock() ; // 添加锁
if(tickets > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) + "张票");
}
lock.unlock() ; // 释放锁
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 创建MyThread类的对象
MyThread mt = new MyThread() ;
// 创建3个线程对象
Thread t1 = new Thread(mt , "窗口1") ;
Thread t2 = new Thread(mt , "窗口2") ;
Thread t3 = new Thread(mt , "窗口3") ;
// 启动线程
t1.start() ;
t2.start() ;
t3.start() ;
}
}
2、死锁问题概述和使用
a、概述:如果出现了同步嵌套,就容易产生死锁问题,是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象。
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
b、案例演示
class ObjectUtils {
//定义两把锁
public static final Object objA=new Object();
public static final Object objB = new Object();
}
class MyThread extends Thread {
boolean flag;
public MyThread(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if (flag) {
synchronized (ObjectUtils.objA) {
System.out.println("true 进来了 持有objA");
try {
sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//阻塞
synchronized (ObjectUtils.objB) {
System.out.println("true 进来了 持有objB");
}
}
} else {
synchronized (ObjectUtils.objB) {
System.out.println("false 进来了 持有objB");
//阻塞
synchronized (ObjectUtils.objA) {
System.out.println("false 进来了 持有objA");
}
}
}
}
}
public class MyDemo {
public static void main(String[] args) throws InterruptedException {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}
输出结果:true 进来了 持有objA
false 进来了 持有objB
原因:在执行了第一条程序后,相互持有了对方的锁而未释放,导致程序发生阻塞不能往下执行
3、线程间的等待唤醒机制
一旦等待,就要立马释放锁,下次被换醒之后,从哪里等待从哪里醒来。
- void wait ();//在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
- void wait ( long timeout);//在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。
- void notify ();//唤醒在此对象监视器上等待的单个线程。
- void notifyAll ();//唤醒在此对象监视器上等待的所有线程。
sleep方法和wait方法的区别:都可以让线程处于阻塞状态
sleep()必须传入时间量;wait可传时间量也可不传时间量
sleep()线程休眠后,不释放锁;wait()方法 一旦等待 就要释放锁
4、线程的状态转换图及常见执行情况
新建:线程被创建出来
就绪:具有CPU的执行资格,但是不具有CPU的执行权
运行:具有CPU的执行资格,也具有CPU的执行权
阻塞:不具有CPU的执行资格,也不具有CPU的执行权
死亡:不具有CPU的执行资格,也不具有CPU的执行权
5、线程池的概述和使用
a、概述:是一个容器,里面预先装有线程对象,并可以帮我们高效的管理线程对象,我们自己手动创建线程,是比较耗费底层资源的,而且这个线程使用完之后,就死亡了,不能重复利用;JDK1.5之后已经给我们提供好了线程池,我们只需要,往线程池里面提交任务即可.
-
获取线程池对象,Java给我们提供了一个工厂,用这个工厂类,来来获取线程池对象
-
ExecutorService executorService = Executors.newCachedThreadPool();
-
JDK5新增了一个Executors工厂类来产生线程池,有如下几个方法
-
public static ExecutorService newCachedThreadPool ();根据任务的数量来创建线程对应的线程个数
-
public static ExecutorService newFixedThreadPool ( int nThreads);固定初始化几个线程
-
public static ExecutorService newSingleThreadExecutor ();初始化一个线程的线程池
b、 举例:
public class MyDemo {
public static void main(String[] args) {
//获取线程池对象
ExecutorService executorService = Executors.newCachedThreadPool();
//往线程池中提交任务
MyRuannable myRuannable = new MyRuannable();
MyRuannable myRuannable2 = new MyRuannable();
MyRuannable myRuannable3 = new MyRuannable();
MyRuannable myRuannable4 = new MyRuannable();
executorService.submit(myRuannable);
executorService.submit(myRuannable2);
executorService.submit(myRuannable3);
executorService.submit(myRuannable4);
//关掉线程池
executorService.shutdown();
}
}
class MyRuannable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"任务执行了");
}
}
6、匿名内部类的方式实现多线程程序
public class MyDemo3 {
public static void main(String[] args) {
//匿名内部类来创建线程
//方式1
new Thread() {
@Override
public void run() {
System.out.println("线程执行了");
}
}.start();
//方式2:
Runnable runnable = new Runnable() {
@Override
public void run() {
System.out.println("线程执行了");
}
};
new Thread(runnable) {
}.start();
}
}
7、定时器的概述和使用
a、概述:定时器是一个应用十分广泛的线程工具,可用于调度多个定时任务以后台线程的方式执行。在Java中,可以通过Timer和TimerTask类来实现定义调度的功能。
b、Timer和TimerTask
- Timer:
public 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);//安排指定的任务在指定的时间开始进行重复的固定延迟执行。 - TimerTask
public abstract void run();//此计时器任务要执行的操作。
public boolean cancel();//取消此计时器任务。
c、案例演示
定时任务的多次执行代码体现
定时删除指定的带内容目录
public class MyDemo {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();//创建定时器
MyTimerTask task = new MyTimerTask(timer);
//timer.schedule(task,2000); //参数1 定时任务,参数 等待多少毫秒后执行
// timer.schedule(task,2000,1000);//连环爆炸
task.cancel(); 取消定时任务
//指定日期去执行任务
String dateStr="2019-01-20 13:46:00";
Date date = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(dateStr);
// timer.schedule(task,date,1000*60*60*24*30);
}
}
class MyTimerTask extends TimerTask{
Timer timer;
public MyTimerTask(Timer timer) {
this.timer=timer;
}
@Override
public void run() {
// System.out.println("砰~~~爆炸了");
// timer.cancel(); //关闭定时器
}
}