-
【JAVA】笔记(20)--- 死锁;如何解决线程安全问题;守护线程;定时器;Call
死锁:
1.特点:由于死锁不会出现异常,也不会出现错误,所以程序会一直僵持在这里,因此这种错误很难调试;
2.死锁模型:
public class ThreadPra1 {
public static void main(String[] args) {
//创建object1,object2对象
Object object1=new Object();
Object object2=new Object();
//让thread1线程和thread2线程,共享object1,object2对象
Thread thread1=new MyThread1(object1,object2);
Thread thread2=new MyThread2(object1,object2);
//依次启动thread1线程和thread2线程,“死锁现象”发生
thread1.start();
thread2.start();
}
}
class MyThread1 extends Thread{
Object object1;
Object object2;
public MyThread1(Object object1, Object object2) {
this.object1 = object1;
this.object2 = object2;
}
public void run() {
//此线程先拿object1的对象锁,再拿object2的对象锁
synchronized (object1){
//让thread1线程拿到object1锁之后,先睡1秒,确保thread2线程能拿到object2锁
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (object2){
}
}
}
}
class MyThread2 extends Thread{
Object object1;
Object object2;
public MyThread2(Object object1, Object object2) {
this.object1 = object1;
this.object2 = object2;
}
public void run() {
//此线程先拿object2的对象锁,再拿object1的对象锁
synchronized (object2){
synchronized (object1){
}
}
}
}
如何解决线程安全问题:、
第一种方案:尽量使用局部变量代替 " 实例变量和静态变量 " ;
第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了)
第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择 ” synchronized 线程同步机制 “ 了;
synchronized 会让程序的执行效率降低,用户体验不好,系统的用户吞吐量降低,用户体验差,在不得已的情况下再选择线程同步机制;
守护线程:
1.特点:死循环,守护线程一直执行,直到所有的用户线程结束,守护线程自动结束(例如垃圾回收线程);
2.实现守护线程: 调用方法 --- 线程 . setDaemon ( true ) ;
public class ThreadPra1 {
public static void main(String[] args) {
BakDataThread bakDataThread=new BakDataThread();
bakDataThread.setName("备份数据线程");
//将bakDataThread线程设置为守护线程
bakDataThread.setDaemon(true);
bakDataThread.start();
for (int i=0;i<10;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("正在下载文件...");
}
}
}
class BakDataThread extends Thread{
public void run() {
int i=1;
//死循环
while (true){
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"执行"+(i++)+"次");
}
}
}
运行实例
定时器:
1.作用:间隔特定的时间,执行特定的程序;例如:每周要进行银行账户的总账操作;每天要进行数据的备份操作;
2.实现方式:
1)使用sleep方法,设置睡眠时间,醒来自动继续执行任务,这种方式是最原始的定时器(现在很少使用);
2)java. util . Timer //专门的一个定时器类
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class ThreadPra1 {
public static void main(String[] args) throws ParseException {
//创建Timer对象
Timer timer=new Timer();
//指定时间格式
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
//获取此时刻时间
Date date=sdf.parse(sdf.format(new Date()));
//执行定时器任务,开始时间为现在,间隔时间为5秒
timer.schedule(new LogTimerTask(),date,5000);
//执行定时器任务,开始时间为现在,间隔时间为1秒
timer.schedule(new TimerTask() {
public void run() {
System.out.println("正在导入数据...");
}
}, date, 1000);
}
}
//自定义的定时器任务类,继承 TimerTask,并重写run方法
class LogTimerTask extends TimerTask{
public void run() {
SimpleDateFormat sdf=new SimpleDateFormat("yyyy-MM-dd HH-mm-ss");
System.out.println(sdf.format(new Date())+":成功备份数据");
}
}
运行实例
#总结 Timer 的使用:
第一,自定义定时器任务类(继承TimerTask),重写 run 方法;
第二,创建Timer对象;
第三,调用 Timer. schedule ( 定时器任务对象 , Date对象,间隔多长毫秒执行一次 ) // Date 对象对应时间为,定时器任务第一次执行的时间
3)实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务;
实现线程的第三种方式:
1.实现 Callable 接口 ( JDK8新特性),这种方式实现的线程可以获取线程的返回值;之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void;
2.Callable 的使用:
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class ThreadPra1 {
public static void main(String[] args) throws Exception{
//创建FutureTask对象,同时利用匿名内部类实现 Callable接口
FutureTask task=new FutureTask(new Callable() {
//call方法就相当于run方法,只不过call方法有返回值
public Object call() throws Exception {
int a=100;
int b=100;
//模拟分支线程执行三秒后,结束run方法
Thread.sleep(3000);
//自动装箱机制
return a+b;
}
});
Thread thread=new Thread(task);
thread.start();
System.out.println("分支线程执行结果:"+task.get());
System.out.println("分支线程真墨迹,主线程表示我终于能从阻塞状态中解放了!");
}
}
运行实例
#总结 Callable 的使用:
第一,创建 FutureTask 对象,同时往构造方法里传匿名内部类(实现了Callable 接口,重写call方法)
第二,创建 Thread 对象,同时往构造方法里传上面创建的 FutureTask 对象;
第三,启动 Thread 对象线程;
3.这种实现线程方式的优缺点:
优点:可以获取线程的执行结果;
缺点:效率较低,在获取线程执行结果的时候,当前线程受阻塞,导致效率降低;
Object 类中的 wait ( ) 和 notify ( ) :
1.wait 方法和 notify 方法的调用:
Object 对象 . wait ( ) ; // 使正在Object 对象上活动的当前线程进入无期限等待,直到被唤醒为止;
Object 对象 . notify( ) ; // 唤醒一个正在 Object 对象上等待的线程;
Object 对象 . notifyAll ( ) // 唤醒 Object 对象上处于等待的所有线程;
2.通过 wait ( ) 和 notify ( ) 实现 ” 生产者和消费者模式 “:
import java.util.ArrayList;
import java.util.List;
public class ThreadPra1 {
public static void main(String[] args) throws Exception{
List list=new ArrayList();
Thread thread1=new Thread(new Producer(list));
Thread thread2=new Thread(new Consumer(list));
thread1.setName("生产者线程");
thread2.setName("消费者线程");
thread1.start();
thread2.start();
}
}
class Producer implements Runnable{
private List list;
public Producer(List list) {
this.list = list;
}
public void run() {
//锁list对象,使list对象成为生产者和消费者的共有资源
synchronized (list){
//死循环
while (true){
//仓库满仓时,使生产者线程陷入等待
if (list.size()>0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//储备仓库
list.add(new Object());
System.out.println(Thread.currentThread().getName()+"--->"+list.get(0));
//唤醒消费者线程进行消费
list.notify();
}
}
}
}
class Consumer implements Runnable{
private List list;
public Consumer(List list) {
this.list = list;
}
public void run() {
//list对象为共有资源
synchronized (list){
//死循环
while (true){
//仓库空仓时,使消费者线程陷入等待
if (list.size()==0){
try {
list.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"--->"+list.get(0));
//清空仓库
list.remove(0);
//唤醒生产者线程进行生产
list.notify();
//list.notifyAll();把生产者和消费者都唤醒也行,因为消费者即使被唤醒,也会被if条件句拦截而再次陷入等待
}
}
}
}
运行实例
重点:
Object . wait 方法会让正在 Object 对象上活动的当前线程进入等待状态,并且释放之前占有的 Object 对象的锁;
Object . notify 方法只会通知,不会释放之前占有的o对象的锁,并继续执行线程,直到 run 方法结束;
wait方法和notify方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,存在线程安全问题;
原文:https://www.cnblogs.com/Burning-youth/p/15640865.html