文章目录
一、死锁现象
1.1 死锁的概念
多个线程抢占共享资源而出现的相互等待的现象
1.2 举例
问题描述:
两个线程都抢占共享资源a与b,除非都拿到a资源与b资源,否则不会释放已有的资源。
共享资源a与b
public class Thread_lock {
public static Thread_lock a = new Thread_lock();
public static Thread_lock b = new Thread_lock();
}
线程:
public class Thread_sub extends Thread {
boolean flag;
public Thread_sub(boolean flag) {
this.flag = flag;
}
@Override
public void run() {
if(flag) {
synchronized (Thread_lock.a) {
System.out.println("线程一拿到了a资源");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(Thread_lock.b) {
System.out.println("线程二拿到了b资源");
}
}
}
else
synchronized(Thread_lock.b) {
System.out.println("线程二拿到了b资源");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized(Thread_lock.a) {
System.out.println("线程二拿到了a资源");
}
}
}
}
主函数:
public class Thread_main {
public static void main(String[] args) {
Thread_sub a = new Thread_sub(true);
Thread_sub b = new Thread_sub(false);
a.start();
b.start();
}
}
结果:
线程一拿到了a资源
线程二拿到了b资源
结果说明两个线程都只拿到了第一份资源,并且都等待对方释放已有的资源从而产生死锁现象。
结论:
- 在拿到第一个资源之后,要sleep一段时间,作用是给第二个线程时间去抢占另一个资源,从而产生死锁现象。
- 两个线程执行步骤(flag为true还是false)的判断条件可以封装进有参构造方法里。
二、生产者与消费者问题
2.1 问题描述
生产者生产资源,消费者消费资源。有资源时,生产者等待,通知消费者消费;
没资源时,消费者等待,通知生产者生产。
2.2 示例
生产者线程
public class produce_thread implements Runnable {
resource res = new resource();
public produce_thread(resource res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
//有资源等待
if (res.flag) {
try {
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//没资源生产(更新产品编号,使用年限)
res.ID = UUID.randomUUID().toString().substring(0, 3);
res.yearsOfUse = new Random().nextInt();
//改标志并通知消费者消费
res.flag = true;
res.notify();
}
}
}
}
消费者线程:
public class user_thread implements Runnable {
resource res = new resource();
public user_thread(resource res) {
this.res = res;
}
@Override
public void run() {
while (true) {
synchronized (res) {
//有资源消费
if (res.flag) {
System.out.println("资源的编号:" + res.ID + " " + "资源的使用年限" + res.yearsOfUse);
res.flag = false;
res.notify();
}
//没资源等待
try {
res.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
主函数:
public class thread_main {
public static void main(String[] args) {
resource res = new resource();
user_thread a = new user_thread(res);
produce_thread b = new produce_thread(res);
Thread user = new Thread(a);
Thread producer = new Thread(b);
user.start();
producer.start();
}
}
资源:
public class resource {
String ID;
int yearsOfUse;
boolean flag;
}
总结:
- 用继承Callable做本例的时候只能生产和消费一次,具体原因还未搞清楚。
- wait()方法的作用是:释放当前锁,线程重新进入抢占资源状态。
- notify()方法的作用时通知另一个线程去执行自己的任务。
2.3 sleep()方法和wait()方法的区别
相同点:
-
都可以将时间量作为参数
-
都可以使当前线程阻塞
不同点: -
sleep()方法不释放当前锁
-
wait()方法释放当前锁
三、线程池
3.1 线程池概述
线程池是一个容器,里面预先装有线程对象,并可以帮我们高效的管理线程对象我们自己手动创建线程,是比较耗费底层资源的,而且这个线程使用完之后,就死亡了,不能重复利用,JDK1.5之后,已经给我们提供好了线程池,我们只需要,往线程池里面提交任务即可。
3.2 获取线程池对象
Java给我们提供了一个工厂,用这个工厂类,来来获取线程池对象
-
public static ExecutorService newCachedThreadPool ()
根据任务的数量来创建线程对应的线程个数 -
public static ExecutorService newFixedThreadPool ( int nThreads)
固定初始化几个线程 -
public static ExecutorService newSingleThreadExecutor ()
初始化一个线程的线程池
3.3线程池常用方法
- submit(线程对象)
往线程池中提交任务 - shutdown()
关闭线程池
public class Thraedpool {
public static void main(String[] args) {
mythread a = new mythread();
mythread a1 = new mythread();
muRunnable b = new muRunnable();
muRunnable b1 = new muRunnable();
ExecutorService pool = Executors.newCachedThreadPool();
pool.submit(a);
pool.submit(a1);
pool.submit(b);
pool.submit(b1);
}
}
线程:
public class muRunnable implements Runnable{
@Override
public void run() {
System.out.println("qwq");
}
}
public class mythread extends Thread{
@Override
public void run() {
System.out.println("qwq");
}
}
四、定时器类 -Timer
4.1 作用
使线程执行的任务执行一次,或者定期重复执行。
4.2 Timer常用方法
- public void schedule (TimerTask task,long delay)
延迟给定时间后,执行任务 - public void schedule (TimerTask task,long delay, long period);
delay:执行第一个任务的延迟时间
period:后续任务之间的时间间隔 - public void schedule (TimerTask task, Date time):
在给定时间点开始执行任务 - public void schedule (TimerTask task, Date firstTime,long period):
- void cancel()
终止此计时器,丢弃所有当前已安排的任务。 - boolean cancel()
取消此计时器任务。
4.3 举例
public class Timer_main {
public static void main(String[] args) throws ParseException {
Timer timer = new Timer();
mytimertask task = new mytimertask(timer);
String str = "2019-01-30 17:06";
Date date = new SimpleDateFormat("YYYY-MM-dd HH:MM").parse(str);
timer.schedule(task,date,1000);
}
}
public class mytimertask extends TimerTask {
Timer timer = new Timer();
public mytimertask(Timer timer) {
this.timer = new Timer();
}
@Override
public void run() {
System.out.println("爆炸");
this.timer.cancel();
}
}
4.4 注意事项
- 如果要停止定时器,在子程序里使用cancel()方法,即在重写的run方法中,在主程序中使用cancle方法会是计时器任务停止执行。
- 注意period和delay的含义,不要搞混了。
五、volatile关键字
5.1 java内存模型
java内存模型规定所有的变量都放在主内存中,每个线程都有自己的工作内存,
线程的工作内存存放的的线程所要使用的变量,这些变量是从主内存中拷贝过来的, 线程对变量的所有操作都是在工作内存中进行的。不同线程间也不能直接读取对方工作内存的变量,需要通过主内存来间接完成。
5.2 可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即知道修改的值。
5.3 volatile关键字的作用
可以保证可见性,当一个共享变量(被多个线程操作的变量)被volatile关键字修饰时,这个关键字会保证这个共享变量被修改后的值立即更新到主内存中,这样其他线程使用该共享变量时拿到的就是该共享变量的最新值。而普通的共享变量不能保证内存可见性,因为普通变量一旦被修改,什么时候更新到主内存是不确定的,此时,其他线程读取该变量,在主内存中拿到的可能是该变量未更新的旧值。
通过synchronized和Lock也能够保证可见性,因synchronized和Lock能保证同一时刻只有一个线程获取锁然后执行同步代码。
5.4 举例
public class mythread extends Thread {
public static boolean flag = false;
@Override
public void run() {
while (!flag) {
}
}
}
public class Tread_main {
public static void main(String[] args) throws InterruptedException {
mythread thread = new mythread();
thread.start();
Thread.sleep(100);
mythread.flag = true;
}
}
参考:
Java的多线程机制系列:(四)不得不提的volatile及指令重排序(happen-before)
http://www.cnblogs.com/mengheng/p/3495379.html
Java之 Timer 类的使用以及深入理解
https://www.cnblogs.com/xiaotaoqi/p/6874713.html#a
Java并发编程:volatile关键字解析
http://www.importnew.com/18126.html