Java多线程

目录

一、进程(process)

1、概念

2、特点

3、并行和并发的区别

二、线程(thread)

1、概念

2、进程与线程的区别

3、线程的生命周期

二、多线程创建

1、继承Thread类创建线程

2、实现Runnable接口创建线程

3、实现Callable接口创建线程

4、使用线程池创建对象

5、守护线程(daemon)

三、线程安全

1、线程同步

2、死锁

3、Lock锁


一、进程(process)

1、概念

        进程就是正在运行的一个程序(program),或是程序执行的一次过程。而程序是静态的代码,是为了完成特定任务,用某种编程语言编写的指令集合。进程作为资源的分配单位,系统会在运行时为每个进程分配不同的内存区域。

2、特点

(1)独立性:进程是系统中独立存在的实体,它可以拥有自己的独立的资源,每一个进程都拥有自己私有的地址空间。在没有经过进程本身允许的情况下,一个用户进程不可以直接访问其他进程的地址空间。

(2)动态性:进程与程序的区别在于,程序只是一个静态的指令集合,进程是一个正在系统中活动的指令集合。在进程中加入了时间的概念,进程具有自己的生命周期和各种不同的状态,这些概念在程序中都是不具备的。

(3)并发性:多个进程可以在单个处理器上并发执行,多个进程之间不会互相影响。

3、并行和并发的区别

       并发是指在一个时间段内运行多个进程,而且运行在同一个处理机上,所以同一时刻只能够执行一个进程,但由于多个进程被快速的进行切换,给人造成了它们同时执行的感觉。但从微观上来说,它并不是同时进行的;

       并行必须在多处理机上进行,是指在同一时刻运行多个进程,每一个处理器运行一个进程。

二、线程(thread)

1、概念

       线程是操作系统能够进行运算调度的最小单位。它被包含在进程之中,是进程中的实际运作单位(即一个程序内部的一条执行路径)。一个进程可以开启多个线程。

       一个程序运行后至少有一个进程,一个进程里包含多个线程。如果一个进程只有一个线程,这种程序被称为单线程,如果一个进程中有多条执行路径被称为多线程程序。

      一个进程中可以有多个线程,每个进程有自己独立的内存,多个线程共享一个进程中的内存。

     一个进程中的多个线程共享相同的内存单元/内存地址空间->它们从同一个堆中分配对象,可以访问相同的变量和对象,就使得线程之间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患

2、进程与线程的区别

        进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。 

3、线程的生命周期

(1)   新建状态(New):当线程对象创建后,即进入了新建状态,如:Thread t = new MyThread();

(2)  就绪状态(Runnable):当调用线程对象的start()方法(t.start()),线程即进入就绪状态。处于就绪状态的线程,只是说明此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线程立即就会执行;

(3)  运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态执行,首先必须处于就绪状态中;

(4)  阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入到就绪状态,才有机会再次被CPU调用以进入到运行状态;

    根据阻塞产生的原因不同,阻塞状态又可以分为三种:

   a)   等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;

   b)   同步阻塞:线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;

   c)   其他阻塞:通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。

(5)  死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。

线程生命周期图解:

二、多线程创建

有四种:

1、继承Thread类创建线程

//1、创建线程子类
class MyThread extends Thread{
    //分线程执行run方法
    //2、重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+"-"+i);
            }
        }
    }
}
public class CreatThread {
    public static void main(String[] args) {
        //3、在主线程中创建线程子类对象(主线程创建的)
        MyThread myThread = new MyThread();
        //4、调用start方法,由分线程执行start方法中的内容
        //主线程继续向下执行
        //给线程设置名称
        myThread.setName("分线程");
        myThread.start();
        //以下代码继续由主线程执行
        //给主线程设置名称
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 1000; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+"-"+i);
            }
        }
    }
}

        注意:开启线程只能用start方法,我们直接用thread 对象调用run方法是不会开启线程的,此时的run方法由主线程调用而不是由我们自己创建的线程调用。

常用方法:

2、实现Runnable接口创建线程

      首先说一下为什么要使用实现Runnable的方式来创建线程:

       由于Java单继承的局限性,当A类已经继承了B类,那么A类就不可以在继承Thread类。此时想要在A类中创建线程,使用继承Thread类的方式就行不通了。所以就有了第二种创建线程的方式-实现Runnable接口。

       我们创建MyThread1类来实现Runnable接口,并重写run方法,run方法中是我们想要新创建的线程要执行的内容,然后再实例化MyThread1类。此时我们创建线程对象,并将MyThread1类的实例化对象当作实参传递给Thread的构造方法,这样我们开启线程之后就可以调用MyThread1类的run方法,也就是我们自己定义的线程执行内容。

  

//1、创建线程子类-实现runnable接口方式
class MyThread1 implements Runnable{
    //分线程执行run方法
    //2、重写run方法
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+"-"+i);
            }
        }
    }
}
public class CreatThread1 {
    public static void main(String[] args) {
        //3、在主线程中创建线程子类对象(主线程创建的)
        MyThread1 myThread1 = new MyThread1();
       //我们需要创建线程对象,将我们实现runnable接口的子类对象传过去
        Thread thread = new Thread(myThread1);
        //给线程设置名称
        thread.setName("分线程1");
        //4、调用start方法,由分线程执行start方法中的内容
        thread.start();
        //以下代码继续由主线程执行
        //给主线程设置名称
        Thread.currentThread().setName("主线程");
        for (int i = 0; i < 1000; i++) {
            if(i % 2 == 0){
                System.out.println(Thread.currentThread().getName()+"-"+i);
            }
        }
    }
}

 

3、实现Callable接口创建线程

步骤:

(1)声明自定义类,实现Callable接口

(2)重写call方法,有返回值(返回我们需要的值)

(3)创建Callable实现类对象

(4)创建FutureTask类对象,Callable实现类对象为形参,用FutureTask包装实现类对象。

(5)创建Thread类对象,FutureTask类对象作为形参

(6)Thread类对象.start(),开启线程

(7)用FutureTask类对象调用get方法获取call方法的返回值。

具体实现:

public class Test1 {
    public static void main(String[] args) {
        // 创建MyCallable对象
        Callable<Integer> myCallable = new MyCallable();
        //使用FutureTask来包装MyCallable对象
        FutureTask<Integer> futureTask = new FutureTask<Integer>(myCallable);
        //FutureTask对象作为Thread对象的target创建新的线程
        Thread thread = new Thread(futureTask);
        //线程进入到就绪状态
        thread.start();
        //主线程
        for (int i = 0; i < 100; i++) {
            if (i%2==1)
                System.out.println(Thread.currentThread().getName() + " " + i);
        }
        //获取call()方法返回的结果
        int sum = 0;
        try {
            sum = futureTask.get();
            System.out.println("sum = " + sum);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
}
class MyCallable implements Callable<Integer> {
    // 与run()方法不同的是,call()方法具有返回值
    @Override
    public Integer call() {
        int sum = 0;
        for (int i = 0; i < 100; i++) {
            if (i%2==0)
            System.out.println(Thread.currentThread().getName() + " " + i);
            sum+=i;
        }
        return sum;
    }
}

4、使用线程池创建对象

public class ThreadPool{
       private static int POOL_NUM = 10; //线程池数量 
          public static void main(String[] args) throws InterruptedException {
             ExecutorService executorService = Executors.newFixedThreadPool(5);
             for(int i = 0; i<POOL_NUM; i++) {
                     RunnableThread thread = new RunnableThread();
                     //Thread.sleep(1000); 
                    executorService.execute(thread);
                 }
             //关闭线程池 
             executorService.shutdown();
         } 
 }
 class RunnableThread implements Runnable { 
        @Override 
        public void run() {
            System.out.println("通过线程池方式创建的线程:" + Thread.currentThread().getName() + " ");
        } 
 } 

说明:

        ExecutorService、Callable都是属于Executor框架。返回结果的线程是在JDK1.5中引入的新特征,还有Future接口也是属于这个框架,有了这种特征得到返回值就很方便了。
        通过分析可以知道,它同样也是实现了Callable接口,实现了Call方法,所以有返回值。这也就是正好符合了前面所说的两种分类。
        执行Callable任务后,可以获取一个Future的对象,在该对象上调用get就可以获取到Callable任务返回的Object了。get方法是阻塞的,即:线程无返回结果,get方法会一直等待。

        再介绍Executors类:提供了一系列工厂方法用于创建线程池,返回的线程池都实现了ExecutorService接口。

  • public static ExecutorService newFixedThreadPool(int n)        创建n个线程的线程池。
  •  public static ExecutorService newCachedThreadPool()        创建一个可缓存的线程池,调用execute 将重用以前构造的线程(如果线程可用)。如果现有线程没有可用的,则创建一个新线程并添加到池中。终止并从缓存中移除那些已有 60 秒钟未被使用的线程。
  • public static ExecutorService newSingleThreadExecutor()        创建一个单线程化的Executor。
  •  public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize)         创建一个支持定时及周期性的任务执行的线程池,多数情况下可用来替代Timer类。

        ExecutoreService提供了submit()方法,传递一个Callable,或Runnable,返回Future。如果Executor后台线程池还没有完成Callable的计算,这调用返回Future对象的get()方法,会阻塞直到计算完成。

 参考:

        https://www.cnblogs.com/lwbqqyumidi/p/3804883.html

  https://blog.youkuaiyun.com/u011480603/article/details/75332435/

 

5、守护线程(daemon)

       线程分为用户线程和守护线程,虚拟机必须确保用户线程执行完毕之后才能将线程关闭,但是虚拟机不用等待守护线程执行完毕就可以将守护线程关闭。

       在用户线程执行完毕之后,虚拟机也会将守护线程关闭,虚拟机关闭需要一点时间,在关闭的这一个时间段内,守护线程依然会继续执行。

      设置守护线程:thread.setDaemom(true)//:默认是false,表示用户线程。

例如:

public class DaemonThread {
    public static void main(String[] args) throws InterruptedException {
        MyDaemon myDaemon = new MyDaemon();
        Thread thread = new Thread(myDaemon);
        //设置守护线程
        thread.setDaemon(true);
        thread.start();
        for (int i = 0; i < 1000; i++) {
            System.out.println("用户main线程"+i);
        }
    }
}

class MyDaemon implements Runnable{
    @Override
    public void run() {
        for (int i = 0; i < 1000; i++) {
            System.out.println("守护线程"+i);
        }
    }
}

用户main线程会执行完,但守护线程不会执行完。

三、线程安全

       如果有多个线程在同时运行,而这些线程可能会同时运行这段代码。程序每次运行结果和单线程运行的结果是一样的,而且其他的变量的值也和预期的是一样的,就是线程安全的。线程安全问题都是由全局变量及静态变量引起的。若每个线程中对全局变量、静态变量只有读操作,而无写操作,一般来说,这个全局变量是线程安全的;若有多个线程同时执行写操作,一般都需要考虑线程同步,否则的话就可能影响线程安全。

1、线程同步

       由于多个线程执行的不确定性引起执行结果的不稳定,多个线程对账本的共享,会造成操作的不完整性,破坏数据。比如你的银行账户有100元,你和你女朋友同时去银行取钱,她取80,你取50,若是在同一时间取的话且不考虑线程同步问题,那么你的账户最后可能会为负数,这明显是不合理的,所以提出线程同步问题。

       Java中提供了线程同步机制,它能够解决上述的线程安全问题。

       同步:实现排队效果,不抢占资源,但可以保证数据安全。

       异步:不排队,抢占资源,效率高但不保证数据安全。

       线程同步的方式有两种:

(1)同步代码块: 在代码块声明上 加上synchronized

synchronized (锁对象) {

    可能会产生线程安全问题的代码

}

同步代码块中的锁对象是需要增删改的任意对象。

(2)同步方法:在方法声明上加上synchronized

public synchronized void method(){

       可能会产生线程安全问题的代码

}

同步方法中的锁对象是当前类本身。

注:静态同步方法: 在方法声明上加上static synchronized

public static synchronized void method(){

    可能会产生线程安全问题的代码

}

静态同步方法中的锁对象是 类名.class。

       解释:synchronized方法控制对“对象”的访问,每个对象对应一把锁,每个synchronized方法都必须获得调用该方法对象锁才能执行,否则线程会阻塞,方法一旦执行,就独占该锁,直到该方法返回才释放锁,后面被阻塞的线程才能获得这个锁,继续执行。

   缺陷:若将一个大的方法声明为synchronized将会影响效率。

(3)JUC安全类型的集合:CopyOnWriteArrayList

注意:同步的方式虽然解决了线程同步问题,但是在操作同步代码时只允许一个线程参与,其他线程只能等待,相当于是一个单线程的处理过程,降低了效率。

2、死锁

       不同的线程分别占用对方所需要的资源,并且不放弃,都在等对方放弃自己所需要的资源,就形成了线程的死锁。出现死锁之后,程序不会出现异常,也不会出现提示,只是所有的线程都处于阻塞状态,无法继续下一步操作。

3、Lock锁

解决线程安全的问题

1、Lock是一个接口,它的一个实现类ReentrantLock中有两个方法:

(1)lock():给以下的代码上锁,保证运行期间只有一个线程执行;相当于同步锁中的同步监视器。

(2)unlock():解锁

2、synchronized与Lock的异同?

相同点:都是为解决线程安全问题。

不同点:同步方法或同步代码块中是必须执将当前的代码块(作用域)执行完毕才可以将同步监视器释放掉。而 Lock是手动解锁,即可以在任意时刻选择inlock,更加灵活。而且使用Lock,JVM将花费较少的时间来调度线程,性能更好,并且具有更好的扩展性(提供更多的子类)。

3、优先使用顺序:Lock->同步代码块->同步方法

注意:必须要保证对于将要执行的若干线程所使用的lock是唯一的。在Runable实现类中lock是唯一的,但是在Thread继承类中,声明多个线程,那么在使用lock的时候会导致lock的不唯一,必须将lock静态化。

例如:

public class LockThread {
    public static void main(String[] args) {
        SaleTicket saleTicket = new SaleTicket();
        new Thread(saleTicket).start();
        new Thread(saleTicket).start();
        new Thread(saleTicket).start();
    }

}
class SaleTicket implements Runnable{
    private final ReentrantLock lock = new ReentrantLock();
    int ticket = 10;
    @Override
    public void run() {
        while (true){
            try{
                lock.lock();
                if(ticket > 0){
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    System.out.println(ticket--);
                }else {
                    break;
                }
            }catch (Exception e){
                e.printStackTrace();
            }finally {
                lock.unlock();
            }
        }
    }
}

       

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值