• java基础(6)


    1.初识多线程

    单线程与多线程:如果程序只有一条执行路径,那么该程序就是单线程程序;如果程序有多条执行路径,那么该程序就是多线程程序。

    想要了解多线程,我们就需要先了解线程,想要了解线程,就必须先了解进程,因为线程是依赖于进程的

    进程

    概述:进程是系统进行资源分配和调用的独立单位,每一个进程都有自己的内存空间和系统资源,简单的说,进程就是正在运行的程序,如网易云音乐,微信,Word等在运行的程序

    多进程的意义:

    单进程的计算机只能做一种事情,而我们现在的计算机都可以做多种事情,也就是说现在的计算机都是支持多进程的,可以在一个时间段内执行多个任务,如边玩游戏边听音乐,多进程的使用可以提高CPU的使用率

    问题:多个进程间是同时发生的么?

    答:不是,因为单CPU在某一个时间点上只能做一件事情,而多个进程能给我们同时进行的感觉是因为cpu在做着程序间的高效切换,现在的计算机都是多核的了,但是我们的进程往往是比cpu多的,所以,cpu还是需要高效切换来给我们达到同时进行多个进程的感觉

    线程

    概述:在同一个进程内又可以执行多个任务,而这每一个任务就可以看成是一个线程,线程是程序的执行单元,执行路径,是调用cpu的最小执行单位,单线程就是程序只有一条执行路径,而多线程就是有多条执行路径

    多线程的意义:

    多线程的存在,不是提高程序的执行速度,其实是为了提高程序的使用率;程序的执行其实都是在抢CPU的资源,CPU的执行权;多个进程是在抢这个资源,而其中的某一个进程如果执行路径比较多的话,就会有更高几率抢到CPU的执行权;我们是不敢保证哪一个线程能够在哪个时刻抢到的,所以线程的执行具有随机性

    并行与并发的区别

    并行是逻辑上的同时发生,指的是在某一个时间内同时运行多个程序;并发则是物理上的同时发生,指在某一个时间点同时运行多个程序

    java程序的运行原理

    由java命令启动JVM,JVM启动就相当于启动了一个进程,接着由该进程创建一个主线程去调用main方法

    问题:jvm虚拟机的启动是单线程的还是多线程的

    答:多线程的,因为垃圾回收线程也要先启动,不然很容易导致内存溢出,现在的垃圾回收线程加上前面的主线程,最少启动两个线程,所以说jvm是多线程的

    2.如何实现多线程

    由于线程是依赖进程存在的,所以我们应该先创建一个进程出来,而进程是由系统创建的,所以我们应该去调用系统功能创建一个进程,java是不能直接调用系统功能的,所以,我们没有办法直接实现多线程程序,但是java可以去调用C/C++写好的程序来实现多线程程序,由C/C++去调用系统功能创建进程,然后由java去调用这些东西,然后提供一些类供我们使用,我们就可以实现多线程程序了

    实现多线程的方法:

    • 方式一:继承Thread类
    • 方式二:实现Runable接口
    • 方式三:实现Callable接口

    继承Thread类实现多线程

    步骤:

    • 自定义类MyThread继承Thread类
    • MyThread类里面重写run()方法
      • 这个run方法用来包含那些被线程执行的代码
    • 创建对象
    • 启动线程

    run()和start的区别:

    run()方法仅仅是封装被线程执行的代码,直接调用是普通方法;start()方法首先启动了线程,然后由jvm去调用该线程的run()方法

    获取和设置线程对象的名称的方法(谁先抢到谁就是Thread-0):

    • public final String getName()
    • public final void setName(String name):设置线程的名称,也可以通过带参构造方法设置线程的名称

    针对不是Thrad类的子类中如何获取线程对象名称呢?

    • public static Thread currentThread():返回当前正在执行的线程对象
    • Thread.currentThread().getName():获取当前正在执行的线程对象的名称

    这种方式相比继承Thread类的好处:

    • 可以避免由于java单继承带来的局限性
    • 适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想

    代码实现:

    //MyThread.java
    
    public class MyThread extends Thread{
    	@Override
    	public void run() {
    		//一般来说,被线程执行的代码肯定是比较耗时的代码,所以我们使用循环
    		for(int i = 0; i < 200; i++){
    			System.out.println(getName() + ":" + i);
    		}
    	}
    }
    
    //MyThreadDemo.java
    
    public class MyThreadDemo {
    	public static void main(String[] args) {
    		MyThread mh1 = new MyThread();
    		MyThread mh2 = new MyThread();
    		
    		mh1.start();
    		mh2.start();
    	}
    }
    

    通过实现Runable接口实现多线程(推荐使用)

    步骤:

    • 自定义类MyRunnable实现Runnable接口
    • 重写run()方法
    • 创建MyRunnable类的对象
    • 创建Thread类的对象,并把C步骤的对象作为构造参数传递

    代码实现:

    //MyRunnable.java
    
    public class MyRunnable implements Runnable {
    
    	@Override
    	public void run() {
    		for(int i = 0; i < 100; i ++){
    			System.out.println(Thread.currentThread().getName() + ":" + i);
    		}
    	}
    
    }
    
    //MyRunnableDemo.java
    
    public class MyRunnableDemo {
    	public static void main(String[] args) {
    		MyRunnable my = new MyRunnable();
    		
    		/*Thread t1 = new Thread(my);
    		Thread t2 = new Thread(my);
    		//设置线程名字
    		t1.setName("线程一");
    		t2.setName("线程二");*/
    		//与上面代码等价
    		Thread t1 = new Thread(my, "线程一");
    		Thread t2 = new Thread(my, "线程二");
    		
    		t1.start();
    		t2.start();
    	}
    }
    

    通过实现Callable接口实现多线程

    概述:Callable是一个带泛型的接口,通过它做多线程的话需要依赖于线程池(后面回讲线程池)

    使用步骤:

    1.创建一个线程池对象,控制要创建几个线程对象

    • public static ExecutorService newFixedThreadPool(int nThreads):创建有多个线程对象的线程池

    2.调用如下方法即可:

    • Future submit(Callable task)
      • 返回值Future是一个接口,它的get方法返回计算结果

    3.线程池开启之后不会自动关闭,如果想关闭,需要执行以下方法

    • public void shutdown():关闭线程池

    示例代码1(不带泛型的Callable接口对象代表的多线程)

    //Mycallable.java
    
    import java.util.concurrent.Callable;
    //不带泛型的Callable对象代表的线程
    public class MyCallable implements Callable{
    
    	@Override
    	public Object call() throws Exception {
    		for(int i = 0; i < 100; i ++){
    			System.out.println(Thread.currentThread().getName() + ":" + i);
    		}
    		return null;
    	}
    
    }
    
    //ExecutorDemo.java
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ExecutorsDemo {
    	public static void main(String[] args) {
    		//创建一个线程对象,控制要创建几个线程对象
    		ExecutorService pool = Executors.newFixedThreadPool(2);
    		//执行Callable对象代表的线程
    		pool.submit(new MyCallable());
    		pool.submit(new MyCallable());
    		//关闭线程池
    		pool.shutdown();
    	}
    }
    

    实例代码2(带泛型的Callable对象代表的多线程,求和案例)

    //MyCallable2.java
    
    import java.util.concurrent.Callable;
    //带泛型的Callable对象代表的多线程
    public class MyCallable2 implements Callable<Integer>{
    
    	private int number;
    	public MyCallable(int number){
    		this.number = number;
    	}
    	@Override
    	public Integer call() throws Exception {
    		int sum = 0;
    		for(int x = 1; x <= number; x ++){
    			sum += x;
    		}
    		return sum;
    	}
    }
    
    // ExecutorsDemo2
    
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    import java.util.concurrent.Future;
    
    public class ExecutorsDemo2 {
    	public static void main(String[] args) throws InterruptedException, ExecutionException {
    		//创建一个线程对象,控制要创建几个线程对象
    		ExecutorService pool = Executors.newFixedThreadPool(2);
    		//执行Callable对象代表的线程
    		Future<Integer> f1 =  pool.submit(new MyCallable(100));
    		Future<Integer> f2 = pool.submit(new MyCallable(200));
    		
    		Integer i1 = f1.get();
    		Integer i2 = f2.get();
    		
    		System.out.println(i1);
    		System.out.println(i2);
    		//关闭线程池
    		pool.shutdown();
    	}
    }
    

    3.线程调度

    概述:假如我们的计算机只有一个CPU,那么,CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片,也就是使用权,才可以执行指令

    线程的两种调度模型:

    • 分时调度模型:所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间片
    • 抢占式调度模型:优先让优先级高的线程使用CPU,如果线程的优先级相同,那么,会随机选择一个,优先级高的线程获得的CPU时间片多一些,java就是使用这种模型

    那么,java中程序的优先级是怎么样的,可以设置优先级么?我们接下来就来看看java中的优先级是什么样的

    设置和获取线程对象优先级的方法:

    • public final void setPriority(int newPriority):更改线程的优先级
    • public final int getPriority():返回线程对象的优先级

    注意:

    • 线程默认优先级是5
    • 线程优先级的范围是1-10
    • 线程优先级高的仅仅表示线程获取的CPU时间片的几率高,要在次数比较多,或者多次运行的时候,才能看到明显的效果(优先级高的先执行)

    线程控制

    我们已经知道了线程的调度,接下来我们就可以使用如下方法对象来对线程进行控制

    线程休眠

    • public static void sleep(long mills):休眠mills毫秒

    线程加入

    • public static void join():使用了join方法的线程执行完后,才能执行其他线程

    线程礼让

    • public static void yield():暂停当前正在执行的线程对象,并执行其他线程,它会让多个线程的执行更加和谐一些,但是不能靠他保证一人一次

    后台线程(守护线程)

    • public final void setDaemon(boolean on):
      设置为true时,即设置为守护线程,当守护的线程结束了的话,它们再在cpu上跑一会就得结束线程,而不是完全执行完自己线程的内容再结束

    中断线程:

    • public final void stop():该方法已经过时了,具有不安全性,不推荐使用
    • public void interrupt():中断线程,把线程的状态终止,并抛出一个InterruptException异常

    4.线程生命周期

    线程的生命周期:

    • 新建:创建线程对象
    • 就绪:有执行资格,没有执行权
    • 运行:有执行资格,有执行权
      • 阻塞:由于一些操作让线程处于了该状态,没有执行资格,没有执行权,而另一些操作却可以把它激活,激活后处于就绪状态
    • 死亡:线程对象变成垃圾,等待被回收

    多线程的状态转换图如下:

    5.线程安全问题

    再讲解我们的线程安全问题之前,我们先来看一个案例:

    三个窗口同时出售一百张电影票的案例:

    //MyRunnable2.java
    
    public class MyRunnable2 implements Runnable{
    
    	private int ticket = 100;
    	@Override
    	public void run() {
    		while(true){
    			if(ticket > 0){
    				try {
    					//在真实场景中,会有网络延迟等问题,所以添加了100ms的延迟
    					Thread.sleep(100);
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    				System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "票");
    			}else{
    				break;
    			}	
    		}
    	}
    
    }
    
    //Demo2.java
    
    public class Demo2 {
    	public static void main(String[] args) {
    		MyRunnable2 my = new MyRunnable2();
    		
    		Thread t1 = new Thread(my, "窗口1");
    		Thread t2 = new Thread(my, "窗口2");
    		Thread t3 = new Thread(my, "窗口3");
    		
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    如果你复制这段代码并执行了几次,就会发现,执行结果居然有卖相同张票,第0张票甚至卖负数的票的情况,这是为什么呢?

    出现相同票的原因:CPU的一次操作必须是原子性的,
    由于我们的程序对ticket变量的操作不是原子性的,这就会导致当我们再卖出某一张票之后,还没等到对ticket变量--,下一个线程就接着卖这同一张票了

    出现负票的原因:随机性和延迟导致的,因为有了延迟和随机性,导致三个线程都进去了if判断语句,但是要等100ms,然后再执行下一个,所以就会导致当卖出第1张票之后,还会卖出第0和负数票

    通过上面这个案例,我们了解到了多线程的不安全性,那产生线程不安全的原因以及怎么解决,接下来就要正式来说说了

    线程安全问题产生的原因(以后我们判断一个程序是否会有线程安全问题的标准):

    • 是否有多线程环境
    • 是否有共享数据
    • 是否有多条语句操作共享数据

    怎么解决线程安全问题

    通过看产生线程安全问题的原因,我们可以知道,前两种导致线程不安全的原因我们无法解决,只能解决第三个原因了

    思想:把多条语句操作共享数据的代码给包起来,让某个线程在执行的时候,别人不能来执行,而java就给我们提供了这样的方法:同步进制

    同步代码块:

    synchronized(对象){
    	需要同步的代码;
    }
    

    注意:同步可以解决安全问题的根本原因就在那个对象上,该对象如同锁的功能,而多个线程必须用的是用一把锁

    对上面案例的改进:

    //MyRunnable3.java
    
    public class MyRunnable3 implements Runnable{
    
    	private int ticket = 100;
    	//创建锁对象
    	private Object obj = new Object(); 
    	@Override
    	public void run() {
    		while(true){
    			synchronized(obj){
    				if(ticket > 0){
    					try {
    						//在真实场景中,会有网络延迟等问题,所以添加了100ms的延迟
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "票");
    				}else{
    					break;
    				}	
    			}
    			
    		}
    	}
    
    }
    
    //Demo3.java
    
    public class Demo3 {
    	public static void main(String[] args) {
    		MyRunnable2 my = new MyRunnable2();
    		
    		Thread t1 = new Thread(my, "窗口1");
    		Thread t2 = new Thread(my, "窗口2");
    		Thread t3 = new Thread(my, "窗口3");
    		
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    同步的特点

    前提:程序是多个线程的

    同步的好处:同步的出现解决了多线程的安全问题

    同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是非常耗费资源的,无形种会降低程序的运行效率,而且同步比较容易产生死锁

    同步代码块以及把synchronized关键字加到方法上

    1.同步代码块的锁对象是任意对象

    2.同步方法的锁对象是this

    3.静态方法的锁对象是类的字节码文件对象

    6.JDK5的Lock锁

    概述:虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并 没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,jdk5之后就提供了一个新的锁对象Lock

    Lock:是一个接口,可以通过ReentrantLock具体类来实现

    Lock获取锁和释放锁的方法:

    • void lock():获取锁
    • void unlock():释放锁

    使用Lock锁售卖电影票的代码:

    // MyRunnable4.java
    
    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class MyRunnable4 implements Runnable{
    
    	private int ticket = 100;
    	private Lock lock = new ReentrantLock();
    	@Override
    	public void run() {
    		while(true){
    			//获取锁
    			try{
    				lock.lock();
    				if(ticket > 0){
    					try {
    						//在真实场景中,会有网络延迟等问题,所以添加了100ms的延迟
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "正在出售第" + (ticket--) + "票");
    				}else{
    					break;
    				}
    			}finally{
    
    				//释放锁
    				lock.unlock();
    			}	
    		}
    	}
    
    }
    
    //Demo4.java
    
    public class Demo2 {
    	public static void main(String[] args) {
    		MyRunnable2 my = new MyRunnable2();
    		
    		Thread t1 = new Thread(my, "窗口1");
    		Thread t2 = new Thread(my, "窗口2");
    		Thread t3 = new Thread(my, "窗口3");
    		
    		t1.start();
    		t2.start();
    		t3.start();
    	}
    }
    

    死锁问题

    概述:有两个或两个以上的线程在争夺资源的过程中,发生的一种互相等待的现象

    死锁的代码实现:

    //MyLock.java
    
    public class MyLock {
    	public static final Object objA = new Object();
    	public static final Object objB = new Object();
    }
    
    //DieLock
    
    public class DieLock extends Thread{
    	private boolean flag;
    	
    	public DieLock(boolean flag){
    		this.flag = flag;
    	}
    	@Override
    	public void run() {
    		if(flag){
    			synchronized (MyLock.objA) {
    				System.out.println("if objA");
    				synchronized (MyLock.objB) {
    					System.out.println("if objB");
    				}
    			}
    		}else{
    			synchronized (MyLock.objB) {
    				System.out.println("else objB");
    				synchronized (MyLock.objA) {
    					System.out.println("else objA");
    				}
    			}
    		}
    	}
    }
    
    //DieLockDemo
    
    public class DieLockDemo {
    	public static void main(String[] args) {
    		DieLock dl1 = new DieLock(true);
    		DieLock dl2 = new DieLock(false);
    		
    		dl1.start();
    		dl2.start();
    	}
    }
    

    线程间通信问题

    概述:不同种类的线程间针对同一个资源的操作,如生产者消费者模式

    生产者消费者模式的实现代码:

    //Person.java
    
    public class Person {
    	private String name;
    	private int age;
    	boolean flag = false;//标记位,标记是否有人了,flase代表没人
    	public Person(){};
    	public Person(String name, int age){
    		this.name = name;
    		this.age = age;
    	}
    	public String getName() {
    		return name;
    	}
    	public void setName(String name) {
    		this.name = name;
    	}
    	public int getAge() {
    		return age;
    	}
    	public void setAge(int age) {
    		this.age = age;
    	}
    	
    	
    }
    
    //Product.java
    
    public class Product implements Runnable{
    	//这里接受一个Person对象,因为要与消费者共享资源
    	private Person p;
    	private int x = 0;
    	public Product(Person p){
    		this.p = p;
    	}
    	@Override
    	public void run() {
    		while(true){
    			//这里用p对象作为锁,因为生产者和消费者需要使用同一把锁对象来保证线程安全
    			synchronized (p) {
    				//假设有人了
    				if(p.flag){
    					try {
    						p.wait();//有人的情况下,就不生产人了,阻塞住,并立即释放锁,将来醒来时,是从这里醒过来的
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    				//没人的情况下,生产人出来
    				if(x % 2 == 0){
    					p.setName("卢一");
    					p.setAge(18);
    				}else{
    					p.setName("黄伊");
    					p.setAge(17);
    				}
    				x ++;
    				////注意这里的修改标记值和唤醒线程必须放在锁里面,如果没有,将会报 java.lang.IllegalMonitorStateException异常
    				//生产人出来了,就把标记值改为true
    				p.flag = true;
    				//唤醒线程
    				p.notify();
    			}
    			
    		}
    		
    	}
    	
    }
    
    //Customer.java
    
    public class Customer implements Runnable {
    	//这里接受一个Person对象,因为要与生产者共享资源
    	private Person p;
    	public Customer(Person p){
    		this.p = p;
    	}
    	@Override
    	public void run() {
    		while(true){
    			//这里用p对象作为锁,因为生产者和消费者需要使用同一把锁对象来保证线程安全
    			synchronized (p) {
    				if(!p.flag){
    					try {
    						p.wait();//没人的情况下,就阻塞等待,将来醒来从这里醒来
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    				}
    				//有人了就看看这个人是谁(消费掉)
    				System.out.println(p.getName() + "-----" + p.getAge());
    				//注意这里的修改标记值和唤醒线程必须放在锁里面,如果没有,将会报 java.lang.IllegalMonitorStateException异常
    				//看腻了,想看下一个人(消费掉了),修改标记值为没人
    				p.flag = false;
    				//唤醒线程
    				p.notify();
    			}
    			
    		}
    		
    	}	
    }
    
    Test.java
    
    public class Test {
    	public static void main(String[] args) {
    		//创建资源
    		Person p = new Person();
    		
    		Product pro = new Product(p); 
    		Customer c = new Customer(p);
    		//创建两条不同种类线程
    		Thread product = new Thread(pro);
    		Thread customer = new Thread(c);
    		//启动线程
    		product.start();
    		customer.start();
    	}
    }
    
    //结果:
    
    卢一-----18
    黄伊-----17
    卢一-----18
    黄伊-----17
    卢一-----18
    黄伊-----17
    卢一-----18
    黄伊-----17
    卢一-----18
    黄伊-----17
    ...
    

    从以上的例子中,我们可以知道,即使是不同种类的线程,也需要相同的锁对象来保证数据的安全性;而且为了更符合生成者消费者模式(生产一个就消费一个),我们需要引入等待唤醒机制

    等待唤醒的三个方法(是Object类中提供的方法):

    • wait():等待
    • notify():唤醒单个线程
    • notifyAll():唤醒所有线程

    为什么这些方法定义不定义在Thread类中,而是Object类中呢?

    答:这些方法的调用必须通过锁对象调用,而我们刚才使用的锁对象就是任意锁对象,所以,这些方法必须定义在Object类中

    7.线程组

    概述:把多个线程组合在一起,它可以对一批线程进行分类管理,java运行程序直接对线程进行控制

    那么,如果我们想要知道某一个线程所在的线程组,怎么办?

    我们可以通过以下方法得到:

    • public final ThreadGroup getThreadGroup():获取线程所在的线程组对象,在通过getName()方法即可获得线程组名称

    ThreadGroup类的使用:

    • ThreadGroup(String name):构造方法,创建一个线程组对象

      • 如:ThreadGroup tg = new ThreadGroup("线程组名");
    • 通过对线程组对象进行线程控制,达到对这个线程组里的所有线程进行线程控制的效果

      • 如:tg.setDaemon(true):设置了该线程组对象下的所有线程为守护线程

    8.线程池

    概述:程序启动一个新线程成本是比较高的,因为他涉及到要与操作系统进行交互,而使用线程池可以很好的提高性能,尤其是当程序中要创建大量生存期比较短的线程时,更应该考虑使用线程池,线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用,在jdk5之前,我们必须手动实现自己的线程池,从jdk5之后,java内置支持线程池

    使用线程池的步骤:

    1.创建一个线程池对象,控制要创建几个线程对象

    • public static ExecutorService newCacheThreadPool():创建一个具有缓冲功能的线程池
    • public static ExecutorService newFixedThreadPool(int nThreads):创建有多个线程对象的线程池
    • public static ExecutorService:造一个线程池,相当于第二个方法里面的nThread值为1 newSingleThreadExecutor():

    2.这种线程池的可以执行:

    • 实现Runnable接口的对象的线程
    • 实现Callable即可的对象的线程

    3.调用如下方法即可:

    • Feture<?> submit(Runnable task)
    • Future submit(Callable task)
      • 返回值Future是一个接口,它的get方法返回计算结果

    4.线程池开启之后不会自动关闭,如果想关闭,需要执行以下方法

    • public void shutdown():关闭线程池

    示例代码:

    //MyRunnable.java
    
    public class MyRunnable implements Runnable {
    
    	@Override
    	public void run() {
    		for(int i = 0; i < 100; i ++){
    			System.out.println(Thread.currentThread().getName() + ":" + i);
    		}
    	}
    
    }
    
    //ExecutorsDemo.java
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class ExecutorsDemo {
    	public static void main(String[] args) {
    		//创建一个线程对象,控制要创建几个线程对象
    		ExecutorService pool = Executors.newFixedThreadPool(2);
    		//执行Runnable对象代表的线程
    		pool.submit(new MyRunnable());
    		pool.submit(new MyRunnable());
    		//关闭线程池
    		pool.shutdown();
    	}
    }
    

    8.匿名内部类实现多线程程序

    示例代码:

    public class Demo {
    	public static void main(String[] args) {
    		//继承Thread类实现多线程
    		new Thread(){
    			@Override
    			public void run() {
    				for(int i = 0; i < 100; i ++){
    					System.out.println(Thread.currentThread().getName() + ":" + i);
    				}
    			};
    		}.start();
    		
    		//实现Runnable接口实现多线程
    		new Thread(new Runnable(){
    
    			@Override
    			public void run() {
    				for(int i = 0; i < 100; i ++){
    					System.out.println(Thread.currentThread().getName() + ":" + i);
    				}
    			}
    			
    		}){}.start();
    		
    		//走的是Thread
    		new Thread(new Runnable(){
    
    			@Override
    			public void run() {
    				for(int i = 0; i < 100; i ++){
    					System.out.println("走的是Runnable:" + i);
    				}
    			}
    			
    		}){
    			@Override
    			public void run() {
    				for(int i = 0; i < 100; i ++){
    					System.out.println("走的是Thread:" + i);
    				}
    			}
    		}.start();
    		
    	}
    }
    

    9.定时器

    概述:定时器可以让我们在指定的时间做某件事情,还可以重复做某件事,依赖Timer类和TimeTask这两个类实现

    Time:定时

    • public Timer():构造方法
    • public void schedule(TimeTask task, long delay):延迟一段时间后执行task任务,只执行一次
    • public void schedule(TimeTask task, long delay, long period):延迟一段时间后执行task任务,每隔一段时间后再重复执行task任务
    • public void schedule(TimeTask task, Date time):安排在指定的时间执行指定的任务
    • public void schedule(TimeTask task, Date firstTime, long period):安排指定的任务在指定的时间进行,每隔一段时间再重复执行任务
    • public void cancel():取消定时器

    代码示例:

    import java.util.Timer;
    import java.util.TimerTask;
    
    public class TimeDemo {
    	public static void main(String[] args) {
    		Timer t = new Timer();
    		//设置三秒延迟后爆炸,并每隔两秒爆炸一次
    		t.schedule(new MyTask(), 3000, 2000);
    	}
    }
    class MyTask extends TimerTask{
    
    	@Override
    	public void run() {
    		System.out.println("beng,爆炸了");
    	}
    	
    }
    

    10.学完多线程须知

    1.同步有几种方式,分别是什么?

    答:两种。同步代码块和同步方法

    2.启动一个线程是run()还是start()?他们的区别?

    答:run()方法封装了被线程执行的代码,直接调用仅仅是普通方法的调用;start()方法启动线程,并由jvm自动调用run()方法

    3.sleep()方法和wait()方法的区别

    sleep()方法必须指定时间,不释放锁;wait()方法可以不知道时间,也可以指定时间,释放锁

    4.为什么wait(),notify(),notifyAll()等方法定义在Object类中

    答:因为这些方法的调用是依赖于锁对象的,而同步代码块的锁对象是任意锁,而Object代表任意的对象,所以,定义在这里面

  • 相关阅读:
    面向消息的持久通信与面向流的通信
    通信协议
    [1]序章,基本
    深拷贝和浅拷贝
    堆/栈 内存管理相关
    C++的四种cast(显示类型转换)
    智能指针相关
    C++对象模型:单继承,多继承,虚继承
    HTTP/TCP
    [读书笔记] 为什么绝不在构造/析构函数中调用virtual函数
  • 原文地址:https://www.cnblogs.com/luyi001/p/13394824.html
Copyright © 2020-2023  润新知