17.多线程(2)

本文介绍Java中定时器的使用方法及线程同步的重要性,包括定时器类的实现和不同类型的线程同步方式,并提供了多个代码示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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不要切换到其他的线程工作,这时候就需要线程同步。
    
  • 需要线程同步的原因

    1. 什么时候需要线程同步

      • 当多线程并发执行,有多段代码同时执行,我们希望某一段代码执行的过程中cpu不要切换到其他的线程工作,这时候就需要线程同步。
      • 如果两段代码(两个线程任务)是同步的,那么同一时间只能执行一段代码,相当于给该线程上了一把锁,在该线程没有执行完这段代码的时候,其他线程是不能占用cpu执行代码的,直到执行完了该线程的代码,才可以换其他线程执行。(上厕所)
    2. 同步代码块

      • 使用synhronized关键字修饰方法或者代码块,就可以实现被修饰的方法或者代码上锁,实现线程同。
      • 多个代码块如果使用了相同的锁对象,那么他们就是同步的
      • 多线程并发操作同一数据时,可能就会出现线程安全问题。
      • 使用线程同步问题就可以解决这一问题,把操作数据的代码进行同步,不要多个线程同时其操作数据。
    3. 案例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{
      	
      }
      
    4. 案例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");
      	}
      }
      
    5. 案例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();
      	}
      }
      
    6. 案例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)
        在这里插入图片描述

    7. 案例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();
      	}
      }
      
    8. 案例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+"张票");
      			}
      		}
      		
      	}
      
      }
      
    9. 案例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操作时需要花费大量的时间的时候,比如读写文件、上传下载视频音频、处理显示、保存等操作的时候。

  • 因此,为了达到安全性和效率的平衡,可以根据实际的应用场景来选择合适的类型

  • 常见的线程安全的类型对比:

    1. HashTable和HashMap
      • HashTable线程安全,效率较低,键和值都不允许为null;
      • HashMap非线程安全,效率较高,键和值都允许为null;
    2. ArrayList和Vector
      • Vector线程安全,效率较低;
      • ArrayList非线程安全,效率较高;
    3. StringBuffer和StringBuilder
      • StringBuffer线程安全,效率较低;
      • StringBuilder非线程安全,效率较高;
  • 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);
    	}
    }
    
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值