多线程的主要内容

本文介绍了Java中的多线程特性,包括进程和线程的概念,以及它们之间的关系。Java通过多线程实现并发执行,提高程序效率。文章详细阐述了线程的生命周期,以及通过继承Thread类、实现Runnable接口和Callable接口三种方式创建线程。此外,还讨论了线程的可见性和原子性问题,指出volatile关键字在解决可见性问题中的作用,以及在并发操作中可能出现的原子性问题。
摘要由CSDN通过智能技术生成

  Java是少数的几种支持“多线程”的语言之一。大多数的程序语言只能循序运行单独一个程序块,但无法同时运行不同的多个程序块。Java的“多线程”恰可弥补这个缺憾,它可以让不同的程序块一起运行,如此一来可让程序运行更为顺畅,同时也可达到多任务处理的目的

1.关于线程

1.1 进程(一个正在运行的程序就是进程)ctrl+alt+delete

  程序: 是为了完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,

  进程是程序的一次动态执行过程,它经历了从代码加载、执行到执行完毕的一个完整过程,这个过程也是进程本身从产生、发展到最终消亡的过程。多进程操作系统能同时运行多个进程(程序),由于CPU具备分时机制,所以每个进程都能循环获得自己的CPU时间片。由于CPU执行速度非常快,使得所有程序好象是在“同时”运行一样。

  每个独立执行的程序称为进程

  在操作系统中进程是进行系统资源分配、调度和管理的最小单位,进程在执行过程中拥有独立的内存单元。比如:Windows采用进程作为最小隔离单位,每个进程都有自己的数据段、代码段,并且与别的进程没有任何关系。因此进程间进行信息交互比较麻烦

1.2 线程 在应用程序中的一个执行路径

为了解决进程调度资源的浪费,为了能够共享资源,出现了线程。线程是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源,多个线程共享内存,从而极大地提高了程序的运行效率。线程是比进程更小的执行单位,线程是进程内部单一的一个顺序控制流。所谓多线程是指一个进程在执行过程中可以产生多个线程,这些线程可以同时存在、同时运行,形成多条执行线。一个进程可能包含了多个同时执行的线程。

一个或更多的线程构成了一个进程(操作系统是以进程为单位的,而进程是以线程为单位的,进程中必须有一个主线程main)。

如果一个进程没有了,那么线程肯定会消失,如果线程消失了,但是进程未必会消失。只有所有的线程都结束了,进程才会结束!!!而且所有线程都是在进程的基础之上同时运行

1.3 进程与线程的关系

进程的产生,肯定会产生至少一个以上的线程;

进程关闭,该进程内的线程会全部销毁;

线程销毁,进程未必会关闭

一个进程包含多个执行路径 包含多个线程

1.4 线程与进程的区别

多线程是实现并发机制的一种有效手段。进程和线程一样,都是实现并发的一个基本单位。线程和进程的主要差别体现如下:

进程:每个进程都有自己独立的代码和数据空间,进程间的切换开销大

线程:一个进程内的多个线程,共享代码和数据空间,线程间的切换开销比较小

多线程的应用范围很广。在一般情况下,程序的某些部分同特定的事件或资源联系在一起,同时又不想为它而暂停程序其它部分的执行,这种情况下,就可以考虑创建一个线程,令它与那个事件或资源关联到一起,并让它独立于主程序运行。通过使用线程,可以避免用户在运行程序和得到结果之间的停顿,还可以让一些任务(如打印任务)在后台运行,而用户则在前台继续完成一些其它的工作。总之,利用多线程技术,可以使编程人员方便地开发出能同时处理多个任务的功能强大的应用程序

1.5 多线程

线程只能实现一次

多线程,指的是一个进程内的多个任务并发执行;

不管计算机上是一个进程还是多个进程,也不管是一个线程还是多个线程,CPU只有一块,要实现多线程,实际上都需要在一个CPU上完成资源的调度。例如:在某一段时间内只允许A线程操作,而在另外一段时间内,CPU就让给了其他线程,此时需要一个时间片的轮转算法,进行资源的调度

多线程的好处:可以更高效地利用CPU资源,同时,让固定流程的程序更加灵活;

cpu 要在多个线程之间来回切换

注意:多个线程之间,谁先抢占到资源,谁就先执行

2. 多线程的实现

  在Java之中,如果要想实现多线程的开发,有三种形式:一种是继承Thread类,另外一种实现Runnable接口,最后一种实现Callable接口.那么下面通过代码分析,来观察这三种实现的操作

2.1 继承Thread类

  线程的操作首先一定要有一个线程的主体操作类,这个主体操作类就可以通过继承Thread类来完成,而继承Thread类之后还要去覆写Thread类中的run()方法,此方法的功能与main()方法类似,属于线程的启动点.

package cn.sz.gl.no5;

public class MyThread extends Thread {

	@Override
	public void run() {
		System.out.println("执行了线程的run方法_");
	}
}

启动线程:

package cn.sz.gl.no6;

public class Test {

	public static void main(String[] args) {
		MyThread mta = new MyThread();
		MyThread mtb = new MyThread();
		MyThread mtc = new MyThread();
		MyThread mtd = new MyThread();
		MyThread mte = new MyThread();
		
		mta.start();
		mtb.start();
		mtc.start();
		mtd.start();
		mte.start();
		
	}
}

 其中start()方法的实现源码如下:

public synchronized void start() {

     if (threadStatus != 0)
         throw new IllegalThreadStateException();
 
     group.add(this);

     boolean started = false;
     try {
         start0();
         started = true;
     } finally {
         try {
             if (!started) {
                 group.threadStartFailed(this);
             }
         } catch (Throwable ignore) {

         }
     }
 }

 private native void start0();

注意:启动多线程必须是通过线程类的对象来调用start()方法。不能直接调用run()方法,如果直接调用run()则仍然是单线程,没有启动多线程。通过调用start()方法,启动一个子线程,子线程会直接运行run()方法内的代码

start()方法执行,首先做一个判断,这里是判断该线程是否已经启动了,如果是已经启动的线程,会报IllegalThreadStateException异常

结论:线程不允许重复启动

调用start0()方法,该方法没有实现,且用native修饰,因为该方法执行,需要调用系统资源,而java有很大的一个特征,就是可移植,那么这时候做法有两种,可以通过JNI技术来实现,但会影响可移植性,我们还有一种选择,把控制权交给JVM来处理,由JVM向底层请求,此时只需要给该方法加上native来修饰就可以了

在多线程使用时,要想启动多线程,必须通过start()方法

2.2 实现Runnable接口

 多线程类实现Runnable接口后,还是需要Thread类下的start()方法来启动线程

package cn.sz.gl.no6;

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		System.out.println("我的线程...");
	}

}

启动线程:

package cn.sz.gl.no6;

public class Test {

	public static void main(String[] args) {
		MyRunnable mra = new MyRunnable();
		MyRunnable mrb = new MyRunnable();
		MyRunnable mrc = new MyRunnable();
		MyRunnable mrd = new MyRunnable();
		MyRunnable mre = new MyRunnable();
		
		new Thread(mra).start();
		new Thread(mrb).start();
		new Thread(mrc).start();
		new Thread(mrd).start();
		new Thread(mre).start();
		
	}
}

2.3 实现Callable接口

(1)创建Callable接口的实现类,并实现call()方法,该call()方法将作为线程执行体,并且有返回值。

(2)创建Callable实现类的实例,使用FutureTask类来包装Callable对象,该FutureTask对象封装了该Callable对象的call()方法的返回值。

(3)使用FutureTask对象作为Thread对象的target创建并启动新线程。

(4)调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

package Thread;

import java.util.concurrent.*;

public class TestThread {
public static void main(String[] args) throws Exception {
   testCallable();
}

public static void testCallable() throws Exception {
   Callable callable = new MyThreadCallable();
   FutureTask task = new FutureTask(callable);
   new Thread(task).start();
   System.out.println(task.get());
   Thread.sleep(10);//等待线程执行结束
   //task.get() 获取call()的返回值。若调用时call()方法未返回,则阻塞线程等待返回值
   //get的传入参数为等待时间,超时抛出超时异常;传入参数为空时,则不设超时,一直等待
   System.out.println(task.get(100L, TimeUnit.MILLISECONDS));
}
}

class MyThreadCallable implements Callable {

@Override
public Object call() throws Exception {
   System.out.println("通过实现Callable,线程号:" + Thread.currentThread().getName());
   return 10;
}
}

2.4 三种实现方式的区别

  • 采用继承Thread类方式:

   (1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。    (2)缺点:因为线程类已经继承了Thread类,所以不能再继承其他的父类。

  • 采用实现Runnable接口方式:

   (1)优点:线程类只是实现了Runable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一个目标对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。    (2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。

  • Runnable和Callable的区别:

   (1)Callable规定的方法是call(),Runnable规定的方法是run().    (2)Callable的任务执行后可返回值,而Runnable的任务是不能返回值得    (3)call方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常    (4)运行Callable任务可以拿到一个Future对象,表示异步计算的结果。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

start()和run()的区别

  • start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取cpu的执行权才可以执行

  • run()方法是由jvm创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)

  • 3. 线程的生命周期

  • 多线程下 并发编程的时候
  • 可见性问题
    package com.cn1;
    //可见性问题
    //提供两个线程 共享一个变量...》共享资源
    //一个线程读数据 一个线程写数据
    //一个线程对共享变量作了修改, 另外一个线程感知不到
    public class Visiable {
        //定义一个共享变量
        //volatile告诉    jvm 不要优化代码(这些代码使用到了共享数据) 保证了内存的可见性
        private volatile static boolean f=true;
    //private static boolean f=true;
    
        public static void main(String[] args) throws Exception {
            //线程不断的读取共享变量的值
            //即时编译器会对热点代码进行优化
            new Thread(()->{
                while (f){
    
                }
            }).start();
    
            Thread.sleep(1000);
            //Runnable
            new Thread(()->{
                f=false;
                System.out.println("第一个线程读到了共享变量的值"+f);
            }).start();
    
    
    
        }

    原子性问题
    package com.cn1;import java.util.ArrayList;public class Test {    
        //1.定义一个共享变量number    
        //2。对number进行1000的++操作;    
        //3.使用5个线程来进行    
        //1.定义一个共享变量number    
        private static int number=0;    
        public static void main(String[] args) throws Exception {        
            //2.对number进行1000的++操作        
            Runnable increment=()->{            
                for (int i=0;i<1000;i++){                
                    number++;            
                }        
            };        
            ArrayList<Thread> list = new ArrayList<>();        
            //3.使用5个线程来进行操作        
            for (int i = 0; i <5 ; i++) {            
                Thread t = new Thread(increment);            
                t.start();            
                list.add(t);        
            }        
            for (Thread t : list) {            
                t.join();        
            }        
            System.out.println("number="+number);    
        }
    }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值