您的位置:首页 - 教程 - Java - 正文
Java基础--多线程的方方面面

1,什么是线程?线程和进程的区别是什么?

2,什么是多线程?为什么设计多线程?

3,Java种多线程的实现方式是什么?有什么区别?

4,线程的状态控制有哪些方法?

5,线程安全、死锁和生产者--消费者

6,线程的优化有哪些方法?

1,什么是线程?线程和进程的区别是什么?

  线程是程序执行的最小单元。

    区别: 进程是操作系统进行资源处理和分配的最小单位,而一个进程可以包含多个线程,并共享进程的资源。

2,什么是多线程?为什么设计多线程?

  介绍之前,我们需要理解并行和并发的定义:

  并行:同一个时刻有多个线程进行。

      并发:同一个时间段内有多个线程进行。

  多线程指的是一个进程可以包含多个并发的线程(同一个时刻只有一个线程运行)。例如酷狗,我们可以一边听歌一边搜索自己喜欢的歌曲。多线程的存在能够让进程及时处理我们多项的请求,提高应用程序的利用率。

  多线程编程需要了解到多线程运行面临的问题。

      • 既然一个进程的多个线程共享进程的资源,怎样保证有多个线程访问同一资源时单个线程的访问不受其它线程的干扰。这是线程安全问题。
      • 多线程怎么控制线程的执行顺序,这是线程调度问题。ps:Java对多线程调度执行抢占式,每个线程有个优先级属性(1--10,10最高),优先级高的有限执行。

3,Java种多线程的实现方式是什么?有什么区别?

  Java实现多线程有两个方式。

    • 继承Thread类,重写run()方法,代码如下:

    线程类MyThread:

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int x=0;x<200;x++){
            System.out.println(x);
        }
    }    
}
View Code

          主类Demo:

public class Demo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();//新建线程类对象
        MyThread mt1 = new MyThread();
        mt.start();//调用start()方法
        mt1.start();
    }
}
View Code

       ps,为什么不直接调用线程类的run()方法,而调用start()方法?

      run()方法只是封装了多线程执行的操作,只是一个普通方法。

                start()方法是启动线程执行的方法,由JVM自动调用run()方法。

    • 实现Runnable 接口,重写run()方法,重点是主类调用的时候不同。

步骤:1,编写实现Runnable 接口的类,重写run()方法

              public class ThreadRunnable implements Runnable { public void run(){}}

        2,在主类中新建线程类对象,obj

              ThreadRunnable tr = new ThreadRunnable(); 

        3新建Thread类t,将obj作为t的构造参数

               Thread t = new Thread(tr);              

        4,调用t的start()方法。

    • 继承类Thread和实现Runnable接口对比

      由于Java只允许单类继承,故多选用实现Runnable接口的方法创建多线程,事实上Thread类也是接口Runnable的实现类。

         public class Thread extends Object implements Runnable    

4,线程的状态控制有哪些方法?

       线程状态控制常用到的方法如下:

    • 线程睡眠sleep(long millis)

      t.sleep(1000);让线程t睡眠1000毫秒,即1秒。

    • 线程加入join()   

      A.start();

           A.join();//try catch

      B.start();

      C.start();

      A执行完之后B和C才可以执行

    • 线程礼让static void yield()

      A.yield();A暂停一下,时间不确定,让同等级的线程优先运行。

    • 线程中断interrupt()

      A.interrupt();把线程的状态中止,并抛出 InterruptedException 。跳出阻塞的部分可以继续执行接下来的代码。    

    interrupt()只是改变中断状态而已. interrupt()不会中断一个正在运行的线程。这一方法实际上完成的是,给受阻塞的线程抛出一个中断信号,这样受阻线程就得以退出阻塞的状态。更确切 的说,如果线程被Object.wait, Thread.join和Thread.sleep三种方法之一阻塞, 它将接收到一个中断异常(InterruptedException),从而提早地终结被阻塞状态。

     如果线程没有被阻塞,这时调用interrupt()将不起作用;否则,线程就将得到InterruptedException异常(该线程必须事先预备好处理此状况),接着逃离阻塞状态。

          ps:对于一个正在运行的线程如果想要其结束运行,可以使用标志位,让线程跳出从而结束运行,示例如下:

public class ThreadFlag extends Thread 
{ 
    public volatile boolean exit = false; 

    public void run() 
    { 
        while (!exit); 
    }
}
View Code
    • 线程等待唤醒wait() notify()。这两个方法并不是线程类的方法,而是锁的方法,在接下一节介绍。

5,线程安全、死锁和生产者--消费者

  我们来看下面的一段代码:

public class ThreadRunnable implements Runnable {
    private static int D = 100;//D是静态变量由多个线程共享。public void run() {
        
            while(true){if(D>0){
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"---"+(D--));
                }
                
           }    
    }//run
}
View Code

 

    由于D是静态变量,它由ThreadRunnable的所有对象访问,每个对象对它进行输出,并减1的操作,直到D为0(可将D假想成某直达列车的票,每  个ThreadRunnable对象是一个售票窗口)假设有三个窗口如下:

public class ThreadRunnableDemo {
    public static void main(String[] args) {
        ThreadRunnable tr = new ThreadRunnable();//创建接口对象
        Thread t1 = new Thread(tr,"窗口1");//创建Thread类,将上述对象作为构造参数
        Thread t2 = new Thread(tr,"窗口2");//创建Thread类,将上述对象作为构造参数
        Thread t3 = new Thread(tr,"窗口3");//创建Thread类,将上述对象作为构造参数
        t1.start();//启动start方法
        t2.start();
        t3.start();
        
    }
}
View Code

        输出:

....
窗口1---97
窗口2---97
....
....
窗口3---2
窗口1---1
窗口2---0
窗口3----1
View Code

 

 出同号票的原因分析:

 

出现0号和负号票的原因分析:

     这就产生了线程不安全的问题,产生线程不安全的场景:

     多个线程访问同一资源,并对资源进行多条语句操作就有可能引发线程不安全。

         概括:多个线程;同一资源;不是原子操作

    前两个条件我们无法改变,我们有的解决思路就是将线程对资源操作语句封装成原子操作(不会被打断)。将操作封装成原子操作。

          Java使用synchronized关键字。

          使用规范:

    •  对共享代码块进行锁:synchronized(锁对象){共享代码块} 锁对象可以是任意的
public void run() {
            while(true){
            synchronized(new Object()){
                if(D>0){
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"---"+(D--));
                }  
           }
        }
}//run()
View Code
    • 对方法进行锁:private synchronized void function_name(){} 此时锁对象是this
    • 当方法是静态时private static synchronized void function_name(){},锁对象是线程类字节码文件对象。

      下面考虑更复杂的情况------死锁

       死锁顾名思义就是加的锁打不开,一般发生在当两个线程互相拿着对方的锁即锁嵌套,造成两个线程一直处于等待中。死锁示例:

      

           经典的生产者和消费者问题

           问题描述:生产者producer生产资源,消费者customer消耗资源呢,我们设计的程序最低保证消费者在消耗资源时必须保证有资源。

           设计思想:消费者和生产者共用一个锁,消费者一直消费资源,直到剩余资源数小于规定(0或者业务目标),消费者线程进入等待,直到生产者生产资源后将自己唤醒。

           实现:

    Producer类

package producer;

import java.util.ArrayList;

public class Producer implements Runnable {
    
    private ArrayList<String> al;
    public Producer(ArrayList<String> al){
        this.al = al;
    }
    
    @Override
    public void run() {
        while(true){
            synchronized (al) {
                while(al.size()>0){
                    try {
                        al.wait();//只要有资源,生产线程就wait()
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                for(int i=0;i<10;i++){
                    al.add(""+i);
                }
                al.notify();//生产完成,唤醒等待线程即消费者线程
            }
        }    
    }
}
View Code

          Customer类

package producer;

import java.util.ArrayList;

public class Customer implements Runnable {
    private ArrayList<String> al;
    public Customer(ArrayList<String> al){
        this.al = al;
    }
    @Override
    public void run() {
        while(true){
            synchronized (al) {
                while(al.size()<1){
                    try {
                        al.wait();//如果没资源,消费者线程就wait()
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                while(al.size()>0){
                    System.out.println("消费者:"+al.size());
                    al.remove(0);
                    
                }
                al.notify();//消费没了,唤醒等待线程即生产者线程
            }
        }        
    }
}
View Code

           调用类Demo

package producer;

import java.util.ArrayList;

public class Demo {
    private static ArrayList<String> al= new ArrayList<String>();
    public static void main(String[] args) {
        
        Producer p = new Producer(al);
        Customer c = new Customer(al);
        
        Thread producer = new Thread(p);
        Thread customer = new Thread(c);
        
        producer.start();
        customer.start();
        
    }
}
View Code

6,线程的优化有哪些方法?

    • 线程池

      实际业务场景中线程的寿命都很短暂,例如对于网站访问,每个用户请求是一个线程,如果来一个用户,进行一套线程的创建、就绪等动作会严重影响

服务器的响应效率,鉴于此,Java中有了线程池的解决办法,它的思想是程序初始运行时在一个容器内新建固定数量的线程,当用到时从容器内取出一个线程,

线程执行完之后再放回到容器内,实质是以空间换时间,这个容器在Java中就被称为线程池。

      Java实现线程池

      Executors类工厂类

     方法:

      public static ExecutorService new FixedThreadPool(int nThreads);//该方法返回一个含有n个线程的线程池接口

      线程池接口:ExecutorService

      方法:

  submit(Runnable task);//将一个线程类加入到线程池

     结束:shutdown()

 

ps:福利:

 

 

 


评论: