一:Java多线程编程

    科技2024-06-12  75

    1、进程与线程

              进程:一个应用程序的运行就是一个进程

              多进程:表示一个时间段上多个程序依次进行,而在一个时间点上只会有一个进程执行

              线程:在进程的基础之上划分的更小的程序单元,线程式在进程基础上创建并使用的,所以线程依赖于进程。但现成的启用速度要比进程快,所以当使用多线程进行并发处理时,其执行的性能要高于进程。

              注意:进程运行在windows系统之上,线程运行于进程基础之上

    在Java中实现多线程,需要一个专门的线程主体类进行线程的执行任务的定义,而这个主体类需要继承特定的父类或者实现特定的接口才可以完成

     

    2、第一种多线程实现方式:Thread类实现多线程

              若有一个类继承了Thread类,且覆写Thread类中提供的一个run()方法,就表示此类为线程的主体类,子类覆写的这个方法就叫线程的主方法,所有要执行的功能都应该写在此方法中。

              1)范例:多线程主体类

    //定义一个线程主体类 class MyThread extends Thread{ //定义类的属性 private String title; //实现有参构造 public MyThread(String title) { super(); this.title = title; } //覆写线程的run方法 @Override public void run(){//线程的主体方法 for (int x = 0;x < 10;x++){ System.out.println(this.title + "运行,x=" + x); } } }

              注意:在启动多线程时,run()方法并不能通过实例化对象进行调用,只能使用start()方法进行调度。

              若使用对象实例化调用,代码如下:

    package cn.demos; //定义一个线程主体类 class MyThread extends Thread{ //定义类的属性 private String title; //实现有参构造 public MyThread(String title) { super(); this.title = title; } //覆写线程的run方法 @Override public void run(){//线程的主体方法 for (int x = 0;x < 10;x++){ System.out.println(this.title + "运行,x=" + x); } } } public class Demo1 { public static void main(String[] args) { new MyThread("线程A").run(); new MyThread("线程B").run(); new MyThread("线程C").run(); } } 结果线程A运行,x=0 线程A运行,x=1 线程A运行,x=2 线程A运行,x=3 线程A运行,x=4 线程A运行,x=5 线程A运行,x=6 线程A运行,x=7 线程A运行,x=8 线程A运行,x=9 线程B运行,x=0 线程B运行,x=1 线程B运行,x=2 线程B运行,x=3 线程B运行,x=4 线程B运行,x=5 线程B运行,x=6 线程B运行,x=7 线程B运行,x=8 线程B运行,x=9 线程C运行,x=0 线程C运行,x=1 线程C运行,x=2 线程C运行,x=3 线程C运行,x=4 线程C运行,x=5 线程C运行,x=6 线程C运行,x=7 线程C运行,x=8 线程C运行,x=9

              可以看到程序结果是按顺序进行,A线程计算完成才进行B线程的计算,随后是C线程,并没有实现交替进行的结果。所以,启动多线程时,需要使用start()方法来进行调用。

              多线程启动:使用start()方法,代码如下

    package cn.demos; //定义一个线程主体类 class MyThread extends Thread{ //定义类的属性 private String title; //实现有参构造 public MyThread(String title) { super(); this.title = title; } //覆写线程的run方法 @Override public void run(){//线程的主体方法 for (int x = 0;x < 10;x++){ System.out.println(this.title + "运行,x=" + x); } } } public class Demo1 { public static void main(String[] args) { new MyThread("线程A").start(); new MyThread("线程B").start(); new MyThread("线程C").start(); } } 结果线程A运行,x=0 线程A运行,x=1 线程A运行,x=2 线程C运行,x=0 线程B运行,x=0 线程B运行,x=1 线程B运行,x=2 线程B运行,x=3 线程B运行,x=4 线程B运行,x=5 线程B运行,x=6 线程B运行,x=7 线程B运行,x=8 线程B运行,x=9 线程C运行,x=1 线程A运行,x=3 线程C运行,x=2 线程A运行,x=4 线程A运行,x=5 线程A运行,x=6 线程A运行,x=7 线程A运行,x=8 线程A运行,x=9 线程C运行,x=3 线程C运行,x=4 线程C运行,x=5 线程C运行,x=6 线程C运行,x=7 线程C运行,x=8 线程C运行,x=9

              如上结果交替显示,表示线程启动成功,执行顺序不可控。

              每一个线程对象只能启动一次,若重复启动会报以下错误:

    问题主要代码信息重复启动

    public static void main(String[] args) {         MyThread mt = new MyThread("线程A");         mt.start();         mt.start();//重复启动

        }

    Exception in thread "main" 

    java.lang.IllegalThreadStateException

     

    3、第二种多线程实现方式:Runnable接口实现多线程

              1)范例:定义并启动多线程(通常方法)

    package cn.demos; //定义一个线程主体类 class MyThread implements Runnable { // 定义类的属性 private String title; // 实现有参构造 public MyThread(String title) { super(); this.title = title; } // 覆写线程的run方法 @Override public void run() {// 线程的主体方法 for (int x = 0; x < 10; x++) { System.out.println(this.title + "运行,x=" + x); } } } public class Demo1 { public static void main(String[] args) { // 启动多线程 Thread threadA = new Thread(new MyThread("线程AAAA")); Thread threadB = new Thread(new MyThread("线程BBBB")); Thread threadC = new Thread(new MyThread("线程CCCC")); // 启动多线程 threadA.start(); threadB.start(); threadC.start(); } }

              

    结果线程AAAA运行,x=0 线程BBBB运行,x=0 线程CCCC运行,x=0 线程BBBB运行,x=1 线程BBBB运行,x=2 线程BBBB运行,x=3 线程BBBB运行,x=4 线程AAAA运行,x=1 线程CCCC运行,x=1 线程AAAA运行,x=2 线程CCCC运行,x=2 线程CCCC运行,x=3 线程CCCC运行,x=4 线程AAAA运行,x=3 线程AAAA运行,x=4

              通过文档,观察Runnable接口定义,从之前的学习中可以得知@FunctionalInterface表示函数编程,即Lamda表达式编程,下面的例子将使用Lamda表达式进行启动线程

    Runnable接口定义

    @FunctionalInterface   //表示函数式接口 public interface Runnable{     public void run();

    }

              2)范例:定义并启动多线程(Lamda表达式启动)

    代码结果

    public class Demo1 {

        public static void main(String[] args) {         for (int x = 0; x < 3; x++) { // 定义线程个数             String title = "线程对象-" + x;             // 进行对象接收             Runnable run = () -> {                 for (int y = 0; y < 5; y++) {                     System.out.println(title + "运行,y=" + y);                 }             };             // 启动线程             new Thread(run).start();         }     } }

    线程对象-0运行,y=0 线程对象-2运行,y=0 线程对象-2运行,y=1 线程对象-2运行,y=2 线程对象-2运行,y=3 线程对象-2运行,y=4 线程对象-1运行,y=0 线程对象-0运行,y=1 线程对象-1运行,y=1 线程对象-0运行,y=2 线程对象-0运行,y=3 线程对象-0运行,y=4 线程对象-1运行,y=2 线程对象-1运行,y=3 线程对象-1运行,y=4

     

    4、Thread与Runnable关系

     Thread类Runnable接口定义 public class Thread extends Object implements Runnable

    public interface Runnable{     public void run();

    }

    代码

    package cn.demos;

    //定义一个线程主体类 class MyThread extends Thread{     //定义类的属性     private String title;     //实现有参构造     public MyThread(String title) {         super();         this.title = title;     }     //覆写线程的run方法     @Override     public void run(){//线程的主体方法         for (int x = 0;x < 10;x++){             System.out.println(this.title + "运行,x=" + x);         }     } } public class Demo1 {

        public static void main(String[] args) {         new MyThread("线程A").start();         new MyThread("线程B").start();         new MyThread("线程C").start();

        } }  

    public class Demo1 {

        public static void main(String[] args) {         for (int x = 0; x < 3; x++) { // 定义线程个数             String title = "线程对象-" + x;             // 进行对象接收             Runnable run = () -> {                 for (int y = 0; y < 5; y++) {                     System.out.println(title + "运行,y=" + y);                 }             };             // 启动线程             new Thread(run).start();         }     } }

     联系

    1、从定义中可以看出,Thread类实现了Runnable接口,即Thread类属于Runnable的子类

    2、Runnable接口里只定义了一个run()方法,所以,Thread类中的run()方法是覆写的Runnable里的

    3、线程对象先调用Thread类中的start()方法,然后start()方法里调用run()方法,具体可看源码

              1)范例:利用卖票程序来实现多个线程的资源并发访问

    代码结果

    package cn.demos;

    //定义一个线程主体类 class MyThread implements Runnable {

        private int ticket = 5;

        @Override     public void run() {         for (int x = 0; x < 100; x++) {             if (this.ticket > 0) {                 // 成功卖出去一张票减少一张                 System.out.println("卖票,ticket=" + this.ticket--);             }

            }     } }

    public class Demo1 {

        public static void main(String[] args) {

            MyThread mt = new MyThread();         new Thread(mt).start();// 第一个线程启动         new Thread(mt).start();// 第二个线程启动         new Thread(mt).start();// 第三个线程启动

        } }  

    卖票,ticket=5 卖票,ticket=3 卖票,ticket=1 卖票,ticket=4 卖票,ticket=2  

     

    5、Callable接口实现多线程

              在JDK1.5之后,新增了一个新的线程实现接口,解决了Runnable接口没有返回值的问题

     CallableFutureTaskRunnableFutureRunnableFuture定义 @FunctionalInterface public interface Callable<V>{ public V call​() throws Exception; } public class FutureTask<V> extends Object implements RunnableFuture<V>public interface RunnableFuture<V> extends Runnable, Future<V>

    public interface Runnable{     public void run();

    }

    public interface Future<V>{     public V get​() throws InterruptedException,ExecutionException;

    //get实现返回值接收 }

    FutureTaskpublic class FutureTask<V>  extends Object  implements RunnableFuture<V>{       public FutureTask​(Callable<V> callable); }

              由定义可以看出,Callable定义的时候可以设置一个泛型,此泛型的类型就是返回的数据类型,如此可以避免向下转型带来的安全隐患。

              关系图:

             2)范例:使用Callable实现多线程处理

    代码结果

    package cn.demos;

    import java.util.concurrent.Callable; import java.util.concurrent.FutureTask;

    class MyThread implements Callable<String> {     @Override     public String call() throws Exception {         for (int x = 0; x < 5; x++) {             System.out.println("线程执行,x=" + x);         }         return "线程执行完毕";     } } public class Demo1 {     public static void main(String[] args) throws Exception {                  FutureTask<String> task = new FutureTask<>(new MyThread());         new Thread(task).start();         System.out.println("线程返回数据!!!!" + task.get());

        } }  

    线程执行,x=0 线程执行,x=1 线程执行,x=2 线程执行,x=3 线程执行,x=4 线程返回数据!!!!线程执行完毕

              面试题:Runnable与Callable的区别

    区别RunnableCallable1提出时间是JDK1.0提出时间是JDK1.52java.lang.Runnable接口之中只提供有一个run()方法,且没有返回值java.util.concurrent.Callable接口中提供有call()方法,可以有返回值

              总结:不论使用何种方法实现多线程,线程的启动只有一个方法,即Thread类的对象.start()方法

    6、多线程运行状态

              a.任何一个线程的对象都会使用Thread类进行封装,而线程的启动时调用start(),所以,当对象调用start()方法后,线程其实并没有执行,而是进入了一种就绪状态;

              b.进入就绪状态之后需要等待进行资源调度,当某一个线程调度成功之后则进入运行状态(run()方法);

              c.线程并不会一直运行,中间需要产生一些暂停状态,例如:某个线程执行一段时间之后需要让出资源(或理解为资源被其他线程抢占),此时就会进入堵塞状态,随后会重新回归到就绪状态;

              d.当run()方法执行完毕之后,实际上该线程的主要任务也结束了,那么此时直接进入停止状态。

    Processed: 0.010, SQL: 8