精华内容
下载资源
问答
  • Java中开启多线程的三种方法
    千次阅读
    2020-06-29 11:50:22

    Java开启多线程有三种方式:

    • 继承Thread类,重写run方法
    • 实现Runnable接口,重写run方法
    • 实现Callable接口,重写call方法(juc并发包中,用的不多)

     

    方式一:直接继承Thread类,重写run()方法

    • 执行线程必须调用start()方法,从而加入到调度器中
    • 不一定立即执行,系统安排调度分配执行(等待分配时间片)
    • 直接调用run()方法不是开启线程,是普通方法调用
    • 使用继承Thread类的方法来创建线程类时,多个线程之间无法共享线程类的实例变量(成员变量)
    /**
     * 创建多线程方法一:
     * 1、创建:继承Thread + 重写run()方法
     * 2、启动:创建子类对象 + start()
     */
    public class StartThread extends Thread{
        //多线程入口
        @Override
        public void run() {
            for(int i = 0; i < 50; i++){
                System.out.println("一边听音乐");
            }
        }
    
        public static void main(String[] args) {
            //创建子对象
            StartThread st = new StartThread();
            //启动
            st.start();  //开启多线程,不保证立即调用,调度交由CPU
            //st.run();  //调用普通方法,正常的逻辑顺序流
            for(int i = 0; i < 50; i++){
                System.out.println("一边coding");
            }
        }
    }
    

    打开start()原码,会发现start()会调用本地方法start0(),再本地方法start0()中会调用run()方法。

     

    方式二:实现Runnable接口,重写run()方法

    • java中存在单继承的局限性,因此建议多采用实现,少用继承,也就是多用Runable接口
    • 方便共享资源,一份资源多个代理
    • Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。
    /**
     * 创建多线程方法二:
     * 1、创建:实现Runnable中的run()方法
     * 2、启动:创建实现类对象 + 代理类Thread对象 + start()
     */
    public class StartRun implements Runnable{
        //多线程入口
        @Override
        public void run() {
            for(int i = 0; i < 50; i++){
                System.out.println("一边听音乐");
            }
        }
    
        public static void main(String[] args) {
            //创建实现类对象
            StartRun sr = new StartRun();
            //创建代理类对象
            Thread t = new Thread(sr);
            //启动
            t.start();  //开启多线程,不保证立即调用,调度交由CPU
            for(int i = 0; i < 50; i++){
                System.out.println("一边coding");
            }
        }
    }

     由于sr和t只用了一次,所以建议采用匿名类

    public class StartRun implements Runnable{
        //多线程入口
        @Override
        public void run() {
            for(int i = 0; i < 50; i++){
                System.out.println("一边听音乐");
            }
        }
    
        public static void main(String[] args) {
            new Thread(new StartRun()).start();
            for(int i = 0; i < 50; i++){
                System.out.println("一边coding");
            }
        }
    }

    针对方法二中,一份资源,多份代理,实现资源共享,这里以一个简单地实例说明

    • ctrl + alt + t 快速加上try-catch代码
    /**
     * 资源共享,并发编程(需要保证线程安全,后续讲解)
     * ctrl + alt + p 快速加上try-catch代码
     */
    public class Web12306 implements Runnable {
    
        private int ticketNumbs = 10;
        @Override
        public void run() {
    
            while(true) {
                if (ticketNumbs <= 0) {
                    break;
                }
                try {
                    Thread.sleep(200);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "--->" + ticketNumbs--);
            }
        }
    
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName());
            Web12306 web = new Web12306();
            new Thread(web,"码畜").start();
            new Thread(web,"码农").start();
            new Thread(web,"码神").start();
        }
    
    }

     

    方式三:使用Callable和Future创建线程

    • juc并发包下实现Callable接口,重写call()方法
    • 据说工作几年之后才会接触,可以了解下用于面试
    import java.util.concurrent.*;
    
    /**
     * 创建线程的方式三
     * 创建:实现Callable中的call()方法
     * 启动:创建执行服务  ExecutorService service = Executors.newFixedThreadPool(1);
     *      提交结果     Future<T> result = service.submit(tc);
     *      获取结果     T r = result.get();
     *      关闭服务     service.shutdownNow();
     */
    public class ThreadCall implements Callable<Boolean> {
        @Override
        public Boolean call() throws Exception {
            for(int i = 0; i < 20; i++){
                System.out.println(Thread.currentThread().getName() + "-->" + "听音乐");
            }
            return true;
        }
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            ThreadCall tc = new ThreadCall();
            ThreadCall tc2 = new ThreadCall();
            //创建执行服务
            ExecutorService service = Executors.newFixedThreadPool(2);
            //提交结果
            Future<Boolean> result = service.submit(tc);
            Future<Boolean> result2 = service.submit(tc2);
            //获取结果
            boolean r = result.get();  //boolean根据泛型指定的类型变
            boolean r2 = result2.get();
            //关闭服务
            service.shutdownNow();
        }
    
    }

     

    补充:阅读疯狂java讲义,总结一下方式三的一些知识点

    前面方式一和方式二都是将run()方法作为线程执行体,那么可否将任一方法包装成线程执行体,java当前不行(C#可以),但是为了更为方便的进行多线程编程,从Java 5开始提供Callable接口,可以说是Runnable接口的增强版。

    主要特点:

    • call()方法可以有返回值
    • call()方法可以声明抛出异常

    疯狂java讲义中使用Callable接口的方式与上述方式不太一样,此书中的方式基本与方式二一致,也是使用Thread代理,只不过在Runnable中直接代理Runnable对象,这里需要使用FutureTask类包装一下,但实质一样。与上述写法明显不同,我想可能是存在不用的使用方式,有时间再去探究juc编程。

     继承Thread类方式实现Runnable or Callable方式
    优点编写简单,如需访问当前线程,只需使用this即可

    1、还可以继承其他类

    2、在这种方式下,多个线程可以共享同一个target对象,也就是一份资源,多个代理,从而可以将CPU、代码和数据分开

    缺点已经继承了Thread类了,无法继承其他类编程相对复杂些,访问当前线程必须使用Thread.currentThread()
    总结推荐采用实现Runnable or Callable方式来创建多线程

    最后附上书中方式三写法:

    package com.ly.thread;
    
    import java.util.concurrent.FutureTask;
    
    /**
     * liyang 2020-08-11
     *
     */
    public class ThirdThread {
    
        public static void main(String[] args) {
            //创建Callable对象
            //ThirdThread rt = new ThirdThread();
            //先使用Lambda表达式创建Callable<>对象
            //再使用FutureTask来包装Callable对象
            FutureTask<Integer> task = new FutureTask<>(() -> {
                int i = 0;
                for(; i < 100; i++) {
                    System.out.println(Thread.currentThread().getName() + "的循环变量i的值: " + i);
                }
                return i; //call()方法可以有返回值
            });
    
            for(int i = 0; i < 100; i++) {
                System.out.println(Thread.currentThread().getName() + "的循环变量i的值: " + i);
                if(i == 20) {
                    //实例还是以Callable的封装对象来创建并启动线程的
                    new Thread(task, "有返回值的线程").start();
                }
            }
    
            try {
                System.out.println("子线程的返回值:" + task.get()); //获取线程返回值
            }
            catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    

    运行结果:交替执行主线程和子线程,以下结果仅仅作为显示,因为各打印100行,太占篇幅

    main的循环变量i的值: 0
    main的循环变量i的值: 1
    main的循环变量i的值: 2
    ...
    
    有返回值的线程的循环变量i的值: 0
    有返回值的线程的循环变量i的值: 1
    ...
    有返回值的线程的循环变量i的值: 99
    main的循环变量i的值: 99
    子线程的返回值:100
    
    Process finished with exit code 0

     

    更多相关内容
  • Java多线程超详解

    万次阅读 多人点赞 2019-06-11 01:00:30
    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。 那么话不多说,今天本帅将记录自己线程的学习。 线程的相关API //获取当前...

    引言

    随着计算机的配置越来越高,我们需要将进程进一步优化,细分为线程,充分提高图形化界面的多线程的开发。这就要求对线程的掌握很彻底。
    那么话不多说,今天本帅将记录自己线程的学习。

    程序,进程,线程的基本概念+并行与并发:

    程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
    进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
    线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

    即:线程《线程(一个程序可以有多个线程)
    程序:静态的代码 进程:动态执行的程序
    线程:进程中要同时干几件事时,每一件事的执行路径成为线程。

    并行:多个CPU同时执行多个任务,比如:多个人同时做不同的事
    并发:一个CPU(采用时间片)同时执行多个任务,比如秒杀平台,多个人做同件事

    线程的相关API

    //获取当前线程的名字
    Thread.currentThread().getName()

    1.start():1.启动当前线程2.调用线程中的run方法
    2.run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
    3.currentThread():静态方法,返回执行当前代码的线程
    4.getName():获取当前线程的名字
    5.setName():设置当前线程的名字
    6.yield():主动释放当前线程的执行权
    7.join():在线程中插入执行另一个线程,该线程被阻塞,直到插入执行的线程完全执行完毕以后,该线程才继续执行下去
    8.stop():过时方法。当执行此方法时,强制结束当前线程。
    9.sleep(long millitime):线程休眠一段时间
    10.isAlive():判断当前线程是否存活

    判断是否是多线程

    一条线程即为一条执行路径,即当能用一条路径画出来时即为一个线程
    例:如下看似既执行了方法一,又执行了方法2,但是其实质就是主线程在执行方法2和方法1这一条路径,所以就是一个线程

    public class Sample{
    		public void method1(String str){
    			System.out.println(str);
    		}
    	
    	public void method2(String str){
    		method1(str);
    	}
    	
    	public static void main(String[] args){
    		Sample s = new Sample();
    		s.method2("hello");
    	}
    }
    

    在这里插入图片描述

    线程的调度

    调度策略:
    时间片:线程的调度采用时间片轮转的方式
    抢占式:高优先级的线程抢占CPU
    Java的调度方法:
    1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
    2.对高优先级,使用优先调度的抢占式策略

    线程的优先级

    等级:
    MAX_PRIORITY:10
    MIN_PRIORITY:1
    NORM_PRIORITY:5

    方法:
    getPriority():返回线程优先级
    setPriority(int newPriority):改变线程的优先级

    注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

    多线程的创建方式

    1. 方式1:继承于Thread类

    1.创建一个集成于Thread类的子类 (通过ctrl+o(override)输入run查找run方法)
    2.重写Thread类的run()方法
    3.创建Thread子类的对象
    4.通过此对象调用start()方法

    start与run方法的区别:

    start方法的作用:1.启动当前线程 2.调用当前线程的重写的run方法(在主线程中生成子线程,有两条线程)
    调用start方法以后,一条路径代表一个线程,同时执行两线程时,因为时间片的轮换,所以执行过程随机分配,且一个线程对象只能调用一次start方法。
    run方法的作用:在主线程中调用以后,直接在主线程一条线程中执行了该线程中run的方法。(调用线程中的run方法,只调用run方法,并不新开线程)

    总结:我们不能通过run方法来新开一个线程,只能调用线程中重写的run方法(可以在线程中不断的调用run方法,但是不能开启子线程,即不能同时干几件事),start是开启线程,再调用方法(即默认开启一次线程,调用一次run方法,可以同时执行几件事)
    在这里插入图片描述

    多线程例子(火车站多窗口卖票问题)

    	package com.example.paoduantui.Thread;
    	
    	import android.view.Window;
    	
    	/**
    	 *
    	 * 创建三个窗口卖票,总票数为100张,使用继承自Thread方式
    	 * 用静态变量保证三个线程的数据独一份
    	 * 
    	 * 存在线程的安全问题,有待解决
    	 *
    	 * */
    	
    	public class ThreadDemo extends Thread{
    	
    	    public static void main(String[] args){
    	        window t1 = new window();
    	        window t2 = new window();
    	        window t3 = new window();
    	
    	        t1.setName("售票口1");
    	        t2.setName("售票口2");
    	        t3.setName("售票口3");
    	
    	        t1.start();
    	        t2.start();
    	        t3.start();
    	    }
    	
    	}
    	
    	class window extends Thread{
    	    private static int ticket = 100; //将其加载在类的静态区,所有线程共享该静态变量
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    2. 方式2:实现Runable接口方式

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

    具体操作,将一个类实现Runable接口,(插上接口一端)。
    另外一端,通过实现类的对象与线程对象通过此Runable接口插上接口实现

    	package com.example.paoduantui.Thread;
    	
    	public class ThreadDemo01 {
    	    
    	    public static  void main(String[] args){
    	        window1 w = new window1();
    	        
    	        //虽然有三个线程,但是只有一个窗口类实现的Runnable方法,由于三个线程共用一个window对象,所以自动共用100张票
    	        
    	        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();
    	    }
    	}
    	
    	class window1 implements Runnable{
    	    
    	    private int ticket = 100;
    	
    	    @Override
    	    public void run() {
    	        while(true){
    	            if(ticket>0){
    	//                try {
    	//                    sleep(100);
    	//                } catch (InterruptedException e) {
    	//                    e.printStackTrace();
    	//                }
    	                System.out.println(Thread.currentThread().getName()+"当前售出第"+ticket+"张票");
    	                ticket--;
    	            }else{
    	                break;
    	            }
    	        }
    	    }
    	}
    

    比较创建线程的两种方式:
    开发中,优先选择实现Runable接口的方式
    原因1:实现的方式没有类的单继承性的局限性
    2:实现的方式更适合用来处理多个线程有共享数据的情况
    联系:Thread也是实现自Runable,两种方式都需要重写run()方法,将线程要执行的逻辑声明在run中

    3.新增的两种创建多线程方式

    1.实现callable接口方式:

    与使用runnable方式相比,callable功能更强大些:
    runnable重写的run方法不如callaalbe的call方法强大,call方法可以有返回值
    方法可以抛出异常
    支持泛型的返回值
    需要借助FutureTask类,比如获取返回结果

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    /**
     * 创建线程的方式三:实现callable接口。---JDK 5.0新增
     *是否多线程?否,就一个线程
     *
     * 比runable多一个FutureTask类,用来接收call方法的返回值。
     * 适用于需要从线程中接收返回值的形式
     * 
     * //callable实现新建线程的步骤:
     * 1.创建一个实现callable的实现类
     * 2.实现call方法,将此线程需要执行的操作声明在call()中
     * 3.创建callable实现类的对象
     * 4.将callable接口实现类的对象作为传递到FutureTask的构造器中,创建FutureTask的对象
     * 5.将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start方法启动(通过FutureTask的对象调用方法get获取线程中的call的返回值)
     * 
     * */
    
    
    //实现callable接口的call方法
    class NumThread implements Callable{
    
        private int sum=0;//
    
        //可以抛出异常
        @Override
        public Object call() throws Exception {
            for(int i = 0;i<=100;i++){
                if(i % 2 == 0){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                    sum += i;
                }
            }
            return sum;
        }
    }
    
    public class ThreadNew {
    
        public static void main(String[] args){
            //new一个实现callable接口的对象
            NumThread numThread = new NumThread();
    
            //通过futureTask对象的get方法来接收futureTask的值
            FutureTask futureTask = new FutureTask(numThread);
    
            Thread t1 = new Thread(futureTask);
            t1.setName("线程1");
            t1.start();
    
            try {
                //get返回值即为FutureTask构造器参数callable实现类重写的call的返回值
               Object sum = futureTask.get();
               System.out.println(Thread.currentThread().getName()+":"+sum);
            } catch (ExecutionException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    
    使用线程池的方式:

    背景:经常创建和销毁,使用量特别大的资源,比如并发情况下的线程,对性能影响很大。
    思路:提前创建好多个线程,放入线程池之,使用时直接获取,使用完放回池中。可以避免频繁创建销毁,实现重复利用。类似生活中的公共交通工具。(数据库连接池)
    好处:提高响应速度(减少了创建新线程的时间)
    降低资源消耗(重复利用线程池中线程,不需要每次都创建)
    便于线程管理
    corePoolSize:核心池的大小
    maximumPoolSize:最大线程数
    keepAliveTime:线程没有任务时最多保持多长时间后会终止
    。。。。。。

    JDK 5.0 起提供了线程池相关API:ExecutorService 和 Executors
    ExecutorService:真正的线程池接口。常见子类ThreadPoolExecutor.
    void execute(Runnable coommand):执行任务/命令,没有返回值,一般用来执行Runnable
    Futuresubmit(Callable task):执行任务,有返回值,一般又来执行Callable
    void shutdown():关闭连接池。

    Executors工具类,线程池的工厂类,用于创建并返回不同类型的线程池
    Executors.newCachedThreadPool()创建一个可根据需要创建新线程的线程池
    Executors.newFixedThreadPool(n)创建一个可重用固定线程数的线程池
    Executors.newSingleThreadExecutor():创建一个只有一个线程的线程池
    Executors.newScheduledThreadPool(n)创建一个线程池,它可安排在给定延迟后运行命令或者定期地执行。

    线程池构造批量线程代码如下:

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.ExecutorService;
    import java.util.concurrent.Executors;
    
    /**
     * 创建线程的方式四:使用线程池(批量使用线程)
     *1.需要创建实现runnable或者callable接口方式的对象
     * 2.创建executorservice线程池
     * 3.将创建好的实现了runnable接口类的对象放入executorService对象的execute方法中执行。
     * 4.关闭线程池
     *
     * */
    
    class NumberThread implements Runnable{
    
    
        @Override
        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{
        @Override
        public void run() {
            for(int i = 0;i<100; i++){
                if(i%2==1){
                    System.out.println(Thread.currentThread().getName()+":"+i);
                }
            }
        }
    }
    
    public class ThreadPool {
    
        public static void main(String[] args){
    
            //创建固定线程个数为十个的线程池
            ExecutorService executorService = Executors.newFixedThreadPool(10);
    
            //new一个Runnable接口的对象
            NumberThread number = new NumberThread();
            NumberThread1 number1 = new NumberThread1();
    
            //执行线程,最多十个
            executorService.execute(number1);
            executorService.execute(number);//适合适用于Runnable
    
            //executorService.submit();//适合使用于Callable
            //关闭线程池
            executorService.shutdown();
        }
    
    }
    

    目前两种方式要想调用新线程,都需要用到Thread中的start方法。

    java virtual machine(JVM):java虚拟机内存结构

    程序(一段静态的代码)——————》加载到内存中——————》进程(加载到内存中的代码,动态的程序)
    进程可细分为多个线程,一个线程代表一个程序内部的一条执行路径
    每个线程有其独立的程序计数器(PC,指导着程序向下执行)与运行栈(本地变量等,本地方法等)
    在这里插入图片描述

    大佬传送门:https://blog.csdn.net/bluetjs/article/details/52874852

    线程通信方法:

    wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

    由于wait,notify,以及notifyAll都涉及到与锁相关的操作
    wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
    notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

    有点类似于我要拉粑粑,我先进了厕所关了门,但是发现厕所有牌子写着不能用,于是我把厕所锁给了别人,别人进来拉粑粑还是修厕所不得而知,直到有人通知我厕所好了我再接着用。

    所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

    线程的分类:

    java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)

    若JVM中都是守护线程,当前JVM将退出。(形象理解,唇亡齿寒)

    线程的生命周期:

    JDK中用Thread.State类定义了线程的几种状态,如下:

    线程生命周期的阶段描述
    新建当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建状态
    就绪处于新建状态的线程被start后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
    运行当就绪的线程被调度并获得CPU资源时,便进入运行状态,run方法定义了线程的操作和功能
    阻塞在某种特殊情况下,被人为挂起或执行输入输出操作时,让出CPU并临时终止自己的执行,进入阻塞状态
    死亡线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束

    在这里插入图片描述

    线程的同步:在同步代码块中,只能存在一个线程。

    线程的安全问题:

    什么是线程安全问题呢?
    线程安全问题是指,多个线程对同一个共享数据进行操作时,线程没来得及更新共享数据,从而导致另外线程没得到最新的数据,从而产生线程安全问题。

    上述例子中:创建三个窗口卖票,总票数为100张票
    1.卖票过程中,出现了重票(票被反复的卖出,ticket未被减少时就打印出了)错票。
    2.问题出现的原因:当某个线程操作车票的过程中,尚未完成操作时,其他线程参与进来,也来操作车票。(将此过程的代码看作一个区域,当有线程进去时,装锁,不让别的线程进去)
    生动理解的例子:有一个厕所,有人进去了,但是没有上锁,于是别人不知道你进去了,别人也进去了对厕所也使用造成错误。
    3.如何解决:当一个线程在操作ticket时,其他线程不能参与进来,直到此线程的生命周期结束
    4.在java中,我们通过同步机制,来解决线程的安全问题。

    方式一:同步代码块
    使用同步监视器(锁)
    Synchronized(同步监视器){
    //需要被同步的代码
    }
    说明:

    1. 操作共享数据的代码(所有线程共享的数据的操作的代码)(视作卫生间区域(所有人共享的厕所)),即为需要共享的代码(同步代码块,在同步代码块中,相当于是一个单线程,效率低)
    2. 共享数据:多个线程共同操作的数据,比如公共厕所就类比共享数据
    3. 同步监视器(俗称:锁):任何一个的对象都可以充当锁。(但是为了可读性一般设置英文成lock)当锁住以后只能有一个线程能进去(要求:多个线程必须要共用同一把锁,比如火车上的厕所,同一个标志表示有人)

    Runable天生共享锁,而Thread中需要用static对象或者this关键字或者当前类(window。class)来充当唯一锁

    方式二:同步方法
    使用同步方法,对方法进行synchronized关键字修饰
    将同步代码块提取出来成为一个方法,用synchronized关键字修饰此方法。
    对于runnable接口实现多线程,只需要将同步方法用synchronized修饰
    而对于继承自Thread方式,需要将同步方法用static和synchronized修饰,因为对象不唯一(锁不唯一)

    总结:1.同步方法仍然涉及到同步监视器,只是不需要我们显示的声明。
    2.非静态的同步方法,同步监视器是this
    静态的同步方法,同步监视器是当前类本身。继承自Thread。class

    方式三:JDK5.0新增的lock锁方法

    package com.example.paoduantui.Thread;
    
    
    import java.util.concurrent.locks.ReentrantLock;
    
    class Window implements Runnable{
        private int ticket = 100;//定义一百张票
        //1.实例化锁
        private ReentrantLock lock = new ReentrantLock();
    
        @Override
        public void run() {
            
                while (true) {
    
                    //2.调用锁定方法lock
                    lock.lock();
    
                    if (ticket > 0) {
                        try {
                            Thread.sleep(100);
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
    
                        System.out.println(Thread.currentThread().getName() + "售出第" + ticket + "张票");
                        ticket--;
                    } else {
                        break;
                    }
                }
    
    
            }
    }
    
    public class LockTest {
    
        public static void main(String[] args){
           Window w= new Window();
    
           Thread t1 = new Thread(w);
           Thread t2 = new Thread(w);
           Thread t3 = new Thread(w);
    
           t1.setName("窗口1");
           t2.setName("窗口1");
           t3.setName("窗口1");
    
           t1.start();
           t2.start();
           t3.start();
        }
    
    }
    

    总结:Synchronized与lock的异同?

    相同:二者都可以解决线程安全问题
    不同:synchronized机制在执行完相应的代码逻辑以后,自动的释放同步监视器
    lock需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())(同时以为着lock的方式更为灵活)

    优先使用顺序:
    LOCK-》同步代码块-》同步方法

    判断线程是否有安全问题,以及如何解决:

    1.先判断是否多线程
    2.再判断是否有共享数据
    3.是否并发的对共享数据进行操作
    4.选择上述三种方法解决线程安全问题

    例题:

    	package com.example.paoduantui.Thread;
    	
    	/***
    	 * 描述:甲乙同时往银行存钱,存够3000
    	 *
    	 *
    	 * */
    	
    	//账户
    	class Account{
    	    private double balance;//余额
    	    //构造器
    	    public Account(double balance) {
    	        this.balance = balance;
    	    }
    	    //存钱方法
    	    public synchronized void deposit(double amt){
    	        if(amt>0){
    	            balance +=amt;
    	            try {
    	                Thread.sleep(1000);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	            System.out.println(Thread.currentThread().getName()+"存钱成功,余额为:"+balance);
    	        }
    	    }
    	}
    	
    	//两个顾客线程
    	class Customer extends Thread{
    	     private Account acct;
    	
    	     public Customer(Account acct){
    	         this.acct = acct;
    	     }
    	
    	
    	
    	    @Override
    	    public void run() {
    	        for (int i = 0;i<3;i++){
    	            acct.deposit(1000);
    	        }
    	    }
    	}
    	
    	//主方法,之中new同一个账户,甲乙两个存钱线程。
    	public class AccountTest {
    	
    	    public static void main(String[] args){
    	        Account acct = new Account(0);
    	        Customer c1 = new Customer(acct);
    	        Customer c2 = new Customer(acct);
    	
    	        c1.setName("甲");
    	        c2.setName("乙");
    	
    	        c1.start();
    	        c2.start();
    	    }
    	
    	}
    

    解决单例模式的懒汉式的线程安全问题:

    单例:只能通过静态方法获取一个实例,不能通过构造器来构造实例
    1.构造器的私有化:
    private Bank(){}//可以在构造器中初始化东西
    private static Bank instance = null;//初始化静态实例

    public static Bank getInstance(){
    if(instance!=null){
    instance = new Bank();
    }
    return instance;
    }

    假设有多个线程调用此单例,而调用的获取单例的函数作为操作共享单例的代码块并没有解决线程的安全问题,会导致多个线程都判断实例是否为空,此时就会导致多个实例的产生,也就是单例模式的线程安全问题。

    解决线程安全问题的思路:

    1. 将获取单例的方法改写成同部方法,即加上synchronized关键字,此时同步监视器为当前类本身。(当有多个线程并发的获取实例时,同时只能有一个线程获取实例),解决了单例模式的线程安全问题。
    2. 用同步监视器包裹住同步代码块的方式。

    懒汉式单例模式的模型,例如:生活中的限量版的抢购:
    当一群人并发的抢一个限量版的东西的时候,可能同时抢到了几个人,他们同时进入了房间(同步代码块内)
    但是只有第一个拿到限量版东西的人才能到手,其余人都不能拿到,所以效率稍高的做法是,当东西被拿走时,我们在门外立一块牌子,售罄。
    这样就减少了线程的等待。即下面效率稍高的懒汉式写法:

    package com.example.paoduantui.Thread;
    
    public class Bank {
        //私有化构造器
        private Bank(){}
        //初始化静态实例化对象
        private static  Bank instance = null;
    
        //获取单例实例,此种懒汉式单例模式存在线程不安全问题(从并发考虑)
    
        public static  Bank getInstance(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
    
        //同步方法模式的线程安全
        public static synchronized Bank getInstance1(){
            if(instance==null){
                instance = new Bank();
            }
            return  instance;
        }
        //同步代码块模式的线程安全(上锁)
        public  static Bank getInstance2(){
            synchronized (Bank.class){
                if(instance==null){
                    instance = new Bank();
                }
                return  instance;
            }
        }
        
        //效率更高的线程安全的懒汉式单例模式
        /**
         * 由于当高并发调用单例模式的时候,类似于万人夺宝,只有第一个进入房间的人才能拿到宝物,
         * 当多个人进入这个房间时,第一个人拿走了宝物,也就另外几个人需要在同步代码块外等候,
         * 剩下的人只需要看到门口售罄的牌子即已知宝物已经被夺,可以不用进入同步代码块内,提高了效率。
         * 
         * 
         * */
        public static Bank getInstance3(){
            if (instance==null){
                synchronized (Bank.class){
                    if(instance==null){
                        instance = new Bank();
                    }
                }
            }
            return  instance;
        }
    }
    

    线程的死锁问题:

    线程死锁的理解:僵持,谁都不放手,一双筷子,我一只你一只,都等对方放手(死锁,两者都进入阻塞,谁都吃不了饭,进行不了下面吃饭的操作)
    出现死锁以后,不会出现提示,只是所有线程都处于阻塞状态,无法继续

    package com.example.paoduantui.Thread;
    
    
    /**
     * 演示线程的死锁问题
     *
     * */
    public class Demo {
    
        public static void main(String[] args){
    
            final StringBuffer s1 = new StringBuffer();
            final StringBuffer s2 = new StringBuffer();
    
    
            new Thread(){
                @Override
                public void run() {
                    //先拿锁一,再拿锁二
                    synchronized (s1){
                        s1.append("a");
                        s2.append("1");
    
                        synchronized (s2) {
                            s1.append("b");
                            s2.append("2");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }.start();
    
            //使用匿名内部类实现runnable接口的方式实现线程的创建
            new Thread(new Runnable() {
                @Override
                public void run() {
                    synchronized (s2){
                        s1.append("c");
                        s2.append("3");
    
                        synchronized (s1) {
                            s1.append("d");
                            s2.append("4");
    
                            System.out.println(s1);
                            System.out.println(s2);
                        }
                    }
                }
            }).start();
        }
    
    }
    

    运行结果:
    1.先调用上面的线程,再调用下面的线程:
    在这里插入图片描述
    2.出现死锁:
    在这里插入图片描述
    3.先调用下面的线程,再调用上面的线程。
    在这里插入图片描述

    死锁的解决办法:

    1.减少同步共享变量
    2.采用专门的算法,多个线程之间规定先后执行的顺序,规避死锁问题
    3.减少锁的嵌套。

    线程的通信

    通信常用方法:

    通信方法描述
    wait()一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器
    notify一旦执行此方法,就会唤醒被wait的一个线程,如果有多个线程,就唤醒优先级高的线程
    notifyAll一旦执行此方法,就会唤醒所有被wait()的线程

    使用前提:这三个方法均只能使用在同步代码块或者同步方法中。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的例子:使用两个线程打印1—100,线程1,线程2交替打印
     *
     * 当我们不采取线程之间的通信时,无法达到线程1,2交替打印(cpu的控制权,是自动分配的)
     * 若想达到线程1,2交替打印,需要:
     * 1.当线程1获取锁以后,进入代码块里将number++(数字打印并增加)操作完以后,为了保证下个锁为线程2所有,需要将线程1阻塞(线程1你等等wait())。(输出1,number为2)
     * 2.当线程2获取锁以后,此时线程1已经不能进入同步代码块中了,所以,为了让线程1继续抢占下一把锁,需要让线程1的阻塞状态取消(通知线程1不用等了notify()及notifyAll()),即应该在进入同步代码块时取消线程1的阻塞。
     *
     * */
    
    class Number implements Runnable{
    
        private int number = 1;//设置共享数据(线程之间对于共享数据的共享即为通信)
    
    
        //对共享数据进行操作的代码块,需要线程安全
        @Override
        public synchronized void run() {
    
            while(true){
                //使得线程交替等待以及通知交替解等待
                notify();//省略了this.notify()关键字
                if(number<100){
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+":"+number);
                    number++;
                    try {
                        wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    break;
                }
            }
        }
    }
    
    public class CommunicationTest {
    
        public static void main(String[] args){
            //创建runnable对象
            Number number = new Number();
    
            //创建线程,并实现runnable接口
            Thread t1 = new Thread(number);
            Thread t2 = new Thread(number);
    
            //给线程设置名字
            t1.setName("线程1");
            t2.setName("线程2");
    
            //开启线程
            t1.start();
            t2.start();
    
        }
    
    }
    

    sleep和wait的异同:

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

    经典例题:生产者/消费者问题:

    生产者(Priductor)将产品交给店员(Clerk),而消费者(Customer)从店员处取走产品,店员一次只能持有固定数量的产品(比如20个),如果生产者视图生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了再通知生产者继续生产:如果店中没有产品了,店员会告诉消费者等一下,如果店中有产品了再通知消费者来取走产品。

    这里可能出现两个问题:
    生产者比消费者快的时候,消费者会漏掉一些数据没有收到。
    消费者比生产者快时,消费者会去相同的数据。

    package com.example.paoduantui.Thread;
    
    
    /**
     * 线程通信的应用:生产者/消费者问题
     *
     * 1.是否是多线程问题?是的,有生产者线程和消费者线程(多线程的创建,四种方式)
     * 2.多线程问题是否存在共享数据? 存在共享数据----产品(同步方法,同步代码块,lock锁)
     * 3.多线程是否存在线程安全问题? 存在----都对共享数据产品进行了操作。(三种方法)
     * 4.是否存在线程间的通信,是,如果生产多了到20时,需要通知停止生产(wait)。(线程之间的通信问题,需要wait,notify等)
     *
     * */
    
    
    	class Clerk{
    	
    	    private int productCount = 0;
    	
    	
    	    //生产产品
    	    public synchronized void produceProduct() {
    	
    	        if(productCount<20) {
    	            productCount++;
    	
    	            System.out.println(Thread.currentThread().getName()+":开始生产第"+productCount+"个产品");
    	            notify();
    	        }else{
    	            //当有20个时,等待wait
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	
    	    //消费产品
    	    public synchronized void consumeProduct() {
    	        if (productCount>0){
    	            System.out.println(Thread.currentThread().getName()+":开始消费第"+productCount+"个产品");
    	            productCount--;
    	            notify();
    	        }else{
    	            //当0个时等待
    	            try {
    	                wait();
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	        }
    	    }
    	}
    	
    	class Producer extends Thread{//生产者线程
    	
    	    private Clerk clerk;
    	
    	    public Producer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        try {
    	            sleep(10);
    	        } catch (InterruptedException e) {
    	            e.printStackTrace();
    	        }
    	        System.out.println(Thread.currentThread().getName()+";开始生产产品......");
    	
    	        while(true){
    	            clerk.produceProduct();
    	        }
    	    }
    	}
    	
    	class Consumer implements Runnable{//消费者线程
    	
    	    private Clerk clerk;
    	
    	    public Consumer(Clerk clerk) {
    	        this.clerk = clerk;
    	    }
    	
    	    @Override
    	    public void run() {
    	
    	        System.out.println(Thread.currentThread().getName()+":开始消费产品");
    	
    	        while(true){
    	            try {
    	                Thread.sleep(1);
    	            } catch (InterruptedException e) {
    	                e.printStackTrace();
    	            }
    	
    	            clerk.consumeProduct();
    	        }
    	
    	    }
    	}
    	
    	public class ProductTest {
    	
    	    public static void main(String[] args){
    	        Clerk clerk = new Clerk();
    	
    	        Producer p1 = new Producer(clerk);
    	        p1.setName("生产者1");
    	
    	        Consumer c1 = new Consumer(clerk);
    	        Thread t1 = new Thread(c1);
    	        t1.setName("消费者1");
    	
    	        p1.start();
    	        t1.start();
    	
    	    }
    	
    	}
    
    展开全文
  • Java多线程(超详细!)

    万次阅读 多人点赞 2021-05-12 17:00:59
    注意:一个进程可以启动线程。 eg.对于java程序来说,当在DOS命令窗口中输入: java HelloWorld 回车之后。 会先启动JVM,而JVM就是一个进程。 JVM再启动一个主线程调用main方法。 同时再启动一个垃圾回收线程...

    1、什么是进程?什么是线程?

    进程是:一个应用程序(1个进程是一个软件)。

    线程是:一个进程中的执行场景/执行单元。

    注意:一个进程可以启动多个线程。

    eg.
    对于java程序来说,当在DOS命令窗口中输入:
    java HelloWorld 回车之后。会先启动JVM,而JVM就是一个进程

    JVM再启动一个主线程调用main方法(main方法就是主线程)。
    同时再启动一个垃圾回收线程负责看护,回收垃圾。

    最起码,现在的java程序中至少有两个线程并发,一个是 垃圾回收线程,一个是 执行main方法的主线程

    2、进程和线程是什么关系?

    进程:可以看做是现实生活当中的公司。

    线程:可以看做是公司当中的某个员工。

    注意:
    进程A和进程B的 内存独立不共享

    eg.
    魔兽游戏是一个进程
    酷狗音乐是一个进程
    这两个进程是独立的,不共享资源。

    线程A和线程B是什么关系?

    在java语言中:

    线程A和线程B,堆内存方法区 内存共享。但是 栈内存 独立一个线程一个栈

    eg.
    假设启动10个线程,会有10个栈空间,每个栈和每个栈之间,互不干扰,各自执行各自的,这就是多线程并发。

    eg.
    火车站,可以看做是一个进程
    火车站中的每一个售票窗口可以看做是一个线程
    我在窗口1购票,你可以在窗口2购票,你不需要等我,我也不需要等你。所以多线程并发可以提高效率。


    java中之所以有多线程机制,目的就是为了 提高程序的处理效率

    3、思考一个问题

    使用了多线程机制之后,main方法结束,是不是有可能程序也不会结束?

    main方法结束只是主线程结束了,主栈空了,其它的栈(线程)可能还在压栈弹栈。

    4.分析一个问题

    对于单核的CPU来说,真的可以做到真正的多线程并发吗?

    对于多核的CPU电脑来说,真正的多线程并发是没问题的。4核CPU表示同一个时间点上,可以真正的有4个进程并发执行。

    单核的CPU表示只有一个大脑:
    不能够做到真正的多线程并发,但是可以做到给人一种“多线程并发”的感觉。

    对于单核的CPU来说,在某一个时间点上实际上只能处理一件事情,但是由于CPU的处理速度极快,多个线程之间频繁切换执行,给别人的感觉是:多个事情同时在做!!!

    eg.
    线程A:播放音乐

    线程B:运行魔兽游戏

    线程A和线程B频繁切换执行,人类会感觉音乐一直在播放,游戏一直在运行,
    给我们的感觉是同时并发的。(因为计算机的速度很快,我们人的眼睛很慢,所以才会感觉是多线程!)

    4、什么是真正的多线程并发?

    t1线程执行t1的。
    t2线程执行t2的。
    t1不会影响t2,t2也不会影响t1。这叫做真正的多线程并发。

    5、关于线程对象的生命周期(附图)?★★★★★

    1. 新建状态
    2. 就绪状态
    3. 运行状态
    4. 阻塞状态
    5. 死亡状态

    在这里插入图片描述

    线程构造方法

    构造方法名备注
    Thread()
    Thread(String name)name为线程名字
    创建线程第二种方式
    Thread(Runnable target)
    Thread(Runnable target, String name)name为线程名字

    6、java语言中,实现线程有两种方式

    第一种方式:

    编写一个类,直接 继承 java.lang.Thread重写 run方法

    1. 怎么创建线程对象? new继承线程的类。
    2. 怎么启动线程呢? 调用线程对象的 start() 方法。

    伪代码:

    // 定义线程类
    public class MyThread extends Thread{
    	public void run(){
    	
    	}
    }
    // 创建线程对象
    MyThread t = new MyThread();
    // 启动线程。
    t.start();
    

    eg.

    public class ThreadTest02 {
        public static void main(String[] args) {
            MyThread t = new MyThread();
            // 启动线程
            //t.run(); // 不会启动线程,不会分配新的分支栈。(这种方式就是单线程。)
            t.start();
            // 这里的代码还是运行在主线程中。
            for(int i = 0; i < 1000; i++){
                System.out.println("主线程--->" + i);
            }
        }
    }
    
    class MyThread extends Thread {
        @Override
        public void run() {
            // 编写程序,这段程序运行在分支线程中(分支栈)。
            for(int i = 0; i < 1000; i++){
                System.out.println("分支线程--->" + i);
            }
        }
    }
    

    注意:

    • t.run() 不会启动线程,只是普通的调用方法而已。不会分配新的分支栈。(这种方式就是单线程。)

    • t.start() 方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
      这段代码的任务只是为了开启一个新的栈空间,只要新的栈空间开出来,start()方法就结束了。线程就启动成功了。
      启动成功的线程会自动调用run方法,并且run方法在分支栈的栈底部(压栈)。
      run方法在分支栈的栈底部,main方法在主栈的栈底部。run和main是平级的。

    调用run()方法内存图:
    在这里插入图片描述

    调用start()方法内存图:
    在这里插入图片描述

    第二种方式:

    编写一个类,实现 java.lang.Runnable 接口,实现run方法

    1. 怎么创建线程对象? new线程类传入可运行的类/接口。
    2. 怎么启动线程呢? 调用线程对象的 start() 方法。

    伪代码:

    // 定义一个可运行的类
    public class MyRunnable implements Runnable {
    	public void run(){
    	
    	}
    }
    // 创建线程对象
    Thread t = new Thread(new MyRunnable());
    // 启动线程
    t.start();
    

    eg.

    public class ThreadTest03 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable()); 
            // 启动线程
            t.start();
            
            for(int i = 0; i < 100; i++){
                System.out.println("主线程--->" + i);
            }
        }
    }
    
    // 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
    class MyRunnable implements Runnable {
        @Override
        public void run() {
            for(int i = 0; i < 100; i++){
                System.out.println("分支线程--->" + i);
            }
        }
    }
    

    采用匿名内部类创建:

    public class ThreadTest04 {
        public static void main(String[] args) {
            // 创建线程对象,采用匿名内部类方式。
            Thread t = new Thread(new Runnable(){
                @Override
                public void run() {
                    for(int i = 0; i < 100; i++){
                        System.out.println("t线程---> " + i);
                    }
                }
            });
    
            // 启动线程
            t.start();
    
            for(int i = 0; i < 100; i++){
                System.out.println("main线程---> " + i);
            }
        }
    }
    

    注意:
    第二种方式实现接口比较常用,因为一个类实现了接口,它还可以去继承其它的类,更灵活。

    7、获取当前线程对象、获取线程对象名字、修改线程对象名字

    方法名作用
    static Thread currentThread()获取当前线程对象
    String getName()获取线程对象名字
    void setName(String name)修改线程对象名字

    当线程没有设置名字的时候,默认的名字是什么?

    • Thread-0
    • Thread-1
    • Thread-2
    • Thread-3

    eg.

    class MyThread2 extends Thread {
        public void run(){
            for(int i = 0; i < 100; i++){
                // currentThread就是当前线程对象。当前线程是谁呢?
                // 当t1线程执行run方法,那么这个当前线程就是t1
                // 当t2线程执行run方法,那么这个当前线程就是t2
                Thread currentThread = Thread.currentThread();
                System.out.println(currentThread.getName() + "-->" + i);
    
                //System.out.println(super.getName() + "-->" + i);
                //System.out.println(this.getName() + "-->" + i);
            }
        }
    }
    

    8、关于线程的sleep方法

    方法名作用
    static void sleep(long millis)让当前线程休眠millis秒
    1. 静态方法:Thread.sleep(1000);
    2. 参数是毫秒
    3. 作用: 让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
      这行代码出现在A线程中,A线程就会进入休眠。
      这行代码出现在B线程中,B线程就会进入休眠。
    4. Thread.sleep()方法,可以做到这种效果:
      间隔特定的时间,去执行一段特定的代码,每隔多久执行一次。

    eg.

    public class ThreadTest06 {
        public static void main(String[] args) {
        	//每打印一个数字睡1s
            for(int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
    
                // 睡眠1秒
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    9、关于线程中断sleep()的方法

    方法名作用
    void interrupt()终止线程的睡眠

    eg.

    public class ThreadTest08 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable2());
            t.setName("t");
            t.start();
    
            // 希望5秒之后,t线程醒来(5秒之后主线程手里的活儿干完了。)
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 终断t线程的睡眠(这种终断睡眠的方式依靠了java的异常处理机制。)
            t.interrupt();
        }
    }
    
    class MyRunnable2 implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "---> begin");
            try {
                // 睡眠1年
                Thread.sleep(1000 * 60 * 60 * 24 * 365);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            //1年之后才会执行这里
            System.out.println(Thread.currentThread().getName() + "---> end");
    }
    

    10、补充:run()方法小知识点

    在这里插入图片描述

    为什么run()方法只能try…catch…不能throws?

    因为run()方法在父类中没有抛出任何异常子类不能比父类抛出更多的异常

    11、java中强行终止一个线程的执行(不推荐使用,了解即可!)

    eg.

    public class ThreadTest09 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable3());
            t.setName("t");
            t.start();
    
            // 模拟5秒
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 5秒之后强行终止t线程
            t.stop(); // 已过时(不建议使用。)
        }
    }
    
    class MyRunnable3 implements Runnable {
    
        @Override
        public void run() {
            for(int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    注意:

    这种方式存在很大的缺点:容易丢失数据

    因为这种方式是直接将线程杀死了,线程没有保存的数据将会丢失。不建议使用。

    12、Java中合理结束一个进程的执行(常用)

    eg.

    public class ThreadTest10 {
        public static void main(String[] args) {
            MyRunable4 r = new MyRunable4();
            Thread t = new Thread(r);
            t.setName("t");
            t.start();
    
            // 模拟5秒
            try {
                Thread.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            // 终止线程
            // 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
            r.run = false;
        }
    }
    
    class MyRunable4 implements Runnable {
    
        // 打一个布尔标记
        boolean run = true;
    
        @Override
        public void run() {
            for (int i = 0; i < 10; i++){
                if(run){
                    System.out.println(Thread.currentThread().getName() + "--->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    // return就结束了,你在结束之前还有什么没保存的。
                    // 在这里可以保存呀。
                    //save....
    
                    //终止当前线程
                    return;
                }
            }
        }
    }
    

    为什么if()语句要在循环里面?

    由于一个线程一直运行此程序,要是if判断在外面只会在启动线程时判断并不会结束,因此需要每次循环判断一下标记。

    补充小知识:线程调度(了解)

    1.常见的线程调度模型有哪些?

    • 抢占式调度模型:
      那个线程的优先级比较高,抢到的CPU时间片的概率就高一些/多一些。
      java采用的就是抢占式调度模型

    • 均分式调度模型:
      平均分配CPU时间片。每个线程占有的CPU时间片时间长度一样。
      平均分配,一切平等。
      有一些编程语言,线程调度模型采用的是这种方式。

    2.java中提供了哪些方法是和线程调度有关系的呢?

    2.1实例方法:

    方法名作用
    int getPriority()获得线程优先级
    void setPriority(int newPriority)设置线程优先级
    • 最低优先级1
    • 默认优先级是5
    • 最高优先级10

    优先级比较高的获取CPU时间片可能会多一些。(但也不完全是,大概率是多的。)

    2.2静态方法:

    方法名作用
    static void yield()让位方法,当前线程暂停,回到就绪状态,让给其它线程。

    yield()方法不是阻塞方法。让当前线程让位,让给其它线程使用。

    yield()方法的执行会让当前线程从“运行状态”回到“就绪状态”。

    注意:在回到就绪之后,有可能还会再次抢到

    2.3实例方法:

    方法名作用
    void join()将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束

    eg.

    class MyThread1 extends Thread {
    	public void doSome(){
    		MyThread2 t = new MyThread2();
    		t.join(); // 当前线程进入阻塞,t线程执行,直到t线程结束。当前线程才可以继续。
    	}
    }
    
    class MyThread2 extends Thread{
    
    }
    

    13、Java进程的优先级

    常量:

    常量名备注
    static int MAX_PRIORITY最高优先级(10)
    static int MIN_PRIORITY最低优先级(1)
    static int NORM_PRIORITY默认优先级(5)

    方法:

    方法名作用
    int getPriority()获得线程优先级
    void setPriority(int newPriority)设置线程优先级
    public class ThreadTest11 {
        public static void main(String[] args) {
            System.out.println("最高优先级:" + Thread.MAX_PRIORITY);//最高优先级:10
            System.out.println("最低优先级:" + Thread.MIN_PRIORITY);//最低优先级:1
            System.out.println("默认优先级:" + Thread.NORM_PRIORITY);//默认优先级:5
            
            // main线程的默认优先级是:5
            System.out.println(hread.currentThread().getName() + "线程的默认优先级是:" + currentThread.getPriority());
    
            Thread t = new Thread(new MyRunnable5());
            t.setPriority(10);
            t.setName("t");
            t.start();
    
            // 优先级较高的,只是抢到的CPU时间片相对多一些。
            // 大概率方向更偏向于优先级比较高的。
            for(int i = 0; i < 10000; i++){
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
        }
    }
    
    class MyRunnable5 implements Runnable {
        @Override
        public void run() {
            for(int i = 0; i < 10000; i++){
                System.out.println(Thread.currentThread().getName() + "-->" + i);
            }
        }
    }
    
    

    注意:

    • main线程的默认优先级是:5
    • 优先级较高的,只是抢到的CPU时间片相对多一些。大概率方向更偏向于优先级比较高的。

    14、关于线程的yield()方法

    方法名作用
    static void yield()让位,当前线程暂停,回到就绪状态,让给其它线程。

    eg.

    public class ThreadTest12 {
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable6());
            t.setName("t");
            t.start();
    
            for(int i = 1; i <= 10000; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    class MyRunnable6 implements Runnable {
    
        @Override
        public void run() {
            for(int i = 1; i <= 10000; i++) {
                //每100个让位一次。
                if(i % 100 == 0){
                    Thread.yield(); // 当前线程暂停一下,让给主线程。
                }
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    

    注意: 并不是每次都让成功的,有可能它又抢到时间片了。

    15、关于线程的join()方法

    方法名作用
    void join()将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束
    void join(long millis)接上条,等待该线程终止的时间最长为 millis 毫秒
    void join(long millis, int nanos)接第一条,等待该线程终止的时间最长为 millis 毫秒 + nanos 纳秒

    eg.

    public class ThreadTest13 {
        public static void main(String[] args) {
            System.out.println("main begin");
    
            Thread t = new Thread(new MyRunnable7());
            t.setName("t");
            t.start();
    
            //合并线程
            try {
                t.join(); // t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    
            System.out.println("main over");
        }
    }
    
    class MyRunnable7 implements Runnable {
    
        @Override
        public void run() {
            for(int i = 0; i < 10000; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    

    注意: 一个线程.join(),当前线程会进入”阻塞状态“。等待加入线程执行完!

    补充小知识:多线程并发环境下,数据的安全问题(重点)

    1.为什么这个是重点?

    以后在开发中,我们的项目都是运行在服务器当中,而服务器已经将线程的定义,线程对象的创建,线程的启动等,都已经实现完了。这些代码我们都不需要编写。

    最重要的是: 你要知道,你编写的程序需要放到一个多线程的环境下运行,你更需要关注的是这些数据在多线程并发的环境下是否是安全的。(重点:★★★★★

    2.什么时候数据在多线程并发的环境下会存在安全问题呢?★★★★★

    满足三个条件:

    1. 条件1:多线程并发
    2. 条件2:有共享数据
    3. 条件3:共享数据有修改的行为

    满足以上3个条件之后,就会存在线程安全问题。

    3.怎么解决线程安全问题呢?

    多线程并发的环境下,有共享数据,并且这个数据还会被修改,此时就存在线程安全问题,怎么解决这个问题?

    线程排队执行。(不能并发)。用排队执行解决线程安全问题。

    这种机制被称为:线程同步机制

    专业术语叫做线程同步,实际上就是线程不能并发了,线程必须排队执行。


    线程同步就是线程排队了,线程排队了就会 牺牲一部分效率 ,没办法,数据安全第一位,只有数据安全了,我们才可以谈效率。数据不安全,没有效率的事儿。


    4.两个专业术语:

    异步编程模型:
    线程t1和线程t2,各自执行各自的,t1不管t2,t2不管t1,谁也不需要等谁,这种编程模型叫做:异步编程模型。

    其实就是:多线程并发(效率较高。)

    异步就是并发。

    同步编程模型:
    线程t1和线程t2,在线程t1执行的时候,必须等待t2线程执行结束,或者说在t2线程执行的时候,必须等待t1线程执行结束,两个线程之间发生了等待关系,这就是同步编程模型。

    效率较低。线程排队执行。

    同步就是排队。

    16、线程安全

    16.1synchronized-线程同步

    线程同步机制的语法是:

    synchronized(){
    	// 线程同步代码块。
    }
    

    重点:
    synchronized后面小括号() 中传的这个“数据”是相当关键的。这个数据必须是 多线程共享 的数据。才能达到多线程排队。

    16.1.1 ()中写什么?

    那要看你想让哪些线程同步。

    假设t1、t2、t3、t4、t5,有5个线程,你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?

    你一定要在()中写一个t1 t2 t3共享的对象。而这个对象对于t4 t5来说不是共享的。

    这里的共享对象是:账户对象。
    账户对象是共享的,那么this就是账户对象!!!
    ()不一定是this,这里只要是多线程共享的那个对象就行。

    注意:
    在java语言中,任何一个对象都有“一把锁”,其实这把锁就是标记。(只是把它叫做锁。)
    100个对象,100把锁。1个对象1把锁。

    16.1.2 以下代码的执行原理?(★★★★★

    1、假设t1和t2线程并发,开始执行以下代码的时候,肯定有一个先一个后。

    2、假设t1先执行了,遇到了synchronized,这个时候自动找“后面共享对象”的对象锁
    找到之后,并占有这把锁,然后执行同步代码块中的程序,在程序执行过程中一直都是
    占有这把锁的。直到同步代码块代码结束,这把锁才会释放。

    3、假设t1已经占有这把锁,此时t2也遇到synchronized关键字,也会去占有后面
    共享对象的这把锁,结果这把锁被t1占有,t2只能在同步代码块外面等待t1的结束
    直到t1把同步代码块执行结束了,t1会归还这把锁,此时t2终于等到这把锁,然后
    t2占有这把锁之后,进入同步代码块执行程序。

    4、这样就达到了线程排队执行。

    重中之重:
    这个共享对象一定要选好了。这个共享对象一定是你需要排队
    执行的这些线程对象所共享的。

    class Account {
        private String actno;
        private double balance; //实例变量。
    
        //对象
        Object o= new Object(); // 实例变量。(Account对象是多线程共享的,Account对象中的实例变量obj也是共享的。)
    
        public Account() {
        }
    
        public Account(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
    
        public String getActno() {
            return actno;
        }
    
        public void setActno(String actno) {
            this.actno = actno;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        //取款的方法
        public void withdraw(double money){
            /**
             * 以下可以共享,金额不会出错
             * 以下这几行代码必须是线程排队的,不能并发。
             * 一个线程把这里的代码全部执行结束之后,另一个线程才能进来。
             */
            synchronized(this) {
            //synchronized(actno) {
            //synchronized(o) {
            
            /**
             * 以下不共享,金额会出错
             */
    		  /*Object obj = new Object();
    	        synchronized(obj) { // 这样编写就不安全了。因为obj2不是共享对象。
    	        synchronized(null) {//编译不通过
    	        String s = null;
    	        synchronized(s) {//java.lang.NullPointerException*/
                double before = this.getBalance();
                double after = before - money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setBalance(after);
            //}
        }
    }
    
    class AccountThread extends Thread {
        // 两个线程必须共享同一个账户对象。
        private Account act;
    
        // 通过构造方法传递过来账户对象
        public AccountThread(Account act) {
            this.act = act;
        }
    
        public void run(){
            double money = 5000;
            act.withdraw(money);
            System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
        }
    }
    
    public class Test {
        public static void main(String[] args) {
            // 创建账户对象(只创建1个)
            Account act = new Account("act-001", 10000);
            // 创建两个线程,共享同一个对象
            Thread t1 = new AccountThread(act);
            Thread t2 = new AccountThread(act);
    
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
        }
    }
    

    以上代码锁this、实例变量actno、实例变量o都可以!因为这三个是线程共享!

    16.1.3 在实例方法上可以使用synchronized

    synchronized出现在实例方法上,一定锁的是 this

    没得挑。只能是this。不能是其他的对象了。所以这种方式不灵活

    16.1.3.1 缺点

    synchronized出现在实例方法上,表示整个方法体都需要同步,可能会无故扩大同步的范围,导致程序的执行效率降低。所以这种方式不常用

    16.1.3.2 优点

    代码写的少了。节俭了。

    16.1.3.3 总结

    如果共享的对象就是this,并且需要同步的代码块是整个方法体,建议使用这种方式。、

    eg.

        public synchronized void withdraw(double money){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }
    

    16.1.4 在方法调用处使用synchronized

    eg.

        public void run(){
            double money = 5000;
            // 取款
            // 多线程并发执行这个方法。
            //synchronized (this) { //这里的this是AccountThread对象,这个对象不共享!
            synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
                act.withdraw(money);
            }
    
            System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
        }
    

    这种方式也可以,只不过扩大了同步的范围,效率更低了。

    17、Java中有三大变量?★★★★★

    • 实例变量:在中。
    • 静态变量:在方法区
    • 局部变量:在中。

    以上三大变量中:

    局部变量永远都不会存在线程安全问题。

    • 因为局部变量不共享。(一个线程一个栈。)
    • 局部变量在中。所以局部变量永远都不会共享。

    1. 实例变量在堆中,堆只有1个。
    2. 静态变量在方法区中,方法区只有1个。

    堆和方法区都是多线程共享的,所以可能存在线程安全问题。

    总结:

    • 局部变量+常量:不会有线程安全问题。
    • 成员变量(实例+静态):可能会有线程安全问题。

    18、以后线程安全和非线程安全的类如何选择?

    如果使用局部变量的话:

    建议使用:StringBuilder

    因为局部变量不存在线程安全问题。选择StringBuilder。

    StringBuffer效率比较低。

    反之:

    使用StringBuffer。

    • ArrayList是非线程安全的。
    • Vector是线程安全的。
    • HashMap HashSet是非线程安全的。
    • Hashtable是线程安全的。

    19、总结synchronized

    synchronized有三种写法:

    第一种:同步代码块

    灵活

    synchronized(线程共享对象){
    	同步代码块;
    }
    

    第二种:在实例方法上使用synchronized

    表示共享对象一定是 this 并且同步代码块是整个方法体

    第三种:在静态方法上使用synchronized

    表示找 类锁类锁永远只有1把

    就算创建了100个对象,那类锁也只有1把。

    注意区分:

    • 对象锁:1个对象1把锁,100个对象100把锁。
    • 类锁:100个对象,也可能只是1把类锁。

    20、我们以后开发中应该怎么解决线程安全问题?

    是一上来就选择线程同步吗?synchronized

    不是,synchronized会让程序的执行效率降低,用户体验不好。
    系统的用户吞吐量降低。用户体验差。在不得已的情况下再选择线程同步机制。

    • 第一种方案:尽量使用局部变量 代替实例变量静态变量”。

    • 第二种方案:如果必须是实例变量,那么可以考虑创建多个对象,这样实例变量的内存就不共享了。(一个线程对应1个对象,100个线程对应100个对象,对象不共享,就没有数据安全问题了。)

    • 第三种方案:如果不能使用局部变量,对象也不能创建多个,这个时候就只能选择synchronized了。线程同步机制

    21、死锁(DeadLock)

    死锁代码要会写。一般面试官要求你会写。
    只有会写的,才会在以后的开发中注意这个事儿。
    因为死锁很难调试。

    在这里插入图片描述

    /**
     * 比如:t1想先穿衣服在穿裤子
     *       t2想先穿裤子在传衣服
     * 此时:t1拿到衣服,t2拿到裤子;
     * 由于t1拿了衣服,t2找不到衣服;t2拿了裤子,t1找不到裤子
     * 就会导致死锁的发生!
     */
    public class Thread_DeadLock {
        public static void main(String[] args) {
            Dress dress = new Dress();
            Trousers trousers = new Trousers();
            //t1、t2共享dress和trousers。
            Thread t1 = new Thread(new MyRunnable1(dress, trousers), "t1");
            Thread t2 = new Thread(new MyRunnable2(dress, trousers), "t2");
            t1.start();
            t2.start();
        }
    }
    
    class MyRunnable1 implements Runnable{
        Dress dress;
        Trousers trousers;
    
        public MyRunnable1() {
        }
    
        public MyRunnable1(Dress dress, Trousers trousers) {
            this.dress = dress;
            this.trousers = trousers;
        }
    
        @Override
        public void run() {
            synchronized(dress){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (trousers){
                    System.out.println("--------------");
                }
            }
        }
    }
    
    class MyRunnable2 implements Runnable{
        Dress dress;
        Trousers trousers;
    
        public MyRunnable2() {
        }
    
        public MyRunnable2(Dress dress, Trousers trousers) {
            this.dress = dress;
            this.trousers = trousers;
        }
    
        @Override
        public void run() {
            synchronized(trousers){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (dress){
                    System.out.println("。。。。。。。。。。。。。。");
                }
            }
        }
    }
    
    class Dress{
    
    }
    
    class Trousers{
    
    }
    

    22、守护线程

    22.1java语言中线程分为两大类:

    • 一类是:用户线程
    • 一类是:守护线程后台线程

    其中具有代表性的就是:垃圾回收线程(守护线程)

    22.2守护线程的特点:

    一般守护线程是一个死循环所有的用户线程只要结束,守护线程自动结束

    注意:主线程main方法是一个用户线程。

    22.3守护线程用在什么地方呢?

    每天00:00的时候系统数据自动备份。
    这个需要使用到定时器,并且我们可以将定时器设置为守护线程。
    一直在那里看着,没到00:00的时候就备份一次。所有的用户线程如果结束了,守护线程自动退出,没有必要进行数据备份了。

    22.4方法

    方法名作用
    void setDaemon(boolean on)on为true表示把线程设置为守护线程

    eg.

    public class ThreadTest14 {
        public static void main(String[] args) {
            Thread t = new BakDataThread();
            t.setName("备份数据的线程");
    
            // 启动线程之前,将线程设置为守护线程
            t.setDaemon(true);
    
            t.start();
    
            // 主线程:主线程是用户线程
            for(int i = 0; i < 10; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class BakDataThread extends Thread {
        public void run(){
            int i = 0;
            // 即使是死循环,但由于该线程是守护者,当用户线程结束,守护线程自动终止。
            while(true){
                System.out.println(Thread.currentThread().getName() + "--->" + (++i));
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    

    23、定时器

    23.1定时器的作用:

    间隔特定的时间,执行特定的程序。

    eg.
    每周要进行银行账户的总账操作。
    每天要进行数据的备份操作。


    在实际的开发中,每隔多久执行一段特定的程序,这种需求是很常见的,那么在java中其实可以采用多种方式实现:

    1. 可以使用sleep方法,睡眠,设置睡眠时间,没到这个时间点醒来,执行任务。这种方式是最原始的定时器。(比较low)

    2. 在java的类库中已经写好了一个定时器:java.util.Timer,可以直接拿来用。
      不过,这种方式在目前的开发中也很少用,因为现在有很多高级框架都是支持定时任务的。

    在实际的开发中,目前使用较多的是Spring框架中提供的SpringTask框架,这个框架只要进行简单的配置,就可以完成定时器的任务。

    构造方法

    构造方法名备注
    Timer()创建一个定时器
    Timer(boolean isDaemon)isDaemon为true为守护线程定时器
    Timer(String name)创建一个定时器,其线程名字为name
    Timer(String name, boolean isDaemon)结合2、3

    方法

    方法名作用
    void schedule(TimerTask task, Date firstTime, long period)安排指定的任务在指定的时间开始进行重复的固定延迟执行
    void cancel()终止定时器

    24、使用定时器实现日志备份

    正常方式:

    class TimerTest01{
        public static void main(String[] args) {
            Timer timer = new Timer();
    //        Timer timer = new Timer(true);//守护线程
            String firstTimeStr = "2021-05-09 17:27:00";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date firstTime = sdf.parse(firstTimeStr);
                timer.schedule(new MyTimerTask(), firstTime, 1000 * 5);//每5s执行一次
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    
    class MyTimerTask extends TimerTask{
        @Override
        public void run() {
            Date d = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String time = sdf.format(d);
            System.out.println(time + ":备份日志一次!");
        }
    }
    

    匿名内部类方式:

    class TimerTest02{
        public static void main(String[] args) {
            Timer timer = new Timer();
            String firstTimeStr = "2021-05-09 17:56:00";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date firstTime = sdf.parse(firstTimeStr);
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        Date d = new Date();
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        String time = sdf.format(d);
                        System.out.println(time + ":备份日志一次!");
                    }
                }, firstTime, 1000 * 5);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    

    25、实现线程的第三种方式:实现Callable接口(JDK8新特性)

    这种方式实现的线程可以获取线程的返回值

    之前讲解的那两种方式是无法获取线程返回值的,因为run方法返回void

    任务需求:
    系统委派一个线程去执行一个任务,该线程执行完任务之后,可能会有一个执行结果,我们怎么能拿到这个执行结果呢?
    使用第三种方式:实现Callable接口方式。

    25.1优点

    可以获取到线程的执行结果。

    25.2缺点

    效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

    eg.

    public class ThreadTest15 {
        public static void main(String[] args) throws Exception {
    
            // 第一步:创建一个“未来任务类”对象。
            // 参数非常重要,需要给一个Callable接口实现类对象。
            FutureTask task = new FutureTask(new Callable() {
                @Override
                public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
                    // 线程执行一个任务,执行之后可能会有一个执行结果
                    // 模拟执行
                    System.out.println("call method begin");
                    Thread.sleep(1000 * 10);
                    System.out.println("call method end!");
                    int a = 100;
                    int b = 200;
                    return a + b; //自动装箱(300结果变成Integer)
                }
            });
    
            // 创建线程对象
            Thread t = new Thread(task);
    
            // 启动线程
            t.start();
    
            // 这里是main方法,这是在主线程中。
            // 在主线程中,怎么获取t线程的返回结果?
            // get()方法的执行会导致“当前线程阻塞”
            Object obj = task.get();
            System.out.println("线程执行结果:" + obj);
    
            // main方法这里的程序要想执行必须等待get()方法的结束
            // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
            // 另一个线程执行是需要时间的。
            System.out.println("hello world!");
        }
    }
    

    26、关于Object类的wait()、notify()、notifyAll()方法

    26.1方法

    方法名作用
    void wait()让活动在当前对象的线程无限等待(释放之前占有的锁)
    void notify()唤醒当前对象正在等待的线程(只提示唤醒,不会释放锁)
    void notifyAll()唤醒当前对象全部正在等待的线程(只提示唤醒,不会释放锁)

    26.2方法详解


    1. 第一:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方法是 Object类中自带 的。

    wait方法和notify方法不是通过线程对象调用
    不是这样的:t.wait(),也不是这样的:t.notify()…不对。


    1. 第二wait()方法作用
    Object o = new Object();
    
    o.wait();
    

    表示:
    让正在o对象上活动的线程进入等待状态,无期限等待,直到被唤醒为止。

    o.wait();方法的调用,会让“当前线程(正在o对象上活动的线程)”进入等待状态。


    1. 第三notify()方法作用
    Object o = new Object();
    
    o.notify();
    

    表示:
    唤醒正在o对象上等待的线程。


    1. 第四notifyAll() 方法 作用
    Object o = new Object();
    
    o.notifyAll();
    

    表示:
    这个方法是唤醒o对象上处于等待的所有线程

    26.3图文

    在这里插入图片描述

    26.4总结 ★★★★★(呼应生产者消费者模式)

    1、wait和notify方法不是线程对象的方法,是普通java对象都有的方法。

    2、wait方法和notify方法建立在 线程同步 的基础之上。因为多线程要同时操作一个仓库。有线程安全问题。

    3、wait方法作用:o.wait() 让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁

    4、notify方法作用:o.notify() 让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁

    27、生产者消费者模式(wait()和notify())

    27.1什么是“生产者和消费者模式”?

    • 生产线程负责生产,消费线程负责消费。
    • 生产线程和消费线程要达到均衡。
    • 这是一种特殊的业务需求,在这种特殊的情况下需要使用wait方法和notify方法

    27.2模拟一个业务需求

    模拟这样一个需求:

    • 仓库我们采用List集合。

    • List集合中假设只能存储1个元素。

    • 1个元素就表示仓库了。

    • 如果List集合中元素个数是0,就表示仓库了。

    • 保证List集合中永远都是最多存储1个元素。

    • 必须做到这种效果:生产1个消费1个

    27.3图文

    在这里插入图片描述

    eg.

    使用wait方法和notify方法实现“生产者和消费者模式”

    public class ThreadTest16 {
        public static void main(String[] args) {
            // 创建1个仓库对象,共享的。
            List list = new ArrayList();
            // 创建两个线程对象
            // 生产者线程
            Thread t1 = new Thread(new Producer(list));
            // 消费者线程
            Thread t2 = new Thread(new Consumer(list));
    
            t1.setName("生产者线程");
            t2.setName("消费者线程");
    
            t1.start();
            t2.start();
        }
    }
    
    // 生产线程
    class Producer implements Runnable {
        // 仓库
        private List list;
    
        public Producer(List list) {
            this.list = list;
        }
        @Override
        public void run() {
            // 一直生产(使用死循环来模拟一直生产)
            while(true){
                // 给仓库对象list加锁。
                synchronized (list){
                    if(list.size() > 0){ // 大于0,说明仓库中已经有1个元素了。
                        try {
                            // 当前线程进入等待状态,并且释放Producer之前占有的list集合的锁。
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 程序能够执行到这里说明仓库是空的,可以生产
                    Object obj = new Object();
                    list.add(obj);
                    System.out.println(Thread.currentThread().getName() + "--->" + obj);
                    // 唤醒消费者进行消费
                    list.notifyAll();
                }
            }
        }
    }
    
    // 消费线程
    class Consumer implements Runnable {
        // 仓库
        private List list;
    
        public Consumer(List list) {
            this.list = list;
        }
    
        @Override
        public void run() {
            // 一直消费
            while(true){
                synchronized (list) {
                    if(list.size() == 0){
                        try {
                            // 仓库已经空了。
                            // 消费者线程等待,释放掉list集合的锁
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    // 程序能够执行到此处说明仓库中有数据,进行消费。
                    Object obj = list.remove(0);
                    System.out.println(Thread.currentThread().getName() + "--->" + obj);
                    // 唤醒生产者生产。
                    list.notifyAll();
                }
            }
        }
    }
    

    注意:
    生产者消费者模式貌似只能使用wait()和notify()实现!

    附录:测试代码(可不看)

    Thread

    package javase;
    
    
    import java.text.ParseException;
    import java.text.SimpleDateFormat;
    import java.util.Date;
    import java.util.*;
    import java.util.concurrent.Callable;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.FutureTask;
    
    public class ThreadTest {
        public static void main(String[] args) {
    
        }
    }
    
    //创建线程的第一种方法:继承Thread类
    class ThreadTest01{
        public static void main(String[] args) {
            MyThread01 t = new MyThread01();
            t.setName("t");
            t.start();//启动线程
            for (int i = 0; i < 1000; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    class MyThread01 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    //创建线程的第二种方法:实现Runnable接口
    class ThreadTest02{
        public static void main(String[] args) {
            Thread t = new Thread(new MyRunnable01(), "t");//创建线程并设置名字
            t.start();
            for (int i = 0; i < 1000; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    // 这并不是一个线程类,是一个可运行的类。它还不是一个线程。
    class MyRunnable01 implements Runnable{
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    //创建线程的第二种方法:实现Runnable接口(采用匿名内部类)
    class ThreadTest03{
        public static void main(String[] args) {
            //匿名内部类
            Thread t = new Thread(new Runnable() {
                @Override
                public void run() {
                    for (int i = 0; i < 1000; i++){
                        System.out.println(Thread.currentThread().getName() + "--->" + i);
                    }
                }
            });
    
            t.setName("t");
            t.start();
            for (int i = 0; i < 1000; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    /**
     * Thread.currentThread()获取当前线程对象(静态方法)
     * 线程.getName()获取当前线程名字
     * 线程.setName()设置当前线程名字
     */
    class ThreadTest04{
        public static void main(String[] args) {
            System.out.println(Thread.currentThread().getName());//当前线程名字 main
            MyThread01 t1 = new MyThread01();
            MyThread01 t2 = new MyThread01();
            t1.setName("t1");
            t2.setName("t2");
            t1.start();
            t2.start();
            for (int i = 0; i < 1000; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    //sleep(long millis)(静态方法)
    class ThreadTest05{
        public static void main(String[] args) {
            for (int i = 0; i < 10; i++){
                try {
                    Thread.sleep(1000);//睡眠1s
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    //interrupt()中断正在睡眠的线程(不推荐使用,了解即可)
    class ThreadTest06 {
        public static void main(String[] args) {
            MyThread02 t = new MyThread02();
            t.setName("t");
            t.start();
    
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello world");
            t.interrupt();
        }
    }
    
    class MyThread02 extends Thread{
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() + "--->begin" );
            try {
                Thread.sleep(1000 * 60 * 60 * 24 * 365);//睡一年
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "--->end" );
        }
    }
    
    //stop()终止一个线程执行(不推荐使用,可能导致数据丢失)
    class ThreadTest07{
        public static void main(String[] args) {
            MyThread03 t = new MyThread03();
            t.setName("t");
            t.start();
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            t.stop();
        }
    }
    
    class MyThread03 extends Thread{
        @Override
        public void run() {
            for (int i = 0; i < 100; i++){
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    //合理终止一个线程:设置一个标记
    class ThreadTest08{
        public static void main(String[] args) {
            MyThread04 t = new MyThread04();
            t.setName("t");
            t.start();
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("hello world");
            // 终止线程
            // 你想要什么时候终止t的执行,那么你把标记修改为false,就结束了。
            t.flag = true;
        }
    }
    
    class MyThread04 extends Thread{
        boolean flag = false;
        @Override
        public void run() {
            if (this.flag){
                return ;
            }
            for (int i = 0; i < 1000; i++){
                if (this.flag){//由于一个线程一直运行此程序,要是判断在外面只会在启动线程时判断并不会结束,因此需要每次循环判断一下标记。
                    /**
                     * 这里可以保存东西
                     */
                    return ;
                }
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    /*//MyThread04另一种写法
    class MyThread04 extends Thread{
        boolean flag = false;
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++){
                if (!this.flag){
                    System.out.println(Thread.currentThread().getName() + "--->" + i);
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }else{
                    return;
                }
    
            }
        }
    }*/
    
    /**关于线程的优先级
     *getPriority()获得线程优先级
     *setPriority()设置线程优先级
     */
    class ThreadTest09{
        public static void main(String[] args) {
            System.out.println("最高优先级:" + Thread.MAX_PRIORITY);//最高优先级:10
            System.out.println("最低优先级:" + Thread.MIN_PRIORITY);//最低优先级:1
            System.out.println("默认优先级:" + Thread.NORM_PRIORITY);//默认优先级:5
    
            MyThread01 t1 = new MyThread01();
            MyThread01 t2 = new MyThread01();
            MyThread01 t3 = new MyThread01();
            t1.setName("t1");
            t2.setName("t2");
            t3.setName("t3");
            t1.setPriority(Thread.MAX_PRIORITY);
            t2.setPriority(Thread.MIN_PRIORITY);
            t3.setPriority(Thread.NORM_PRIORITY);
            t1.start();
            t2.start();
            t3.start();
    
            try {
                Thread.sleep(1000 * 5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("t1的优先级:" + t1.getPriority());//t1的优先级:10
            System.out.println("t2的优先级:" + t2.getPriority());//t2的优先级:1
            System.out.println("t3的优先级:" + t3.getPriority());//t3的优先级:5
        }
    }
    
    //yield()让位,当前线程暂停,回到就绪状态,让给其它线程(静态方法)
    class ThreadTest10{
        public static void main(String[] args) {
            Thread t1 = new Thread(new MyRunnable02(), "t1");
            Thread t2 = new Thread(new MyRunnable02(), "t2");
            t1.start();
            t2.start();
        }
    }
    
    class MyRunnable02 implements Runnable{
    
        @Override
        public void run() {
            for (int i = 0; i < 1000; i++){
                //每100个让位一次。
                if (i % 100 == 0){
                    Thread.yield();// 当前线程暂停一下,让给主线程。
                }
                System.out.println(Thread.currentThread().getName() + "--->" + i);
            }
        }
    }
    
    //join()线程合并。将一个线程合并到当前线程中,当前线程受阻塞,加入的线程执行直到结束。
    class ThreadTest11{
        public static void main(String[] args) {
            System.out.println("main begin");
            MyThread01 t1 = new MyThread01();
            t1.setName("t1");
            t1.start();
            try {
                t1.join();//t合并到当前线程中,当前线程受阻塞,t线程执行直到结束。
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println("main end");
        }
    }
    
    //守护线程
    class ThreadTest12{
        public static void main(String[] args) {
            MyThread05 t = new MyThread05();
            t.setName("t");
            t.setDaemon(true);//设置守护线程
            t.start();
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "--->" + i);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    class MyThread05 extends Thread{
        @Override
        public void run() {
            int i = 0;
            while (true){
                System.out.println(Thread.currentThread().getName() + "--->" + i++);
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    
    //使用定时器实现日志备份
    class TimerTest01{
        public static void main(String[] args) {
            Timer timer = new Timer();
    //        Timer timer = new Timer(true);//守护线程
            String firstTimeStr = "2021-05-09 17:27:00";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date firstTime = sdf.parse(firstTimeStr);
                timer.schedule(new MyTimerTask(), firstTime, 1000 * 5);//每5s执行一次
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    
    class MyTimerTask extends TimerTask{
        @Override
        public void run() {
            Date d = new Date();
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            String time = sdf.format(d);
            System.out.println(time + ":备份日志一次!");
        }
    }
    
    class TimerTest02{
        public static void main(String[] args) {
            Timer timer = new Timer();
            String firstTimeStr = "2021-05-09 17:56:00";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
            try {
                Date firstTime = sdf.parse(firstTimeStr);
                timer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        Date d = new Date();
                        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
                        String time = sdf.format(d);
                        System.out.println(time + ":备份日志一次!");
                    }
                }, firstTime, 1000 * 5);
            } catch (ParseException e) {
                e.printStackTrace();
            }
        }
    }
    
    //实现线程的第三种方式:实现Callable接口
    class ThreadTest13{
        public static void main(String[] args) {
            System.out.println("main begin");
            FutureTask task = new FutureTask(new MyCallable());
            Thread t = new Thread(task, "t");
            t.start();
            try {
                Object o = task.get();//会导致main线程阻塞
                System.out.println("task线程运行结果:" + o);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } catch (ExecutionException e) {
                e.printStackTrace();
            }
            System.out.println("main end");
        }
    }
    
    class MyCallable implements Callable{
        @Override
        public Object call() throws Exception {//相当于run()方法,不过这个有返回值
            System.out.println("MyCallable begin");
            Thread.sleep(1000 * 5);
            System.out.println("MyCallable end");
            return 1;
        }
    }
    
    /**
     * 生产者消费者模式
     */
    class Thread14{
        public static void main(String[] args) {
            List<Object> list = new ArrayList<>();
            Thread producer = new Producer(list);
            Thread consumer = new Consumer(list);
            producer.setName("生产者线程");
            consumer.setName("消费者线程");
            producer.start();
            try {
                Thread.sleep(1000);//睡眠1s保证producer线程先执行
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            consumer.start();
        }
    }
    
    /**
     * Producer类和Consumer类run()方法没有synchronized
     * 如果生产者线程和消费者线程同时进入run()方法就会引起
     * java.lang.IllegalMonitorStateException异常
     * (两个线程无限等待)
     */
    class Producer extends Thread{
        List<Object> list;
    
        public Producer() {
        }
    
        public Producer(List<Object> list) {
            this.list = list;
        }
    
        @Override
        public void run() {
            while(true){
                synchronized (list) {//this是当前对象,锁的是list,不是当前对象
                    if (list.size() > 0) {
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Object obj = new Object();
                    list.add(obj);
                    System.out.println(Thread.currentThread().getName() + "生产:" + obj);
                    list.notifyAll();
                }
            }
        }
    }
    
    class Consumer extends Thread{
        List<Object> list;
    
        public Consumer() {
        }
    
        public Consumer(List<Object> list) {
            this.list = list;
        }
    
        @Override
        public void run() {
            while (true){
                synchronized (list) {
                    if (list.size() == 0) {
                        try {
                            list.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    Object obj = list.get(0);
                    list.remove(0);
                    System.out.println(Thread.currentThread().getName() + "消费:" + obj);
                    list.notifyAll();
                }
            }
        }
    }
    
    /**
     * 循环模拟生产者消费者模式
     */
    class PC{
        public static void main(String[] args) {
            List<Object> list = new ArrayList<>();
    
            while (true){
                if (list.size() > 1){
                    continue;
                }else{
                    Object o = new Object();
                    list.add(o);
                    System.out.println("生产:" + o);
                }
    
                if (list.size() == 0){
                    continue;
                }else{
                    Object o = list.get(0);
                    list.remove(0);
                    System.out.println("消费:" + o);
                }
            }
        }
    }
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    
    

    ThreadSafe-1

    package javase;
    
    /**
     *     不使用线程同步机制,多线程对同一个账户进行取款,出现线程安全问题。
     */
    public class ThreadSafe01 {
        public static void main(String[] args) {
            Account01 act = new Account01("act-001", 10000);
            Thread t1 = new Thread(new AccountRunnable01(act), "t1");
            Thread t2 = new Thread(new AccountRunnable01(act), "t2");
            t1.start();
            t2.start();
        }
    }
    
    class Account01{
        private String actno;
        private double balance;
    
        public Account01() {
        }
    
        public Account01(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
    
        public String getActno() {
            return actno;
        }
    
        public void setActno(String actno) {
            this.actno = actno;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        /**
         * // t1和t2并发这个方法。。。。(t1和t2是两个栈。两个栈操作堆中同一个对象。)
         * @param money
         */
        public void withdraw(double money){
            /*this.setBalance(this.getBalance() - money);//这样写不会出问题*/
    
            //以下代码,只要t1没有执行完this.setBalance(after);,t2进来执行都会导致数据错误!
            double before = this.getBalance();
            double after = before - money;
            //模拟网络延迟
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }
    }
    
    class AccountRunnable01 implements Runnable{
        private Account01 act;
    
        public AccountRunnable01() {
        }
    
        public AccountRunnable01(Account01 act) {
            this.act = act;
        }
    
        public Account01 getAct() {
            return act;
        }
    
        public void setAct(Account01 act) {
            this.act = act;
        }
    
        @Override
        public void run() {
            act.withdraw(5000);
            System.out.println(Thread.currentThread().getName() + "取款5000,还剩余额:" + act.getBalance());
        }
    }
    

    ThreadSafe-2

    package javase;
    
    /**
     * 使用线程同步机制,解决线程安全问题。
     */
    public class ThreadSafe02 {
        public static void main(String[] args) {
            Account02 act = new Account02("act-001", 10000);
            Thread t1 = new Thread(new AccountRunnable02(act), "t1");
            Thread t2 = new Thread(new AccountRunnable02(act), "t2");
            t1.start();
            t2.start();
        }
    }
    
    class Account02{
        private String actno;
        private double balance;
    
        Object o = new Object();
    
        public Account02() {
        }
    
        public Account02(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
    
        public String getActno() {
            return actno;
        }
    
        public void setActno(String actno) {
            this.actno = actno;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        public void withdraw(double money){
            /**
             * 以下可以共享,金额不会出错
             */
            synchronized(this) {
            //synchronized(actno) {
            //synchronized(o) {
            /**
             * 以下不共享,金额会出错
             */
    /*        Object obj = new Object();
            synchronized(obj) {
            synchronized(null) {//编译不通过
            String s = null;
            synchronized(s) {//java.lang.NullPointerException*/
            double before = this.getBalance();
                double after = before - money;
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                this.setBalance(after);
            }
        }
    }
    
    class AccountRunnable02 implements Runnable{
        private Account02 act;
    
        public AccountRunnable02() {
        }
    
        public AccountRunnable02(Account02 act) {
            this.act = act;
        }
    
        public Account02 getAct() {
            return act;
        }
    
        public void setAct(Account02 act) {
            this.act = act;
        }
    
        @Override
        public void run() {
            //synchronized (act) { 这种方式也可以,只不过扩大了同步的范围,效率更低了
                act.withdraw(5000);
            //}
            System.out.println(Thread.currentThread().getName() + "取款5000,还剩余额:" + act.getBalance());
        }
    }
    

    ThreadSafe-3

    package javase;
    
    public class ThreadSafe03 {
        public static void main(String[] args) {
            Account03 act = new Account03("act-001", 10000);
            Thread t1 = new Thread(new AccountRunnable03(act), "t1");
            Thread t2 = new Thread(new AccountRunnable03(act), "t2");
            t1.start();
            t2.start();
        }
    }
    
    class Account03{
        private String actno;
        private double balance;
    
        public Account03() {
        }
    
        public Account03(String actno, double balance) {
            this.actno = actno;
            this.balance = balance;
        }
    
        public String getActno() {
            return actno;
        }
    
        public void setActno(String actno) {
            this.actno = actno;
        }
    
        public double getBalance() {
            return balance;
        }
    
        public void setBalance(double balance) {
            this.balance = balance;
        }
    
        /**
         * synchronized出现在实例方法上,一定锁的是this。
         * @param money
         */
        public synchronized void withdraw(double money){
            double before = this.getBalance();
            double after = before - money;
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            this.setBalance(after);
        }
    }
    
    class AccountRunnable03 implements Runnable{
        private Account03 act;
    
        public AccountRunnable03() {
        }
    
        public AccountRunnable03(Account03 act) {
            this.act = act;
        }
    
        public Account03 getAct() {
            return act;
        }
    
        public void setAct(Account03 act) {
            this.act = act;
        }
    
        @Override
        public void run() {
            act.withdraw(5000);
            System.out.println(Thread.currentThread().getName() + "取款5000,还剩余额:" + act.getBalance());
        }
    }
    

    DeadLock

    package javase;
    
    /**
     * 比如:t1想先穿衣服在穿裤子
     *       t2想先穿裤子在传衣服
     * 此时:t1拿到衣服,t2拿到裤子;
     * 由于t1拿了衣服,t2找不到衣服;t2拿了裤子,t1找不到裤子
     * 就会导致死锁的发生!
     */
    public class Thread_DeadLock {
        public static void main(String[] args) {
            Dress dress = new Dress();
            Trousers trousers = new Trousers();
            //t1、t2共享dress和trousers。
            Thread t1 = new Thread(new MyRunnable1(dress, trousers), "t1");
            Thread t2 = new Thread(new MyRunnable2(dress, trousers), "t2");
            t1.start();
            t2.start();
        }
    }
    
    class MyRunnable1 implements Runnable{
        Dress dress;
        Trousers trousers;
    
        public MyRunnable1() {
        }
    
        public MyRunnable1(Dress dress, Trousers trousers) {
            this.dress = dress;
            this.trousers = trousers;
        }
    
        @Override
        public void run() {
            synchronized(dress){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (trousers){
                    System.out.println("--------------");
                }
            }
        }
    }
    
    class MyRunnable2 implements Runnable{
        Dress dress;
        Trousers trousers;
    
        public MyRunnable2() {
        }
    
        public MyRunnable2(Dress dress, Trousers trousers) {
            this.dress = dress;
            this.trousers = trousers;
        }
    
        @Override
        public void run() {
            synchronized(trousers){
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                synchronized (dress){
                    System.out.println("。。。。。。。。。。。。。。");
                }
            }
        }
    }
    
    class Dress{
    
    }
    
    class Trousers{
    
    }
    
    展开全文
  • 轻松解决普通poi形式导出Excel的中出现的栈溢出问题,此资源可实现千万级数据分批导出csv文件,csv大数据量导出(千万级别,不会内存溢出),多线程导出 ,生产环境已经很稳定的使用着
  • Java创建多线程的五种方法

    千次阅读 2021-12-31 15:01:09
    Java创建多线程的五种方法总结

    Java创建多线程的五种方法

    • 仅作为Java编程的交流和学习,如有问题还望不吝赐教。欢迎补充和交流,转载请注明!

    (一)继承Thread类

    1.实现描述

    • 通过继承Thread并且重写其run( ),run方法中定义需要执行的任务。创建后的子类通过调用start( )方法即可执行线程方法。
    • 通过继承Thread实现的线程类,多个线程间无法共享线程类的实例变量。需要创建不同Thread对象,自然不共享资源。

    2.具体步骤

    1)定义UserThread类,继承Thread类
    2)重写run( )方法
    3)创建UserThread对象
    4)调用start( )方法

    3.代码实现

    在这里插入图片描述
    在这里插入图片描述
    执行结果

    4.注意事项

    • 数据资源不共享,多个线程分别完成自己的任务。比如三个售票窗口同时售票,各自卖各自的票,会出现三个售票窗口出售同一张票的问题。

    (二)实现Runnable接口

    1.实现描述

    • 需要先定义一个类实现Runnable接口并重写该接口的run( )方法,此run方法是线程执行体。接着创建 Runnable实现类的对象,作为创建Thread对象的参数target,此Thread对象才是真正的线程对象。
    • 利用实现Runnable接口的线程类创建对象,可以实现线程之间的资源共享。

    2.具体步骤

    1)定义一个UserRun类,实现Runnble接口
    2)重写run( )方法
    3)创建UserRun类的对象
    4)创建Thread类的对象,UserRun类的对象作为Thread类构造方法的参数
    5)启动线程

    3.代码实现

    在这里插入图片描述
    在这里插入图片描述
    输出结果

    4.注意事项

    • 数据资源共享,多个线程共同完成一个任务(多个线程共享了创建线程对象的资源)。比如三个售票窗口(三个线程)同时售票(MyThread类中的ticket),三个线程共同使用资源。

    (三)实现Callable接口

    1.实现描述

    • Callable接口如同Runable接口的升级版,其提供的call( )方法将作为线程的执行体,同时允许有返回值。
    • Callable对象不能直接作为Thread对象的target,因为Callable接口是 Java5 新增的接口,不是Runnable接口的子接口。
    • 对于这个问题的解决方案,就引入 Future接口,此接口可以接受call( )的返回值,RunnableFuture接口是Future接口和Runnable接口的子接口,可以作为Thread对象的target。

    2.具体步骤

    1)定义类UserCallable,实现Callable接口
    2)重写call( )方法
    3)创建UserCallable的对象
    4)创建RunnableFuture接口的子类FutureTask的对象,构造函数的参数是UserCallable的对象
    5)创建Thread类的对象,构造函数的参数是FutureTask的对象
    6)启动线程

    3.代码实现

    在这里插入图片描述
    在这里插入图片描述
    输出和结果

    4.注意事项

    • 数据资源共享,多个线程共同完成一个任务(多个线程共享了创建线程对象的资源)。比如三个售票窗口(三个线程)同时售票(MyThread类中的ticket),三个线程共同使用资源。同时,线程调用完毕后还会带有返回值

    (四)继承TimerTask类

    1.实现描述

    • 定时器类Timer和TimerTask可以做为实现线程的另一种方式。
    • Timer是一种线程设施,用于安排以后在后台线程中执行的任务。可安排任务执行一次,或者定期重复执行,可以看成一个定时器,可以调度TimerTask。
    • TimerTask是一个抽象类,实现了Runnable接口,所以具备了多线程的能力。

    2.具体步骤

    1)定义类UserTimerTask,继承抽象类TimerTask
    2)创建UserTask类的对象
    3)创建Timer类的对象,设置任务的执行策略

    3.代码实现

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

    输出结果

    4.注意事项

    • 定时器类创建线程更多的是用于定时任务的处理,并且各线程间数据资源不共享,多个线程分别完成自己的任务。

    (五)通过线程池启动多线程

    1.实现描述

    • 通过Executors 的工具类可以创建线程池。
    • 提高系统响应速度,当有任务到达时,通过复用已存在的线程,无需等待新线程的创建便能立即执行。
    • 降低系统资源消耗,通过重用已存在的线程降低线程创建和销毁造成消耗。
    • 方便线程并发数的管控。因为线程若是无限制的创建,可能会导致内存占用过多而产生OOM,并目会造成CPU过度切换。

    2.实现方法

    1) FixThreadPool(int n)固定大小的线程池

    (1)具体步骤

    ① 通过Executors.newFixedThreadPool(5)创建固定大小的线程池
    ② 重写Runnable类的run( )方法,并使用线程池执行任务
    ③ Shutdown( )关闭线程池

    (2)代码实现

    在这里插入图片描述
    执行结果

    (3)注意事项
    • 创建固定大小的线程池,可实现数据资源共享,多个线程共同完成一个任务。

    2)SingleThreadExecutor( )单线程池

    (1)具体步骤

    ① 通过Executors.newSingleThreadExecutor( )创建单线程池
    ② 重写Runnable类的run( )方法,并使用线程池执行任务
    ③ Shutdown( )关闭线程池

    (2)代码实现

    在这里插入图片描述
    执行结果

    (3)注意事项
    • 线程池仅创建一个线程执行任务。

    3)CachedThreadPool( )缓存线程池

    (1)具体步骤

    ① 通过Executors.newCachedThreadPool( )创建尽可能多的线程池
    ② 重写Runnable类的run( )方法,并使用线程池执行任务
    ③ Shutdown( )关闭线程池

    (2)代码实现

    在这里插入图片描述
    执行结果

    (3)注意事项
    • 该方法会创建尽可能多的线程来完成任务,如案例中虽然只有10张票,但线程池却生成了至少12个线程。

    4)ScheduledThreadPool(int n)定时周期性线程池

    (1)具体步骤

    ① 通过Executors.newScheduledThreadPool(5)创建固定核心线程数(最小维护的线程数,线程创建后不会被回收)的线程池,线程按计划定期执行。
    ② 重写Runnable类的run( )方法,并使用线程池执行任务
    ③ Shutdown( )关闭线程池

    (2)代码实现

    在这里插入图片描述
    执行结果

    (3)注意事项
    • 创建一个周期性的线程池,支持定时及周期性执行任务(第一个时间参数是执行延迟时间,第二个参数是执行间隔时间)。

    5)WorkStealingPool( )新线程池类ForkJoinPool的扩展

    (1)具体步骤

    ① 通过Executors.newWorkStealingPool( )创建线程池
    ② 重写Runnable类的run( )方法,通过Thread类的对象调用Runnable类的对象,使用线程池执行任务
    ③ Sleep( )让主线程等待子线程执行完毕,也可以使用计数器的方式
    ④ Shutdown( )关闭线程池

    (2)代码实现

    在这里插入图片描述
    执行结果

    (3)注意事项
    • 因为每一个线程都有一个自己的任务队列,因为任务有多有少,可能造成CPU负载不均衡。通过本方法可以有效利用多核CPU优势,少任务的线程可以通过“窃取”任务较多的线程的任务,从而均衡各CPU任务的执行情况。

    欢迎补充和交流,转载请注明!

    展开全文
  • Java 实现多线程的四种方式

    万次阅读 2020-09-19 22:06:34
    Java 中实现多线程一共有四种方式: 继承 Thread 类 实现 Runnable 接口 实现 Callable 接口 线程池 下面我将对这四种方式进行入门级的解析和演示。 一、继承 Thread 类 通过继承 Thread 类实现多线程的步骤...
  • Java多线程设计模式_清晰完整PDF版

    千次下载 热门讨论 2015-04-22 17:37:18
    Java多线程设计模式_清晰完整PDF版,带有源码。绝对的清晰版,并且内容完整。
  • java多线程执行任务(工具)

    千次阅读 2022-03-09 14:54:09
    在项目开发的过程中经常会碰到多线程执行任务,每次用线程池实现时,由于每次的需求都有所差别有时是所有任务同时执行有时是分批次执行有时还需要知道所有任务什么时候执行完。今天闲着写了一个通用的多线程执行工具...
  • java多线程设计模式详解(PDF及源码)

    热门讨论 2014-05-07 20:54:32
    java多线程设计模式详解 (PDF及源码)
  • Java 多线程(超详细)

    千次阅读 2021-01-12 21:14:38
    多线程学习思路:为什么学习线程?为了解决CPU利用率问题,提高CPU利用率。 =》 什么是进程?什么是线程? =》 怎么创建线程?有哪几种方式?有什么特点? =》 分别怎么启动线程? =》 多线程带来了数据安全问题,该...
  • java多线程小游戏

    热门讨论 2012-11-30 17:09:48
    java多线程小游戏
  • Java多线程】停止线程

    万次阅读 2021-10-03 22:37:21
    Java中有以下3种方法可以终止正在进行的线程: 1)使用退出标志,使线程正常退出,也就是当run方法完成后线程终止。 2)使用stop方法强行终止线程,但是不推荐使用这个方法,因为stop和suspend及resum
  • java多线程之volatile详解     什么是volatile volatile是JVM提供的轻量级同步机制 好,开始讲大家看不懂的东西了! volatile有三大特性: 保证可见性 不保证原子性 禁止指令重排 傻了吧,这他妈都是些什么jb...
  • Java 多线程编程基础(详细)

    万次阅读 多人点赞 2020-11-03 17:36:30
    Java多线程编程基础进程与线程多线程实现Thread类实现多线程Runnable接口实现多线程Callable接口实现多线程多线程运行状态多线程常用操作方法线程的命名和获取线程休眠线程中断线程强制执行线程让步线程优先级设定...
  • Java多线程面试题(面试必备)

    万次阅读 多人点赞 2020-05-26 01:15:38
    文章目录一、多线程基础基础知识1. 并发编程1.1 并发编程的优缺点1.2 并发编程的三要素1.3 并发和并行有和区别1.4 什么是多线程多线程的优劣?2. 线程与进程2.1 什么是线程与进程2.2 线程与进程的区别2.3 用户线程...
  • JAVA多线程是什么

    千次阅读 2020-08-31 23:53:23
    一、 什么是多线程: 我们现在所使用操作系统都是多任务操作系统(早期使用的DOS操作系统为单任务操作系统),多任务操作指在同一时刻可以同时做多件事(可以同时执行多个程序)。 多进程:每个程序都是一个进程,在操作...
  • 然后网上都说各种加索引,加索引貌似是有查询条件时在某个字段加索引比较快一些,但是毕竟是人家的库不能瞎动,再者说了,数据量偏大一点的,条件加上也还有好多数据怎么办,我想到了多线程的方式,话不多说,开始弄...
  • java使用多线程查询大批量数据

    千次阅读 2021-03-08 08:59:40
    前言在某些时候,一旦单表数据量过大,查询数据的时候就会变得...这个查询的过程,数据量一旦过大,单线程查询数据将会成为瓶颈,下面尝试使用多线程来尝试查询一张数据量较大的表由于代码逻辑不是很难,直接上代码...
  • Java多线程

    千次阅读 多人点赞 2019-07-17 18:12:57
    所谓多线程,就是说一个应用程序有多条执行路径,每当我们打开一个应用程序...在Java中有两种方式实现多线程分别是继承Thread类和实现Runnable接口 目录 一、继承Thread类 二、实现Runnable接口 三、继承Thread...
  • java使用多线程导出excel

    万次阅读 热门讨论 2019-12-11 18:55:42
    由于单表的数据量特别大,发现在最终导出excel的时候,由于数量太大,导出速度特别慢,想了一些办法,不管使用何种API,单线程始终是操作的瓶颈,因此最终考虑使用多线程进行改善 总体思路: 1、数据总量分段 2、每...
  • 万字图解Java多线程

    万次阅读 多人点赞 2020-09-06 14:45:07
    java多线程我个人觉得是javaSe中最难的一部分,我以前也是感觉学会了,但是真正有多线程的需求却不知道怎么下手,实际上还是对多线程这块知识了解不深刻,不知道多线程api的应用场景,不知道多线程的运行流程等等,...
  • 多线程带来的问题 为什么需要多线程 其实说白了,时代变了,现在的机器都是多核的了,为了榨干机器最后的性能我们引入单线程。 为了充分利用CPU资源,为了提高CPU的使用率,采用多线程的方式去同时完成几件事情而不...
  • java for循环内执行多线程

    千次阅读 2021-03-23 22:41:40
    一、java多线程来加快循环效率 import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class tset...
  • Java多线程学习(吐血超详细总结)

    万次阅读 多人点赞 2015-03-14 13:13:17
    本文主要讲了java多线程的使用方法、线程同步、线程数据传递、线程状态及相应的一些线程函数用法、概述等。
  • Java多线程(三)线程的调度

    万次阅读 2021-03-04 13:24:49
    前提: 如果一个进程中同时三个线程,那么谁先谁后呢? 代码实现案例: /** * 测试线程的执行顺序 */ public class TestThreadSort implements Runnable{ public static void main(String[] args) { //使用...
  • Java多线程Java多线程技能

    万次阅读 2021-10-02 20:18:56
    1. 进程和多线程的概念及线程的优点 提到多线程这个技术就不得不提及“进程”这个概念,在“百度百科”中对进程的解释如下: 进程(Process)是计算机中的程序关于某数据集合上的一次运行活动,是系统进行资源...
  • java获取多线程返回值

    千次阅读 2019-10-30 08:44:43
    对于多线程大家都不陌生,可以提高咱们程序的执行效率,但是各线程之间都是独立运行,如何来获取各个线程的数据并集中处理呢?废话少说,下面上代码。 1.首先多线程要实现Callable接口,记住是Callable,一定要加上...
  • Java创建多线程的8种方式

    万次阅读 多人点赞 2018-07-21 21:16:51
    Java创建启动线程的多种方式 1、继承Thread类,重写run()方法 2、实现Runnable接口,重写run() 3、匿名内部类的方式 4、带返回值的线程(实现implements Callable&lt;返回值类型&gt;)————以上3...
  • java多线程-学习总结(完整版)

    千次阅读 多人点赞 2020-12-04 00:02:16
    java多线程 线程和进程 线程的生命周期 新建New 就绪&运行 Runable&Running 阻塞Blocked 等待 waiting 计时等待Time waiting 销毁Terminated 线程池概念和多线程使用场景 线程池的参数解析 线程池阻塞队列...
  • Java使用多线程异步执行批量更新操作

    千次阅读 热门讨论 2021-01-11 00:16:32
    因此,开多线程来执行批量任务是十分重要的一种批量操作思路,其实这种思路实现起来也十分简单,就拿批量更新的操作举例: 整体流程图 步骤 获取需要进行批量更新的大集合A,对大集合进行拆分操作,分成N个小集合A...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,448,358
精华内容 579,343
关键字:

java怎么开多线程

java 订阅