• 多线程java (附代码)


    多线程

    在这里插入图片描述
    在这里插入图片描述

    单核CPU,以单个线程完成多个任务会比多个线程完成的要快,因为单核CPU要是有多个线程的话CPU要切换

    在这里插入图片描述

    何时需要多线程

    • 程序需要同时执行两个或多个任务
    • 程序需要实现一些需要等待的人物,如用户输入,文件读写操作、搜索等
    • 需要一些后台运行的程序

    多线程的创建

    1.创建一个继承于Thread类的子类
    2.重写Thread的run方法,将此线程执行的操作声明在run方法中
    3.创建Thread类的子类的对象
    4.通过此对象调用start

    多线程是存在交互性的

    package ThreadT;
    //1. 创建Thread的子类
    class MyThread extends Thread{
    	// 2.重写run
    	public void run(){
    		for(int i = 0;i < 100; i++){
    			if(i % 2 == 0){
    				System.out.println(i);
    			}
    		}
    	}
    }
    public class ThreadTest {
    	public static void main(String[] args) {
    		// 3. 创建Thread子类的对象
    		MyThread t1 = new MyThread();
    	
    		// 4.通过此对象调用start
    		t1.start();
    		for(int i = 0;i < 100; i++){
    			if(i % 2 == 0){
    				System.out.println(i + "+++++++++++++++");
    			}
    		}
    	
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    i 和 i********** 会交互

    start两个作用,1.启用当前线程。2.使用当前线程的run

    不能让已经start的线程去start

    线程的常用方法

    1.start() 启动当前线程,调用当前的run
    2.run() 通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    3. currentThread 静态方法,返回执行当前代码的线程
    4. getName:获取当前线程的名字
    5. setName: 设置当前线程的名字
    6. yield : 释放当前cpu的执行权(当前下一刻也能抢过来)
    7. join:在线程A中调用线程B的join方法,线程A进入阻塞状态,直到线程B完全执行完以后,线程A才结束阻塞
    8. stop 强行结束线程周期,不建议使用
    9. sleep(long millitime) 休息一会 ,单位是毫秒,在指定的时间之内,当前线程是堵塞状态,都会报异常 try - catch 一下 倒计时啥的用一下
    10.isAlive() : 判断当前线程是否还存活

    package ThreadT;
    
    class HelloThread extends Thread{
    	public void run(){
    		for(int i = 0;i < 100;i++){
    			if(i % 2 == 0){
    			
    //				try {
    //					sleep(1000);
    //				} catch (InterruptedException e) {
    //					e.printStackTrace();
    //				}
    				System.out.println(Thread.currentThread().getName() + ":" + i);
    			}
    		
    			if(i % 20 == 0){
    				this.yield(); // 释放当前CPU执行权
    			}
    		}	
    	}
    
    	public HelloThread(String name){
    		super(name);  // 也能这样给线程取名字
    	}
    }
    public class ThreadMethodTest {
    	public static void main(String[] args) {
    		HelloThread h1 = new HelloThread("线程一");
    		// h1.setName("线程一");
    		h1.start();
    	
    		// 给主线程做个命名
    		Thread.currentThread().setName("主线程");
    		for(int i = 0;i < 100;i++){
    			if(i % 2 == 0){
    				System.out.println(Thread.currentThread().getName() + ":" + i);
    			}
    		
    			if (i == 20){
    				try {
    					h1.join(); // h1过来执行完
    				} catch (InterruptedException e) {
    					e.printStackTrace();
    				}
    			}
    		}
    		System.out.println(h1.isAlive());
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49

    线程的优先值设置

    高优先级的线程抢占CPU

    线程的优先级等级

    • MAX_PRIORITY : 10
    • MIN_PRIORITY : 1
    • NORM_PRIORITY : 5 默认的优先级

    getPriotity() 可以返回线程优先级
    setPriority(int newPriority) 改变线程的优先级

    说明:

    线程的创建时回继承父线程的优先级
    低优先级只是获得调度的概率低,并非一定是在高优先级线程之后才被调用

    创建多线程有两种方式

    // 1.创建一个实现了Runnable接口的类
    // 2.实现类去实现Runnable的抽象方法 run()
    // 3.创建实现类的对象
    // 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    // 5.通过Thread类的对象调用start()

    package ThreadT;
    // 创建线程的方式2 实现Runnable 接口
    
    //1.创建一个实现了Runnable接口的类
    class MThread implements Runnable{
    	// 2.实现类去实现Runnable的抽象方法 run()
    	@Override
    	public void run() {
    		for (int i = 0;i < 100;i++){
    			if (i % 2 == 0){
    				System.out.println(Thread.currentThread().getName() + ":" + i);
    			}
    		}
    	}
    
    
    }
    public class ThreadTest1 {
    	public static void main(String[] args) {
    		// 3.创建实现类的对象
    		MThread mThread = new MThread();
    		// 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
    		Thread t1 = new Thread(mThread);
    		t1.start();
    	
    	
    		// 再启动一个线程,遍历100以内的偶数,可以不用给钱new MThread
    		Thread t2 = new Thread(mThread);
    		t2.start(); 
    	}
    
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32

    线程的生命周期

    在这里插入图片描述
    在这里插入图片描述

    线程的安全问题

    在这里插入图片描述

    问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也操作共享数据
    解决:当一个线程A在操作共享数据时,其他线程都不能参与进来,直到线程A完成操作,其他线程才可以开始操作,这样即使线程A出现了阻塞,也不能被改变

    Java中通过同步机制,来解决线程的安全问题

    方式一 : 同步代码块

    synchranized(同步监视器){
    	// 需要被同步的代码
    }
    
    • 1
    • 2
    • 3
    说明:
    • 操作共享数据的代码,即位需要被同步的代码 --> 不能包含代码多了,也不能包含代码少了。
    • 共享数据:多个线程共同操作的变量
    • 同步监视器:俗称,锁。任何一个类的对象,都可以来充当锁。要求多个线程必须要用同一把锁。在创建类的时候就 Object obj = new Object(); 就造一个对象然后用Thread 去弄就不用在Object前加static 如果要造多个类的对象,就得 static Object。(接口的话一般就造一个继承Thread的对象)

    类也是个对象,且类只会加载一次

    同步的方式:安全了,但是变慢了。操作同步代码时,只能有一个线程参与,其他线程等待,相当于是一个单线程的过程,效率低

    在继承Thread类创建多线程的方式中,慎用this来充当同步监视器,建议使用当前类来充当同步监视器

    方式二:同步方法
    如果操作共享数据的代码,正好完整的声明在一个方法中,我们不妨将此方法声明同步的。

    public synchronized void ...()  // 同步监视器:this
    
    • 1

    用接口创建这样是没问题,但是如果继承的话,

    public static synchronized void show() // 同步监视器:当前类
    
    • 1

    1.关于同步方法的总结:同步方法仍然需要涉及到同步监视器,只是不需要我们显式的声明
    2.非静态的同步方法,同步监视器是:this
    静态的同步方法:同步监视器是当前类本身

    使用同步机制将单例模式中的懒汉式改为线程安全的(之前提过)

    package ThreadT;
    
    // 使用同步机制将单例模式中的懒汉式改为线程安全的
    public class BankTest {
    
    }
    
    class Bank{
    	private Bank(){}
    
    	private static Bank instance = null;
    	// 若有多个线程可能会一起进去,有线程安全问题
    	// 方式一:效率稍差
    	public static Bank getInstance(){
    //		synchronized (Bank.class){
    //		if (instance == null){
    //			instance = new Bank();
    //			
    //		}
    //		return instance;
    //		}
    //	}
    	// 方式二:效率高,后面的线程没必要进行同步代码块了
    		if(instance == null){
    			synchronized (Bank.class){
    				if (instance == null){
    					instance = new Bank();
    		
    				}
    			}
    		}
    		return instance;
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    死锁

    不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己所需要的同步资源,就形成了线程的死锁
    出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处理阻塞状态,无法继续

    解决方法

    • 专门的算法、原则
    • 尽量减少同步资源的定义
    • 尽量避免嵌套同步

    我们使用同步时,要避免死锁

    package ThreadT;
    
    public class ThreadsisuoTest {
    	public static void main(String[] args) {
    		StringBuffer s1 = new StringBuffer();
    		StringBuffer s2 = new StringBuffer();
    	
    		new Thread(){
    			public void run(){
    				synchronized(s1){
    					s1.append("a");
    					s2.append("1");
    				
    					try {
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					synchronized(s2){
    						s1.append("b");
    						s2.append("2");
    					
    						System.out.println(s1);
    						System.out.println(s2);
    					}
    				}
    			}
    		}.start();
    	
    		new Thread(new Runnable(){
    			public void run(){
    				synchronized(s2){
    					s1.append("c");
    					s2.append("3");
    				
    					try {
    						Thread.sleep(100);
    					} catch (InterruptedException e) {
    						// TODO Auto-generated catch block
    						e.printStackTrace();
    					}
    					synchronized(s1){
    						s1.append("d");
    						s2.append("4");
    					
    						System.out.println(s1);
    						System.out.println(s2);
    					}
    				}	
    			}
    		}).start();
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54

    第一个线程进入的时候拿着s1的钥匙,sleep了一下,第二个线程就有更大的概率会启动(第一个线程不sleep第二个线程也可能会启动,就是让更大的可能让两个线程都停留在拿了第一个锁的位置),这时候第一个线程手上的钥匙是s1 ,第二个线程手上的钥匙是s2,但第一个线程想要出去又需要钥匙s2,第二个线程也需要钥匙s1,他两就会僵持不动

    Lock(锁)

    在这里插入图片描述

    Lock 和 synchronized 的异同

    • 相同点:都是用来解除线程安全问题

    • 不同点:synchronized 机制在执行完相应的同步代码后,自动的释放同步监视器
      Lock 需要手动的去启动同步(Lock()),同时结束同步也需要手动的实现(unlock())

      package ThreadT;

      import java.util.concurrent.locks.ReentrantLock;

      // Lock锁 – JKD5.0新增
      public class LockTest {
      public static void main(String[] args) {
      Windows w = new Windows();

        	Thread t1 = new Thread(w);
        	Thread t2 = new Thread(w);
        	Thread t3 = new Thread(w);
        
        	t1.setName("窗口1");
        	t2.setName("窗口2");
        	t3.setName("窗口3");
        
        
        	t1.start();
        	t2.start();
        	t3.start();
        }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13

      }

      class Windows implements Runnable{
      private int ticket = 100;
      // 1.实例化一个Lock
      private ReentrantLock lock = new ReentrantLock();

        @Override
        public void run() {
        	while(true){
        		try{
        			// 2.调用lock
        			lock.lock();
        	
        		if(ticket > 0){
        			System.out.println(Thread.currentThread().getName() + ":, 售票,票号为 " + ticket);
        			ticket --;
        		}else{
        			break;
        		}
        		}finally{
        			//3.调用解锁方法
        			lock.unlock();
        		}
        	
        		}
        	
        	}
        
        }
      
      • 1
      • 2
      • 3
      • 4
      • 5
      • 6
      • 7
      • 8
      • 9
      • 10
      • 11
      • 12
      • 13
      • 14
      • 15
      • 16
      • 17
      • 18
      • 19
      • 20
      • 21
      • 22
      • 23

    建议解决线程安全问题时,最好用Lock 再同步代码块,其实都差不多

    sleep 和 wait 方法的异同

    相同点:一旦执行到sleep和wait,都可以使得当前的进程进入阻塞状态
    不同点:1.两个方法声明的位置不同,Thread 类中声明sleep,Object类中声明wait
    2.调用的要求不同:sleep()可以在任何需要的常见下调用,wait必须在使用同步代码块或同步方法中调用
    3.关于是否释放同步监视器:如果两个方法都使用在同步方法块或者同步方法中,sleep方法不会释放锁,wait()会释放同步监视器(锁)

    JDK5.0新增的创建线程的方式

    新增方式一:实现Callable接口

    与使用Runnable相比,Callable功能更强大

    • 相比run方法,可以有返回值
    • 方法可以抛出异常
    • 支持泛型的返回值
    • 需要借助FutureTask类,比如获取返回结果

    在这里插入图片描述

    package ThreadT;
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    // 实现Callable接口 --- JDK 5.0新增
    
    // 1.创建一个实现Callable接口的类
    class NumThread implements Callable{
    
    	@Override
    	// 2.实现call方法,将此线程需要实现的操作声明在此方法中,并且可以有返回值
    	public Object call() throws Exception {
    		int sum = 0;
    		for (int i = 1;i <= 100;i++){
    			if(i % 2 == 0){
    				System.out.println(i);
    				sum += i;
    			}
    		}
    		return sum;
    	}
    
    }
    public class ThreadNew {
    	public static void main(String[] args) {
    		// 3.创建Callable接口实现类的对象
    		NumThread numThread = new NumThread();
    		// 4.将Callable实现类的对象作为参数传递到FutureTask构造器中,创建FutureTask对象
    		FutureTask futureTask = new FutureTask(numThread);
    		// 5.将FutureTask的对象作为参数传递到Thread的构造器中,创建Thread对象,最后start
    		new Thread(futureTask).start();
    		try {
    			// 6.需要的话得到返回值
    			// get方法返回值即为futureTask构造器参数Callable重写的call方法的返回值
    			Object sum = futureTask.get();
    			System.out.println("总和为" + sum);
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		} catch (ExecutionException e) {
    			e.printStackTrace();
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45

    如何理解Callable 比 Runnable 好

    • call() 有返回值
    • call() 可以抛出异常,被外面的操作捕获,获得异常的信息
    • Callable 支持泛型

    线程池

    在这里插入图片描述
    在这里插入图片描述

     package ThreadT;
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    public class xianchengchi {
    	public static void main(String[] args) {
    		// 1.提供指定线程数量的线程池
    		ExecutorService service = Executors.newFixedThreadPool(10);
    		// 2.执行指定的线程操作,需要提供Runnable
    		service.execute(new NumberThread()); //适合Runnable
    		service.execute(new NumberThread1()); //适合Runnable
    	
    		// 3.关闭线程池
    		service.shutdown();
    		// service.submit(task); //适合Callable
    	
    	}
    
    }
    
    
    class NumberThread implements Runnable{
    	public void run(){
    		for (int i = 0;i < 100;i++){
    			if(i % 2 == 0){
    				System.out.println(Thread.currentThread().getName() + ":" + i);
    			}
    		}
    	}
    }
    
    class NumberThread1 implements Runnable{
    	public void run(){
    		for (int i = 0;i < 100;i++){
    			if(i % 2 != 0){
    				System.out.println(Thread.currentThread().getName() + ":" + i);
    			}
    		}
    	}
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
  • 相关阅读:
    企业电子招投标采购系统——功能模块&功能描述+数字化采购管理 采购招投标
    鸿蒙自定义DrawerLayout侧滑菜单实现原理
    SpringMVC之JSON返回&异常处理机制
    文档信息抽取技术:从非结构化文本到结构化信息的旅程
    6、以template进行编程
    Java8新特性 Lambda表达式
    UNIAPP_ReferenceError: TextEncoder is not defined 解决
    SAP ABAP——数据类型(四)【TYPE系列关键字】
    Greenplum 对比 Hadoop
    HTTP介绍:一文了解什么是HTTP
  • 原文地址:https://blog.csdn.net/abc1234564546/article/details/127798396