8.定时器
定时器可以用于比如系统每周删除一次日志文件,或者在指定日期关闭系统。
-
定时器类
import java.util.TimerTask; /** * 定时器 * 用于给线程任务定时操作 * */ public class MyTask extends TimerTask{ @Override public void run() { for (int i = 0; i <= 10; i++) { System.out.println(Thread.currentThread().getName()+ "正在执行" + i); } } }
-
测试
import java.util.Timer; public class TimeTest01 { public static void main(String[] args) { Timer timer = new Timer(); //设置5000毫秒之后执行该任务 //timer.schedule(new MyTask(), 5000); //1秒之后执行任务,并且每2秒执行一次线程 timer.schedule(new MyTask(), 1000, 2000); //在2020年12月31日21时54分12秒执行MyTask定时器run方法里面的任务,每200毫秒执行一次 Calendar time = new GregorianCalendar(2020,12,31,21,54,12); timer.schedule(new MyTask(), time.getTime(), 2000); } }
9.线程同步安全
非线程安全案例
-
子线程
public class Printer { public void print1() { System.out.print("这"); System.out.print("里"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("南"); System.out.print("昌"); System.out.print("\r\n"); } public void print2(){ System.out.print("我"); System.out.print("们"); System.out.print("都"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("老"); System.out.print("表"); System.out.print("\r\n"); } }
-
测试
public class ThreadTest { public static void main(String[] args) { Printer p = new Printer(); new Thread() { @Override public void run() { while(true) { p.print1(); } } }.start(); new Thread(new Runnable() { @Override public void run() { while(true) { p.print2(); } } }).start(); } }
-
结果
这里是江西南昌 这里是江西南昌 这里是江西南昌 这里是江西南昌 这里是江西南们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表 我们都是江西老表
-
原因
当执行"我们都是江西老表"这个线程任务的时候,由于还没有执行完就被其他线程抢占了cpu资源执行了"这里是江西南昌"这个线程任务,所以造成了数据紊乱,造成了数据不安全。 当多线程并发执行,有多段代码同时执行,我们希望某一段代码执行的过程中cpu不要切换到其他的线程工作,这时候就需要线程同步。
-
需要线程同步的原因
-
什么时候需要线程同步
- 当多线程并发执行,有多段代码同时执行,我们希望某一段代码执行的过程中cpu不要切换到其他的线程工作,这时候就需要线程同步。
- 如果两段代码(两个线程任务)是同步的,那么同一时间只能执行一段代码,相当于给该线程上了一把锁,在该线程没有执行完这段代码的时候,其他线程是不能占用cpu执行代码的,直到执行完了该线程的代码,才可以换其他线程执行。(上厕所)
-
同步代码块
- 使用synhronized关键字修饰方法或者代码块,就可以实现被修饰的方法或者代码上锁,实现线程同。
- 多个代码块如果使用了相同的锁对象,那么他们就是同步的
- 多线程并发操作同一数据时,可能就会出现线程安全问题。
- 使用线程同步问题就可以解决这一问题,把操作数据的代码进行同步,不要多个线程同时其操作数据。
-
案例1
public class Printer { Demo d = new Demo(); public void print1() { //同步synchronized同步代码块上锁 //当这段代码没有执行完,其他线程不能抢占cpu资源 //两个代码块只要是同一个对象就代表同一把锁,锁对象可以是任意类型 synchronized (d) { //锁对象不能是匿名数组,因为匿名对象不是同一个对象 // synchronized (new Demo()) { System.out.print("这"); System.out.print("里"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("南"); System.out.print("昌"); System.out.print("\r\n"); } } public void print2(){ synchronized (d) { System.out.print("我"); System.out.print("们"); System.out.print("都"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("老"); System.out.print("表"); System.out.print("\r\n"); } } } class Demo{ }
-
案例2
非静态方法使用同步代码块
public class Printer { public void print1() { //非静态的同步方法的锁是什么? //非静态的同步方法的锁对象,可以用this synchronized (this) { System.out.print("这"); System.out.print("里"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("南"); System.out.print("昌"); System.out.print("\r\n"); } } //synchronized修饰方法,同步方法 public synchronized void print2(){ System.out.print("我"); System.out.print("们"); System.out.print("都"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("老"); System.out.print("表"); System.out.print("\r\n"); } }
-
案例3:静态线程同步方法
public class Printer { public static void print1() { //静态的同步方法的锁是什么? //静态的同步方法的锁对象是该类的字节码对象 synchronized (Printer.class) { System.out.print("这"); System.out.print("里"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("南"); System.out.print("昌"); System.out.print("\r\n"); } } //静态同步方法,作用和上面print1方法一致 public static synchronized void print2(){ System.out.print("我"); System.out.print("们"); System.out.print("都"); System.out.print("是"); System.out.print("江"); System.out.print("西"); System.out.print("老"); System.out.print("表"); System.out.print("\r\n"); } }
测试
public class ThreadTest { public static void main(String[] args) { new Thread() { @Override public void run() { while(true) { Printer.print1(); } } }.start(); new Thread(new Runnable() { @Override public void run() { while(true) { Printer .print2(); } } }).start(); } }
-
案例4:模拟多人抢票
-
结果
抢票软件购买了第3张票,还剩下7张票 学生购买了第3张票,还剩下7张票 黄牛购买了第3张票,还剩下7张票 黄牛购买了第6张票,还剩下4张票 抢票软件购买了第6张票,还剩下4张票 学生购买了第6张票,还剩下4张票 学生购买了第9张票,还剩下1张票 黄牛购买了第9张票,还剩下1张票 抢票软件购买了第9张票,还剩下1张票 学生购买了第10张票,还剩下0张票
-
原因如图
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tV1thgXi-1602504972528)(D:\downfile\QQ\xian7 (1)].png)
-
-
案例5;通过线程同步实现多人抢票
public class Site implements Runnable{ private int count = 10;//票数 private int num = 0;//购买第几张票 @Override public void run() { while(true) { if(!qiangpiao()) { break; } } } public synchronized boolean qiangpiao() { while(true) { if(count<=0) { return false; } num++; count--; try { //线程休眠,增加其他线程买到票的机率 Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"购买了第"+num+"张票,还剩下"+count+"张票"); return true; } } }
public class Test { public static void main(String[] args) { Site site = new Site(); Thread t1 = new Thread(site,"学生"); Thread t2 = new Thread(site,"抢票软件"); Thread t3 = new Thread(site,"黄牛"); t1.start(); t2.start(); t3.start(); } }
-
案例6:使用synchronized同步代码块实现多人抢票
public class Site implements Runnable{ private int count = 10;//票数 private int num = 0;//购买第几张票 @Override public void run() { while(true) { synchronized (this) { if(count<=0) { break; } num++; count--; try { //线程休眠,增加其他线程买到票的机率 Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"购买了第"+num+"张票,还剩下"+count+"张票"); } } } }
-
案例7:利用多线程实现老人和年轻人爬山
/** * 老人和年轻人爬山 * @author Administrator * */ public class ClimbThread extends Thread{ private int time;//爬100的时间 private int num=0;//爬多少个100米 public ClimbThread(String name, int time, int kilometer) { super(name); this.time = time; this.num = kilometer/100; } @Override public void run() { while(num>0) { try { //线程休眠 Thread.sleep(this.time); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"爬完100米!"); num--; } System.out.println(Thread.currentThread().getName()+"到达终点"); } }
测试
public class Test { public static void main(String[] args) { ClimbThread youngMan = new ClimbThread("年轻人",500,1000); ClimbThread oldMan = new ClimbThread("老年人",1500,1000); Thread t1 = new Thread(youngMan,"---年轻人--"); Thread t2 = new Thread(oldMan,"老年人"); t1.start(); t2.start(); } }
-
10.线程安全的类型
方法是否同步 | 效率 | 适用场景 | |
---|---|---|---|
线程同步 | synchronized修饰的方法或者synchronized代码块 | 低 | 多线程并发共享数据 |
非线程同步 | 否 | 高 | 单线程 |
-
了解了多线程的好处之后,我们了解了在什么情况下使用多线程技术,但是并不是所有的情况下使用多线程就是好事,因为多线程的情况下,cpu要花时间去维护,cpu处理各线程的请求时在线程间也要花时间,所以一般情况下是可以不用多线程,用了反而得不偿失,大多情况下,要用到多线程的主要是需要处理大量的IO操作时需要花费大量的时间的时候,比如读写文件、上传下载视频音频、处理显示、保存等操作的时候。
-
因此,为了达到安全性和效率的平衡,可以根据实际的应用场景来选择合适的类型
-
常见的线程安全的类型对比:
- HashTable和HashMap
- HashTable线程安全,效率较低,键和值都不允许为null;
- HashMap非线程安全,效率较高,键和值都允许为null;
- ArrayList和Vector
- Vector线程安全,效率较低;
- ArrayList非线程安全,效率较高;
- StringBuffer和StringBuilder
- StringBuffer线程安全,效率较低;
- StringBuilder非线程安全,效率较高;
- HashTable和HashMap
-
ArrayList如何实现线程安全
import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Vector; public class Test { public static void main(String[] args) { //非线程安全 ArrayList<Object> list1 = new ArrayList<Object>(); //解决措施1:Vector是和ArrayList一样的数组形式的存储的集合并且是线程安全的 Vector<Object> list2 = new Vector<>(); list2.add("123"); //解决措施2:通过Collections.synchronizedList(list1<T>)创建线程安全的集合 List<Object> list3 = Collections.synchronizedList(list1); } }