死锁:
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次 正在下载文件... Process finished with exit code 0
定时器:
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())+":成功备份数据"); } }
2021-12-04 00-35-54:成功备份数据 正在导入数据... 正在导入数据... 正在导入数据... 正在导入数据... 正在导入数据... 2021-12-04 00-35-59:成功备份数据 正在导入数据... 正在导入数据... 正在导入数据... 正在导入数据... 正在导入数据... 2021-12-04 00-36-04:成功备份数据 正在导入数据... 正在导入数据... 正在导入数据... 正在导入数据... 正在导入数据... 2021-12-04 00-36-09:成功备份数据 ......
#总结 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("分支线程真墨迹,主线程表示我终于能从阻塞状态中解放了!"); } }
分支线程执行结果:200 分支线程真墨迹,主线程表示我终于能从阻塞状态解放了! Process finished with exit code 0
#总结 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条件句拦截而再次陷入等待 } } } }
生产者线程--->java.lang.Object@7941b718 消费者线程--->java.lang.Object@7941b718 生产者线程--->java.lang.Object@77865640 消费者线程--->java.lang.Object@77865640 生产者线程--->java.lang.Object@73b9dc68 消费者线程--->java.lang.Object@73b9dc68 生产者线程--->java.lang.Object@36bb5c7b 消费者线程--->java.lang.Object@36bb5c7b 生产者线程--->java.lang.Object@42b6d4c3 消费者线程--->java.lang.Object@42b6d4c3 ......
重点:
Object . wait 方法会让正在 Object 对象上活动的当前线程进入等待状态,并且释放之前占有的 Object 对象的锁;
Object . notify 方法只会通知,不会释放之前占有的o对象的锁,并继续执行线程,直到 run 方法结束;
wait方法和notify方法建立在线程同步的基础上,因为多线程要同时操作一个仓库,存在线程安全问题;
ps:由于博主目前只是一只猿宝宝,所以有些地方可能说的有些片面,若前辈们能够指点一二就更好了 (~ ̄(OO) ̄)ブ