多线程之基础
发布日期:2022-04-11 08:52:46 浏览次数:15 分类:技术文章

本文共 16671 字,大约阅读时间需要 55 分钟。

目录


程序、进程、线程的概念

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

进程就是正在执行的程序,从windows的角度讲,进程是操作系统进行资源分配的最小单位。

线程进程可进一步细化为线程,是一个进程内部的最小执行单元 ,是操作系统进行任务调度的最小单元,隶属于进程。是cpu进行调度的最小单位。

线程和进程的关系

一个进程可以包含多个线程,一个线程只能属于一个进程,线程不能脱离进程

而独立运行; (例如在执行qq的进程时,不会突然跳转到其他的进程中)

每一个进程至少包含一个线程(称为主线程);在主线程中开始执行程序,java 程序的入口main()方法就是在主线程中被执行的

在主线程中可以创建并启动其它的线程;

一个进程内的所有线程共享该进程的内存资源。(线程在进程的内部执行)

创建线程

在单线程中仍然遵循的时自上而下的运行规则。在多线程中才会并发的执行线程。

方式有两种:

继承Thread类的方式

Thread是程序中执行的线程,Java虚拟机允许程序同时执行多个线程。创建线程的方法创建一个类继承Thread类,直接调用父类中的方法启动即可。

public class Test {    public static void main(String[] args) {        ThreadDemo threadDemo=new ThreadDemo();        //启动线程的方法 start0();        threadDemo.start();        ThreadDemo1 threadDemo1=new ThreadDemo1();        threadDemo1.start();//会出现交替执行,消费完本次的时间片后,会交替为其他线程    }}public class ThreadDemo extends Thread{    @Override    public void run() {        for(int i=0;i<30;i++){            try {                sleep(100);                System.out.println(i);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}public class ThreadDemo1 extends Thread {    @Override       public void run() {        for(int i=0;i<30;i++){            try {                sleep(100);                System.out.println("ThreadDemo1:"+i);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

实现Runnable接口的方式

java.lang.Runnable接口中仅仅只有一个抽象方法:

public void run();

Runnable接口的存在主要是为了解决Java中不允许多继承的问题。

public class Demo {    //main方法在主线程中执行    public static void main(String[] args) {        MyThread myt=new MyThread();        Thread th=new Thread(myt);        //这才是创建了一个线程,myt的类中的内容是为这个线程分配的任务        th.start();        for(int i=0;i<100;i++){            System.out.println("main:"+i);        }    }}public class MyThread implements Runnable{    @Override    public void run() {      for(int i=0;i<100;i++){          System.out.println("Runable:"+i);      }    }}

继承Thread类虽然方便,可以直接调用,但是却不能再继承其他的类,因此,实现Runable接口有更好的扩展性。

继承方式和实现方式的联系与区别

【区别】

继承Thread: 线程代码存放Thread子类run方法中。

实现Runnable:线程代码存在接口的子类的run方法。

【实现Runnable的好处】

1)避免了单继承的局限性

2)多个线程可以共享同一个接口实现类的对象,非常适合多个相同线程来处

理同一份资源。

Thread类中方法

Thread.curentThread() //得到当前执行线程的对象

Thread.currentThread().getName () //拿到线程的名字

Thread th=new Thread(myth,"窗口1");//也可以通过在创建对象是赋值名字。这是一个构造方法,可以分配任务以及定义线程的名称

Thread.currentThread().setName();另一种为对象定义名称的方式

线程的优先级

事实上,计算机只有一个cpu,各个线程轮流获得cpu的使用权,才能执行任务;优先级较高的线程有更多获得cpu的机会,反之亦然。优先级用整数表示,取值范围是1~10,一般情况下,线程的默认优先级都是5,但是也可以通过setPrioritygetPriority方法来设置或返回优先级;

public class Test {    public static void main(String[] args) {        ThreadDemo threadDemo=new ThreadDemo();        threadDemo.setPriority(5);//放置的优先级为5        threadDemo.start();        ThreadDemo1 threadDemo1=new ThreadDemo1();        threadDemo1.setPriority(10);//放置的优先级为10        threadDemo1.start();        System.out.println( "threadDemo1:   "+threadDemo1.getPriority());        System.out.println("threadDemo:    "+threadDemo.getPriority());    }}
public class ThreadDemo extends Thread{    @Override    public void run() {        for(int i=0;i<30;i++){            try {                sleep(100);                System.out.println("ThreadDemo:"+i);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}
public class ThreadDemo1 extends Thread {    @Override    public void run() {        for(int i=0;i<30;i++){            try {                sleep(100);                System.out.println("ThreadDemo1:"+i);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }}

开始要先运行主线程,因此会先显示优先级

调度策略

时间片 每个线程执行固定的时间片,来进行交替执行

抢占式高优先级的线程抢占CPU

Java中的线程支持设置优先级的,默认的优先级是5,最低是1,最高是10

线程状态

线程在它的生命周期中会处于不同的状态:

新建:当一个Thread类或其子类的对象被声明并创建时,新生的线程对

象处于新建状态

就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时

间片,此时它已具备了运行的条件,只是没分配到CPU资源

运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态,run

()方法定义了线程的操作和功能

阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出

CPU并临时中止自己的执行,进入阻塞状态

死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常

导致结束

新建状态需要调用start()进入就绪状态,就绪状态被cpu调度上后进入到运行状态获得cpu的执行权(有操作系统的调度算法来决定运行状态)。

如果正常的失去cpu的执行权,就回到就绪状态。而yield()方法属于线程让步,当线程正在运行时,调用此方法,主动的让出cpu的执行权,不会进入到阻塞状态,会重新进入到就绪状态进行排队。还有IO阻塞,例如在输入时一直没有输入,cpu也不会一直等待,等确定输入内容后提交,才会再重新进入就绪状态进行排队。

非正常的失去cpu的执行权则到达阻塞状态,例如sleep()主动的让线程进入休眠。join()方法让此线程加入到当前线程,等待此线程死亡,死亡后再执行当前线程,因此当前线程进入阻塞。

运行状态中的线程正常执行完毕,就会进入到死亡状态。stop()方法也可以结束线程,但是这种方法是不安全的,在运行过程中可能会造成进程的异常。

线程的分类

Java中的线程分为两类:用户线程和守护线程

守护线程的作用是为其他线程的运行提供便利服务,守护线程最典型的应用就是 GC (垃圾回收器),它就是一个很称职的守护者。

用户线程和守护线程两者几乎没有区别,唯一的不同之处就在于虚拟机的离开:如果用户线程已经全部退出运行了,只剩下守护线程存在了,虚拟机也就退出了。 因为没有了被守护者,守护线程也就没有工作可做了,也就没有继续运行程序的必要了注意:设置线程为守护线程必须在启动线程之前,否则会跑出一个 IllegalThreadStateException异常。

public class ThreadDemo extends Thread{    @Override    public void run() {        for(int i=0;i<30;i++){            try {                sleep(100);                System.out.println("用户线程:"+i);            } catch (InterruptedException e) {                e.printStackTrace();            }        }    }            public static void main(String[] args) {        ThreadDemo threadDemo=new ThreadDemo();        threadDemo.start();        ThreadDemo1 threadDemo1=new ThreadDemo1();        threadDemo1.setDaemon(true);//设置为守护线程,必须在线程启动前设置        threadDemo1.start();    }        public void run() {        while(true){            try {                sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            System.out.println("守护进程");        }    }}

多线程概念

对线程是指程序中包含多个执行单元,即在一个程序中可以同时运行多个不同的线程执行不同的任务,也就是说允许多个程序创建多个并行执行的线程来完成各自的任务。

何时需要多线程

● 程序需要同时执行两个或多个任务。

● 程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、

网络操作、搜索等。

● 需要一些后台运行的程序时。

多线程的优点

  • 提高程序的响应.

  • 提高CPU的利用率.

  • 改善程序结构,将复杂任务分为对个线程,独立运行.

多线程的缺点

  • 线程也是程序,所以线程需要占用内存,线程越多占用内存也越多

  • 多线程需要协调和管理,所以需要cpu时间跟踪线程

  • 线程之间对共享资源的访问会相互影响,必须解决竟用共享资源的问题(多个线程在对同一个共享资源访问时,可能会出现问题。例如买票问题)

线程同步

多线程同步

多个线程同时读写同一份共享资源时,可能会引起冲突,所以引入线程“同步”机制,即各线程间要有先来后到。

同步就是排队+锁

几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作

为了保证数据在方法中被访问时的正确性,在访问时加入锁机制(访问量大时必须加锁)

public class ReenDemo implements Runnable{ //ReentrantLock()    int ticket=10;    static Lock lock=new ReentrantLock();    /*    显示的加锁,加在代码中间     */    @Override    public void run() {        while(true){            try {                lock.lock();                Thread.sleep(100);            } catch (InterruptedException e) {                e.printStackTrace();            }            if(ticket>0){                System.out.println(Thread.currentThread().getName()+" "+ticket);                ticket--;            }else {                break;            }            lock.unlock();        }    }    public static void main(String[] args) {      ReenDemo ticket1=new ReenDemo();          new Thread(ticket1,"窗口1").start();          new Thread(ticket1,"窗口2").start();    }}

确保一个时间点只有一个线程访问共享资源。可以给共享资源加一把锁,哪个线程获取了这把锁,才有权利访问该共享资源.

在Java代码中实现同步:

使用synchronized(同步监视器)关键字同步方法或代码块。

synchronized (同步监视器){

// 需要被同步的代码;

}

synchronized还可以放在方法声明中,表示整个方法,为同步方法。

例如:

public synchronized void show (String name){

// 需要被同步的代码;

}

/*   创建一个出票的线程 */public class TicketThread  extends Thread{     //static 修饰的变量是类变量,在内存中只有一份     static  int num = 10;// 设定有10张票     static  Object obj = new Object();    @Override    public void run() {          while(true){           /*           synchronized (同步对象)  同步对象可以是任意类的对象,但是只能唯一的,只有一份的                        继承Thread时不能用this,因为我们创建两个线程对象,哪个线程访问,this就表示的是哪个线程,因此会造成两个线程都可以进入。但是实现Runable接口时,因为线程任务只有一个,因此使用this时可以的             对象中有一个区域叫对象头,对象头中有一个锁状态的记录            */              synchronized (obj){//进入同步代码块 加锁 也就将对象头锁状态改为 加锁                  try {                      Thread.sleep(1000);                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  if(num>0){                      System.out.println(Thread.currentThread().getName()+"买到了票:"+num);                      num--;                  }else{                      break;                  }                  //同步代码块执行完成后,会自动释放锁 也就将对象头锁状态改为 无锁              }          }    }    public static void main(String[] args) {        TicketThread t1 = new TicketThread();                          t1.setName("窗口1");                          t1.start();        TicketThread t2 = new TicketThread();                        t2.setName("窗口2");                        t2.start();    }}
/*   创建一个出票的线程*/public class ThreadDemo implements Runnable{    static int num=10;        public void run(){            while(true) {                synchronized (this) {                    try {                        Thread.sleep(30);                        if (num > 0) {                            System.out.println(Thread.currentThread().getName() + ":" + num);                            num -= 1;                        } else {                            break;                        }                    } catch (InterruptedException e) {                        e.printStackTrace();                    }                }            }        }}public static void main(String[] args){    TicketThread t=new TicketThread();       new Thread(t,"窗口1").start();       new Thread(t,"窗口2").start( );    }

synchronized 修饰非静态方法,锁标志的对象默认是this。因此继承Thread类的类创建线程时,调用ticket()方法需要使用静态方法

synchronized修饰静态方法,锁标志对象时类的Class对象,只要是同一个类的对象,那么他们对应的Class对象是相同的 (继承Thread)

public class TicketThread  extends Thread{     //static 修饰的变量是类变量,在内存中只有一份     static  int num = 10;// 设定有10张票     static  Object obj = new Object();    @Override    public void run() {          while(true){                ticket();               if(num==0){                      break;               }          }    }    /*       synchronized 修饰非静态方法,锁标志的对象默认是this       synchronized 修饰静态方法,锁标志对象是类的Class对象,只要是同一个类的对象,那么他们对应的Class对象时相同的     */    public static synchronized void ticket(){        if(num>0){            System.out.println(Thread.currentThread().getName()+"买到了票:"+num);            num--;        }    }         public static void main(String[] args) {        TicketThread t1 = new TicketThread();                          t1.setName("窗口1");                          t1.start();        TicketThread t2 = new TicketThread();                        t2.setName("窗口2");                        t2.start();        System.out.println(t1.getClass()==t2.getClass());    }}

实现Runnable接口时,synchronized修饰非静态方法,锁标志的对象默认是this,但执行的任务本来就只有一个所以可以直接调用这个非静态方法。在修饰静态方法时,因为本来就是执行一个任务,因此是否使用静态方法都可以。

public class TicketDemo implements  Runnable {    int  num = 10;    @Override    public void run() {              while(true){                  try {                      ticket();                  } catch (InterruptedException e) {                      e.printStackTrace();                  }                  if(num<=0){                        break;                    }              }    }    /*       由于出票任务类,只创建了一个对象,所以两个线程拿到的this是同一个     */    public synchronized void ticket() throwsInterruptedException {        if(num>0){            Thread.sleep(1000);            System.out.println(Thread.currentThread().getName()+":"+num);            num--;        }    }            public static void main(String[] args) {        TicketDemo ticketDemo = new TicketDemo();        Thread t1 = new Thread(ticketDemo,"窗口1");        Thread t2 = new Thread(ticketDemo,"窗口2");        t1.start();        t2.start();    }}

一个线程持有锁会导致其他素有需要此锁的线程挂起;在多线程竞争下,加锁,释放锁会导致比较多的上下文切换和调度延时,引起性能问题。

Lock(锁)

•从JDK 5.0开始,Java提供了更强大的线程同步机制-通过显式定义同步锁对象

来实现同步。同步锁使用Lock对象充当。

•java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的

工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,

线程开始访问共享资源之前应先获得Lock对象。

ReentrantLock类实现了Lock,它拥有与synchronized相同的并发性和内存

语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加

锁、释放锁.

public class TicketThread extends Thread{     //static 修饰的变量是类变量,在内存中只有一份      static  int num = 10;      static Lock lock = new ReentrantLock();    @Override    public void run() {        while(true){               try {                   lock.lock();//加锁  锁标记在ReentrantLock对象维护                   Thread.sleep(100);                   if(num>0){                       System.out.println(Thread.currentThread().getName()+"买到了票:"+num);                       num--;                   }else{                       break;                   }               }catch (Exception e){               }finally {                   lock.unlock();//释放锁              }        }    }         public static void main(String[] args) {        TicketThread t1 = new TicketThread();                          t1.setName("窗口1");                          t1.start();        TicketThread t2 = new TicketThread();                        t2.setName("窗口2");                        t2.start();    }}

synchronized和Lock ReentrantLock的区别

synchronized是一个关键字,修饰代码块和方法,隐式的加锁和释放锁,底层实现需要靠虚拟机底层控制完成。

Lock ReentrantLock是一个实现类,实现了Lock接口中的方法,只能修饰代码块,并且是显示的加锁和释放锁,是通过Java代码来控制实现的。

线程死锁

死锁概念

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

以下模拟一段可能造成死锁的代码:

public class DieLockThread extends Thread{    static Object objA = new Object(); //a锁    static Object objB = new Object(); //b锁    boolean flag;//标记    public DieLockThread(boolean flag) {        this.flag = flag;    }    @Override    public void run() {         if(flag){             synchronized (objA){                 System.out.println("if objA");                 synchronized (objB){                     System.out.println("if objB");                 }             }         }else{             synchronized (objB){                 System.out.println("else objb");                 synchronized (objA){                     System.out.println("else obja");                 }             }         }    }    public static void main(String[] args) {        DieLockThread t1 = new DieLockThread(true);        DieLockThread t2 = new DieLockThread(false);        t1.start();        t2.start();    }}

线程通信

线程通讯指的是多个线程相互牵制,相互调度,即线程间对的相互作用,要实现这种牵制,前提需要在同步代码块中才能实现。

涉及三个方法:

.wait一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(相当于释放锁)。 因此wait()与sleep()的区别就在于 wait()属于Object类,线程执行该方法的同时会释放锁,进入到阻塞状态,唤醒需要使用notify()方法。sleep()方法属于Thread类,线程调用该方法进入休眠,但不会释放锁,一直等到休眠结束继续运行该线程。

.notify一旦执行此方法,就会唤醒被wait的一个线程。如果有多个线程被wait,就唤醒优先级高的那个。

.notifyAll一旦执行此方法,就会唤醒所有被wait的线程。

注意:

.wait(),notify(),notifyAll()三个方法必须使用在同步代码块或同步方法中。

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

生产者(Productor)将产品放在柜台(Counter),而消费者(Customer)从柜台处取走产品,生产者一次只能生产固定数量的产品(比如:1),这时柜台中不能 再放产品,此时生产者应停止生产等待消费者拿走产品,此时生产者唤醒消费者来取走产品,消费者拿走产品后,唤醒生产者,消费者开始等待。

/*  消费者线程 */public class CustomerThread extends  Thread{    Counter counter;    public CustomerThread(Counter counter) {        this.counter = counter;    }    @Override    public void run() {         while(true){             try {                 Thread.sleep(100);             } catch (InterruptedException e) {                 e.printStackTrace();             }             counter.minus();         }    }}
/*  生产者线程 */public class ProductorThread extends Thread{    Counter counter;    public ProductorThread(Counter counter) {        this.counter = counter;    }    @Override    public void run() {       while(true){           try {               Thread.sleep(100);           } catch (InterruptedException e) {               e.printStackTrace();           }           counter.plus();       }    }}
/*  只用一个柜台 里面有一个产品数量 */public class Counter {       int num = 1;       /*         生产者生产商品         由于只创建了一个Counter对象,synchronized修饰方法,同步锁对象时this,plus和minus 两个方法公用同一把锁        */       public synchronized void plus(){           if(num==0){               num++;               System.out.println("生产者生产商品:"+num);               this.notify();//生产者生产后,唤醒消费者           }else{               try {                   this.wait();//柜台有商品的,直接等待,释放锁               } catch (InterruptedException e) {                   e.printStackTrace();               }           }       }     /*         消费者取走商品        */       public synchronized void minus(){           if(num>0){               num--;               System.out.println("消费者拿走商品:"+num);               this.notify();//消费者拿走商品,唤醒生产者           }else{               try {                   this.wait();//没有商品,消费者等待,释放锁               } catch (InterruptedException e) {                   e.printStackTrace();               }           }       }}
public class Test {    public static void main(String[] args) {        Counter counter = new Counter();//两个线程共享的柜台        ProductorThread p = new ProductorThread(counter);        CustomerThread c = new CustomerThread(counter);        p.start();        c.start();    }}

新增创建线程方式

实现Callable接口与使用Runnable相比,Callable功能更强大些.

• 相比run()方法,可以有返回值

• 方法可以抛出异常 ,其他的创建线程的方式都需要try catch处理异常

• 支持泛型的返回值

• 需要借助FutureTask类,获取返回结果

接收任务

FutureTask<Integer> futureTask = new FutureTask(任务);

创建线程

Thread t = new Thread(futureTask);

t.start();

Integer val = futureTask.get();获得线程call方法的返回值

有问题希望大家可以提出来大家一起学习~~~

转载地址:https://blog.csdn.net/weixin_53086191/article/details/123362254 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:多线程之多个线程同时下载同一个资源文件
下一篇:多线程之内置锁原理简介

发表评论

最新留言

表示我来过!
[***.240.166.169]2024年04月04日 10时27分05秒