zoukankan      html  css  js  c++  java
  • Java-21 多线程

    day18

    1.多线程概述

    • 进程:一个程序运行,程序在内存中分配的那片空间。

    • 线程:进程中一个执行单元执行路径

      进程中至少有一个线程,如果进程中有多个线程,就是多线程的程序。

    • 并行与并发:

      并行:某一时间点,有多个程序同时执行,多核CPU运行
      并发:某一时间段,有多个程序同时执行,并不是真正意义的同时执行。 为多线程。
      
    • 并发真的是同时执行吗?

      不是,而是时间间隔很短,造成同时执行感觉。
      
    • 多线程优势?

      提高了用户体验,提高了程序的运行效率,提高CPU使用率。
      

    2.开启线程两种方式

    • 开启线程

      /*
       * 1.继承Thread
       * 2.重写run方法
       * 3.创建子类的对象
       * 4.调用start方法
       * */
      public class ThreadDemo1 {
      	public static void main(String[] args) {
              // 创建子类的对象
      		Demo d1 = new Demo("Jack");
      		Demo d2 = new Demo("Tom");
              // 设置线程名字
      		d1.setName("d1");
      		// 获取d1执行线程名字
      		System.out.println(d1.getName());
      		// 获取当前线程名字
      		System.out.println(Thread.currentThread().getName());
              
              // 调用start方法
      		d1.start();
      		d2.start();
      	}
      }
      // 继承Thread
      class Demo extends Thread{
      	String nickName;
      	public Demo(String nickName) {
      		this.nickName = nickName;
      	}
          // 重写run方法
      	public void run() {
      		for(int i=0;i<30;i++) {
      			System.out.println(nickName + "---" + i);
      		}
      	}
      }
      // 整个运行过程有三个线程运行,主线程开启d1和d2线程
      
    • run方法与start方法区别

      start:开启新的线程,会自动调用run方法在新的线程中执行
      run:没有开启新的线程,只是普通方法
      
    • 开启新线程第二种方式

      声明实现Runnable接口的类,该类然后实现run方法,然后可以分配该类的实例,在创建Thread时做为一个参数来传递并启动,采用这种风格的同一个例子
      
      /*
       * 实现多线程第二种方式:
       * 1.实现Runnable
       * 2.重写run方法
       * 3.创建Runnable子类的对象
       * 4.创建Thread类的对象,把第三步的对象传到构造方法中
       * 5.使用Thread子类对象,调用start方法
       * */
      public class ThreadDemo2 {
      	public static void main(String[] args) {
      		Demo5 d = new Demo5();// 只有Thread或子类线程对象才是线程对象。它只是线程任务度夏宁。
      		Thread th = new Thread(d); // th才是线程对象
      		Thread th2 = new Thread(d);
      		th.start();
      		th2.start();
      	}
      }
      
      class Demo5 implements Runnable{
      	public void run() {
      		for (int i=0;i<20;i++) {
      			System.out.println(Thread.currentThread().getName() + "---" + i);
      		}
      	}
      }
      
    • 两种实现方式,区别是?

      第一种方式有局限性的,因为Java是单继承,如果一个类已经有一个继承,它就不能再继承Thread类,就无法实现多线程。而第二种实现通过接口方式实现更合理,并且第二种方式更加符合面向对象特点:高内聚低耦合,把线程对象和线程任务分离开了。
      

    3.线程中方法

    1.sleep方法使用

    public Static void sleep(long millis)
    静态方法 进入阻塞状态,时间结束后进入可执行
    
    • 示例:
    Thread.sleep(3000);
    

    sleep方法让谁阻塞,取决于他在哪个线程中。

    2.join方法使用

    public final void join()
    被谁调用,让哪个线程先执行,执行完毕后,再执行所在线程
    
    • 示例
    
    public class JoinDemo1 {
    	public static void main(String[] args) {
    		Sum s = new Sum();
    		s.start();
    		// join:用谁调用,就让那个线程先执行,执行完毕后,再执行他所在线程(将他所在线程阻塞)。
    		try {
    			s.join();
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		
    		System.out.println(Sum.sum);
    	}
    }
    
    class Sum extends Thread{
    	static int sum = 0;
    	public void run() {
    		for(int i=0;i<=1000;i++) {
    			sum += i;
    		}
    	}
    }
    

    3.yield方法

    public static void yield()
    让其他线程先执行,不一定生效,因为让谁执行是CPU决定的
    

    4.stop方法

    停止一个线程
    

    5.interrupt 方法

    打断线程的阻塞状态,进入可执行状态,会抛出异常
    
    • 示例:打断子线程阻塞
    public class interruptDemo {
    	public static void main(String[] args) {
    		Demo3 d = new Demo3();
    		d.start();
    		// 将d执行线程阻塞状态打断。
    		d.interrupt();
    		System.out.println("over");
    	}
    }
    
    
    class Demo3 extends Thread{
    	public void run() {
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		for (int i=0;i<10;i++) {
    			System.out.println(i);
    		}
    	}
    }
    
    • 打断主线程阻塞
    public class InterruptDemo2 {
    	public static void main(String[] args) {
    		// 主线程传给Demo4
    		Demo4 d = new Demo4(Thread.currentThread());
    		d.start();
    		try {
    			Thread.sleep(2000);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}	
    		System.out.println("over");
    	}
    }
    
    class Demo4 extends Thread{
    	Thread th;
    	public Demo4(Thread th) {
    		this.th = th;
    	}
    	// 用于打断主线程
    	public void run() {
    		th.interrupt();
    	}
    }
    
    • 练习:创建两个线程,一个线程负责打印大写字母表,一个线程负责打印小写字母表
    public class Threadlianxi1 {
    	public static void main(String[] args) {
    		Thread th1 = new Thread(new Task1());
    		Thread th2 = new Thread(new Task2());
    		th1.start();
    		th2.start();
    	}
    }
    
    
    class Task1 implements Runnable{
    	public void run() {
    		for(char i='a';i<='z';i++) {
    			System.out.println(i);
    		}
    	}
    }
    class Task2 implements Runnable{
    	public void run() {
    		for(char i='A';i<='Z';i++) {
    			System.out.println(i);
    		}
    	}
    }
    

    4.线程生命的周期

    主线程执行时候在栈空间,开辟空间给子线程,而他们的之间栈是独立的,但他们堆空间数据是共享的
    

    5.线程安全的问题

    • 买票示例:
    public class SellTicketsDemo {
    	public static void main(String[] args) {
    		Tickets t = new Tickets();
    		Thread th1 = new Thread(t);
    		Thread th2 = new Thread(t);
    		Thread th3 = new Thread(t);
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    
    class Tickets implements Runnable{
    	static int tickets = 100;
    	public void run() {
    		while (true) {
    			if (tickets> 0) {
    				try {
    					Thread.sleep(30);
    				} catch (InterruptedException e) {
    					// TODO Auto-generated catch block
    					e.printStackTrace();
    				}
    				System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    			} else {
    				break;
    			}
    		}
    	}
    }
    
    
    • 在执行代码时候,会发现多个线程会卖出同一张票。这样会产生线程安全问题。

    • 线程安全产生原因:1.具备多线程。2.操作共享数据。3.操作共享数据的代码有多条。

    • 通过加锁:让每一时刻只能有一个线程操作数据。方式有三种

    1.同步代码块

    synchronized(锁对象){
    	容易产生线程安全问题的代码
    }
    
    // 锁对象:可以是任意对象,但是必须保证多个线程使用是同一个对象。
    
    • 示例:
      • 关键点:锁对象选择
    public class SellTicketsDemo {
    	public static void main(String[] args) {
    		Tickets t = new Tickets();
    		Thread th1 = new Thread(t);
    		Thread th2 = new Thread(t);
    		Thread th3 = new Thread(t);
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    
    class Tickets implements Runnable{
    	static int tickets = 100;
        // 如果o方法run方法里,则无法实现线程安全,原因是执行run方法,实现3个o对象,对于这三个线程来说o对象不是共有的同一个对象。
    	Object o = new Object();
    	public void run() {
    		while (true) {
    			synchronized (o) {// o所在的类被new几次
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    			}
    		}
    	}
    }
    
    • 锁对象选取错误示例:
      • Tickets2被new了3次导致,没有锁住。
    public class SellTicketsDemo2 {
    	public static void main(String[] args) {
    		Tickets2 th1 = new Tickets2();
    		Tickets2 th2 = new Tickets2();
    		Tickets2 th3 = new Tickets2();
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    class Tickets2 extends Thread{
    	static int tickets = 100;
    	Object o = new Object();
    	public void run() {
    		while (true) {
    			synchronized (o) {
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    			}
    		}
    	}
    }
    
    • 方式1:解决方式通过:构造方法解决:
    public class SellTicketsDemo2 {
    	public static void main(String[] args) {
    		Object o = new Object();
    		Tickets2 th1 = new Tickets2(o);
    		Tickets2 th2 = new Tickets2(o);
    		Tickets2 th3 = new Tickets2(o);
    		th1.start();
    		th2.start();
    		th3.start();
    	}
    }
    
    class Tickets2 extends Thread{
    	static int tickets = 100;
    	Object o;
        // 构造方法
    	public Tickets2(Object o) {
    		this.o=o;
    	}
    
    	public void run() {
    		while (true) {
    			synchronized (o) {
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    			}
    		}
    	}
    }
    
    • 方式2:使用静态方式:
    Static Object o = new Object();
    
    • 方式3:使用字符串的常量
    synchronized ("abc")
    
    • 方式4:使用Class对象
    synchronized (SellTicketsDemo2.class)
    // 在jvm中,每一个类的Class总共只有一个
    

    2.同步方法

    • 把synchronized放到方法的修饰符中,锁的是整个方法。
    public class SellTicketsDemo3 {
    	public static void main(String[] args) {
    		Tickets3 th1 = new Tickets3();
    		new Thread(th1).start();
    		new Thread(th1).start();
    		new Thread(th1).start();
    	}
    }
    
    class Tickets3 implements Runnable{
    	static int tickets = 100;
    	// 默认锁对象:this
    	public synchronized void run() {
    		while (true) {
    				if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    		}
    	}
    }
    

    上述代码虽然解决了线程安全问题,但是编程了单线程程序,原因synchronized锁的范围太大,第一个进来线程,执行完整个while循环。导致在使用同步方法时候也需要注意这个问题。

    • 但可以通过同步方法解决懒汉式单例模式:
    package com.xjk;
    // 懒汉式:存在问题,存在线程安全问题
    public class Singleton2 {
    	private static Singleton2 s;
    	// 构造方法私有化,为了不让别人随便new
    	private Singleton2() {
    	}
    	// 通过synchronized 解决线程安全问题
    	public synchronized static Singleton2 getInstance() {
    		if (s == null) {
    			s = new Singleton2();
    		} 
    		return s;
    	}
    }
    // 当启100个线程,当一个线程执行到s=new Singleton2();此时刚要new Singleton2,cpu切到第二个线程,因为s此时还是等于null,第二个线程也执行new Singleton2() 导出单例模式创建多个对象。此时通过同步方法添加synchronized可以有效解决此问题。
    
    • 同样StringBuffer是线程安全的内部有synchronized,效率相对StringBuilder低,StringBuilder是线程不安全的。

    3.Lock

    import java.util.concurrent.locks.Lock;
    import java.util.concurrent.locks.ReentrantLock;
    
    public class SellTicketsDemo4 {
    	public static void main(String[] args) {
    		Tickets4 th1 = new Tickets4();
    		new Thread(th1).start();
    		new Thread(th1).start();
    		new Thread(th1).start();
    	}
    }
    
    class Tickets4 implements Runnable{
    	static int tickets = 100;
    	// 也要保证该锁对象对于多个线程是同一个
    	Lock lock = new ReentrantLock();
    	public synchronized void run() {
    		while (true) {
    				lock.lock();// 加锁
    				try {
    					if (tickets> 0) {
    					try {
    						Thread.sleep(30);
    					} catch (InterruptedException e) {
    						e.printStackTrace();
    					}
    					System.out.println(Thread.currentThread().getName() + "====" + "正在出售第" + tickets--  +"张票!");
    				} else {
    					break;
    				}
    				} finally {
    					lock.unlock();//解锁
    				}
    		}
    	}
    }
    
    • 释放锁的代码放到finally代码块中,否则容易造成程序阻塞。

    6.死锁

    • 是指两个或两个以上的线程在执行的过程中,因争夺资源产生一种互相等待现象。

    7.线程池用法

    • 线程池可以减少创建和销毁线程的次数,每个工作线程都可以被重复利用,可执行多个任务。

    • 可以根据系统的承受能力,调整线程池中工作线程数目,放置因为消耗过多的内存,而把服务器累死(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。

    • 线程池的创建:

      public static ExecutorService newCachedThreadPool()
      	创建一个具有缓存功能的线程池
      public static ExecutorService new FixedThreadPool(int nThreads)
      	创建一个可重用的,具有固定线程数的线程池
      public static ExecutorService newSingleThreadExecutor()
      	创建一个只有单线程的线程池,相当于上个方法的参数是1
      
    • 示例:

      import java.util.concurrent.ExecutorService;
      import java.util.concurrent.Executors;
      
      public class ThreadPoolDemo {
      	public static void main(String[] args) {
              // 1
      		ExecutorService pool = Executors.newCachedThreadPool();
      		pool.execute(new Runnable() {
      			public void run() {
      				System.out.println("hello world");
      			}
      		});
      		// 关闭线程池
      		pool.shutdown();
              
              // 2
              pool = Executors.newFixedThreadPool(20);
              
      	}
      }
      // 缓存线程池,如果没有任务会等待60s就关闭
      

    8.wait/Notify/NotifyAll

    • wait,notify,notifyAll这三个方法都是Object中方法,并且这三个方法必须在同步方法或同步代码块中使用。
    wait: 让线程进入等待状态,进入等待状态线程会释放锁对象(也就是允许其他线程进入同步代码块),直到使用相同的锁对象调用notify/notifyAll方法才会结束阻塞状态。
    notify:唤醒,会从等待池中唤醒一个处于等待状态的线程,使其进入可执行状态。
    notifyAll:唤醒全部,会将等待池中所有处于等待状态的线程唤醒,使其进入可执行状态。
    

    ​ notify或notifyAll以后,被唤醒的线程并不是立马执行,需要等到notify,notifyAll所在代码块执行完毕后才会执行。因为只有同步代码块执行完毕后,才会释放锁对象,其他线程才可以进来。

    ​ wait方法会释放锁对象,也就是一个线程使用wait进入等待状态后,允许其他线程进入同步代码块。而sleep方法不会释放锁对象,到时间后自己会醒来。

    • 示例:
    public class WaitNotifyDemo {
    	public static void main(String[] args) {
    		Object o = new Object();
    		// 当使用
    		new Thread(new Demos1(o)).start();
    		new Thread(new Demos1(o)).start();
    		try {
    			// sleep 作用是保证上面2个线程都执行到wait,然后第三个线程可以使用notifyAll解除阻塞
    			Thread.sleep(30);
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    		new Thread(new Demos2(o)).start();
    	}
    }
    
    class Demos1 implements Runnable{
    	Object o;
    	public Demos1(Object o) {
    		this.o = o;
    	}
    	@Override
    	public void run() {
    		synchronized(o) {
    			System.out.println(Thread.currentThread().getName() + "Wait ,,,start...");
    			try {
    				// 阻塞线程
    				o.wait();
    			} catch (InterruptedException e) {
    				// TODO Auto-generated catch block
    				e.printStackTrace();
    			}
    			System.out.println("等待结束...");
    		}
    	}
    }
    
    class Demos2 implements Runnable{
    	Object o;
    	public Demos2(Object o) {
    		this.o = o;
    	}
    	@Override
    	public void run() {
    		synchronized(o) {
    			System.out.println(Thread.currentThread().getName() + "notify ,,,start...");
    			// 解除阻塞
                 // o.notify();
    			o.notifyAll();
    			System.out.println(Thread.currentThread().getName() + "notify ,,,end...");
    		}
    	}
    }
    /*
    Thread-0Wait ,,,start...
    Thread-1Wait ,,,start...
    Thread-2notify ,,,start...
    Thread-2notify ,,,end...
    等待结束...
    等待结束...
     * */
    
    

    ​ 注意:

    ​ 1.wait.notify.notifyAll 一定要在同步代码块中执行,使用锁对象调用。

    ​ 2.要想能唤醒wait,必须使用同一个锁对象调用notify/notifyAll

    ​ 3.wait方法会释放锁对象,进入了等待状态以后,允许其他线程进入同步代码块执行。

    ​ 4.notify方法唤醒了以后,wait不是立马执行,等待notify中代码执行完毕。

    ​ 5.而notify方法只能唤醒一个(随机唤醒),而notifyAll全部都能唤醒

    • 为什么wait,notify,notfiyAll 放到Object类中?

      因为他们使用锁对象调用,锁对象可以是任意对象,任意对象都有的方法定义在Object
      

    9.定时器

    • 方法

      public void schedule(TimerTask task, long delay) 延迟多少毫秒后执行定时任务
      public void schedule(TimerTask task, Date date) 指定时间执行定时任务
      public void schedule(TimerTask task, long delay, long period) 延迟执行,指定间隔后循环执行
      public void cancel() 取消定时任务
      
    • 示例1:

      import java.util.Timer;
      import java.util.TimerTask;
      
      public class TimerDemo {
      	public static void main(String[] args) {
      		Timer timer = new Timer();
      		// 5000毫秒执行一次任务,同一个定时任务只能执行一次
      		// 它是多线程启动的
      		timer.schedule(new TimerTask() {
      			public void run() {
      				System.out.println("你好");
      				// 取消定时任务,一般放到定时任务中
      				timer.cancel();
      			}
      		}, 5000);
      	}
      }
      
      
    • 示例2:日期定时

      import java.text.ParseException;
      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.Timer;
      import java.util.TimerTask;
      
      public class TimerDemo {
      	public static void main(String[] args) throws ParseException {
      		Timer timer = new Timer();
              // 如果时间已经过期,它会立刻运行
      		Date d = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse("2020-10-10 10:10:10");
      		timer.schedule(new TimerTask() {
      			public void run() {
      				System.out.println("起床了!");
      			}
      		}, d);
      	}
      }
      
    • 示例3:

      import java.text.SimpleDateFormat;
      import java.util.Date;
      import java.util.Timer;
      import java.util.TimerTask;
      
      public class TimerDemo2 {
      	public static void main(String[] args) {
      		// 3秒后执行,每隔1秒执行一次
      		new Timer().schedule(new TimerTask() {
      			public void run() {
      				System.out.println(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
      			}
      		},3000,1000);
      	}
      }
      
      
  • 相关阅读:
    axios 进行类库封装
    vue的中vuex为何需要mutation更新状态,vue-router的路由的理解
    发布订阅者模式、观察者模式总结
    es 模块的基础知识,深度了解
    绑定事件的模型
    rem+media+jquery布局结局方案
    VDOM总结
    react-redux
    发布网站配置文件和SSL
    css3d旋转
  • 原文地址:https://www.cnblogs.com/xujunkai/p/13888474.html
Copyright © 2011-2022 走看看