黑马程序员——多线程

来源:互联网 发布:c语言和java区别 编辑:程序博客网 时间:2024/06/10 20:45


——Java培训、Android培训、iOS培训、.Net培训、期待与您交流!—— 

一、概述

 

多线程就是一个应用程序有多条执行路径

 

1.进程与线程

1)进程:

正在运行的程序,是系统进行资源分配和调用的独立单位。

每一个进程都有它自己的内存空间和系统资源。

2)线程:

是进程中的单个顺序控制流,是一条执行路径。

一个进程如果只有一条执行路径,则称为单线程程序。

,则称为多线程程序。

 

2.多进程和多线程

1)意义:

单线程:一个应用程序只有一条执行路径

多线程:一个应用程序有多条执行路径

 

2)作用

多进程:提高CPU的使用率

多线程:提高应用程序的使用率

 

3.Java程序的运行原理

1)Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。

2)JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

 

4.线程Thread

构造方法:

public Thread()

public Thread(Runnable target)

public Thread(Runnable target, Stringname)

 

二、Thread详解

1.多线程的实现方案

1)继承Thread类

A:自定义类MyThread继承Thread类。

B:MyThread类里面重写run()

C::创建对象

D:启动线程:调用start();方法

run():仅仅是封装被线程执行的代码,直接调用是普通方法

start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

 

2)实现Runnable接口

A:自定义类MyRunnable实现Runnable接口

B:重写run()方法

C:创建MyRunnable类的对象

D:创建Thread类的对象,并把C步骤的对象作为构造参数传递

 

3)案例

继承Thread类:

class MyThread extends Thread { 

 

   @Override 

   public void run() { 

       // 自己写代码 

       for (int x = 0; x < 200; x++) { 

           System.out.println(x); 

       } 

   } 

 

 

public class MyThreadDemo { 

   public static void main(String[] args) { 

       // 创建线程对象 

       // MyThread my = new MyThread(); 

       // // 启动线程 

       // my.run(); 

       // my.run(); 

       // 调用run()方法为什么是单线程的呢? 

       // 因为run()方法直接调用其实就相当于普通的方法调用,所以你看到的是单线程的效果 

       // 要想看到多线程的效果,就必须说说另一个方法:start() 

       // 面试题:run()和start()的区别? 

       // run():仅仅是封装被线程执行的代码,直接调用是普通方法 

       // start():首先启动了线程,然后再由jvm去调用该线程的run()方法。 

       // MyThread my = new MyThread(); 

       // my.start(); 

       // // IllegalThreadStateException:非法的线程状态异常 

       // // 为什么呢?因为这个相当于是my线程被调用了两次。而不是两个线程启动。 

       // my.start(); 

 

       // 创建两个线程对象 

       MyThread my1 = new MyThread(); 

       MyThread my2 = new MyThread(); 

 

       my1.start(); 

       my2.start(); 

   } 

 

实现Runnable接口:

class MyRunnable implements Runnable { 

 

   @Override 

   public void run() { 

       for (int x = 0; x < 100; x++) { 

           // 由于实现接口的方式就不能直接使用Thread类的方法了,但是可以间接的使用 

           System.out.println(Thread.currentThread().getName() + ":" +x); 

       } 

   } 

 

 

public class MyRunnableDemo { 

   public static void main(String[] args) { 

       // 创建MyRunnable类的对象 

       MyRunnable my = new MyRunnable(); 

 

       // 创建Thread类的对象,并把C步骤的对象作为构造参数传递 

       // Thread(Runnable target) 

       // Thread t1 = new Thread(my); 

       // Thread t2 = new Thread(my); 

       // t1.setName("林青霞"); 

       // t2.setName("刘意"); 

 

       // Thread(Runnable target, String name) 

       Thread t1 = new Thread(my, "林青霞"); 

       Thread t2 = new Thread(my, "刘意"); 

 

       t1.start(); 

       t2.start(); 

    } 

 

2.线程的调度和优先级问题

1)线程的调度

A:分时调度:每个线程都被单独的分配一段时间进行执行

B:抢占式调度:线程随机的获取时间片段进行执行,Java采用的是该调度方式

2)获取和设置线程优先级

A:获取

public final int getPriority():返回线程对象的优先级

默认是5

B:设置

public final void setPriority(intnewPriority):更改线程的优先级。 

范围是1-10

 

3.线程的控制(常见方法)

1)休眠线程

public static void sleep(long millis):线程休眠millis毫秒

2)加入线程

public final void join():等待该线程终止。

3)礼让线程

public static void yield():暂停当前正在执行的线程对象,并执行其他线程。 

让多个线程的执行更和谐,但是不能靠它保证一人一次。

4)后台线程

public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。

当正在运行的线程都是守护线程时,Java 虚拟机退出。该方法必须在启动线程前调用。 

5)终止线程

public final void stop():让线程停止,过时了,但是还可以使用。

public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。

 

4.线程的生命周期

1)新建——2)就绪——3)运行——4)阻塞——5)死亡

 

5.运行问题

多线程共享资源时会发生问题。

public class SellTicket implements Runnable{ 

   // 定义100张票 

   private int tickets = 100; 

 

   @Override 

   public void run() { 

       while (true) { 

           if (tickets > 0) { 

               System.out.println(Thread.currentThread().getName()+ "正在出售第" 

                        + (tickets--) + "张票"); 

           } 

       } 

   } 

 

public class SellTicketDemo { 

   public static void main(String[] args) { 

       // 创建资源对象 

       SellTicket st = new SellTicket(); 

 

       // 创建三个线程对象 

       Thread t1 = new Thread(st, "窗口1"); 

       Thread t2 = new Thread(st, "窗口2"); 

       Thread t3 = new Thread(st, "窗口3"); 

 

       // 启动线程 

       t1.start(); 

       t2.start(); 

       t3.start(); 

   } 

}  

 

程序会产生小于0的票数等问题。

 

三、多线程安全问题

 

1.产生的原因

也是我们以后判断一个程序是否有线程安全问题的依据

1)是否有多线程环境

2)是否有共享数据

3)是否有多条语句操作共享数据

1)和2)的问题改变不了,所以只能想办法去把3)改变一下

 

2.同步解决线程安全问题

 

把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。

问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。

 

1)同步代码块

synchronized(对象) {

需要被同步的代码;

}

这里的锁对象可以是任意对象。

 

2)同步方法

把同步加在方法上。

这里的锁对象是this

 

3)静态同步方法

把同步加在方法上。

这里的锁对象是当前类的字节码文件对象

 

4)注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
多个线程必须是同一把锁。

 

3.线程安全的一些类

StringBuffer,Vector,Hashtable

查看原码之后,发现这些类都加了线程锁

把一个线程不安全的集合类变成一个线程安全的集合类的方法:

用Collections工具类的方法:public static <T> List<T> synchronizedList(List<T>list)

 

4.同步的弊端:

1)效率低,线程经常处于等待状态
2)容易产生死锁

 

5.解决了线程同步问题的案例

public class SellTicket implements Runnable{ 

 

   // 定义100张票 

   private static int tickets = 100; 

 

   // 定义同一把锁 

   private Object obj = new Object(); 

   private Demo d = new Demo(); 

 

   private int x = 0; 

     

   //同步代码块用obj做锁 

// @Override 

// public void run() { 

//     while (true) { 

//         synchronized (obj) { 

//              if (tickets > 0) { 

//                  try { 

//                      Thread.sleep(100); 

//                  } catch (InterruptedExceptione) { 

//                      e.printStackTrace(); 

//                  } 

//                 System.out.println(Thread.currentThread().getName() 

//                         + "正在出售第" +(tickets--) + "张票 "); 

//              } 

//         } 

//     } 

// } 

     

   //同步代码块用任意对象做锁 

// @Override 

// public void run() { 

//     while (true) { 

//         synchronized (d) { 

//              if (tickets > 0) { 

//                  try { 

//                      Thread.sleep(100); 

//                  } catch (InterruptedExceptione) { 

//                      e.printStackTrace(); 

//                  } 

//                 System.out.println(Thread.currentThread().getName() 

//                          + "正在出售第" +(tickets--) + "张票 "); 

//              } 

//         } 

//     } 

// } 

     

   @Override 

   public void run() { 

       while (true) { 

           if(x%2==0){ 

                synchronized (SellTicket.class){ 

                    if (tickets > 0) { 

                        try { 

                           Thread.sleep(100); 

                        } catch (InterruptedExceptione) { 

                           e.printStackTrace(); 

                        } 

                       System.out.println(Thread.currentThread().getName() 

                                + "正在出售第" +(tickets--) + "张票 "); 

                    } 

                } 

           }else { 

//              synchronized (d) { 

//                  if (tickets > 0) { 

//                      try { 

//                         Thread.sleep(100); 

//                      } catch(InterruptedException e) { 

//                         e.printStackTrace(); 

//                      } 

//                     System.out.println(Thread.currentThread().getName() 

//                              + "正在出售第" +(tickets--) + "张票 "); 

//                  } 

//              } 

                 

                sellTicket(); 

                 

           } 

           x++; 

       } 

   } 

 

// private void sellTicket() { 

//     synchronized (d) { 

//         if (tickets > 0) { 

//         try { 

//                  Thread.sleep(100); 

//         } catch (InterruptedException e) { 

//                  e.printStackTrace(); 

//         } 

//         System.out.println(Thread.currentThread().getName() 

//                      + "正在出售第" +(tickets--) + "张票 "); 

//         } 

//     } 

// } 

     

   //如果一个方法一进去就看到了代码被同步了,那么我就再想能不能把这个同步加在方法上呢? 

//  private synchronized void sellTicket() { 

//         if (tickets > 0) { 

//         try { 

//                  Thread.sleep(100); 

//         } catch (InterruptedException e) { 

//                  e.printStackTrace(); 

//         } 

//         System.out.println(Thread.currentThread().getName() 

//                      + "正在出售第" +(tickets--) + "张票 "); 

//         } 

// } 

     

   private static synchronized void sellTicket() { 

       if (tickets > 0) { 

       try { 

                Thread.sleep(100); 

       } catch (InterruptedException e) { 

                e.printStackTrace(); 

       } 

       System.out.println(Thread.currentThread().getName() 

                    + "正在出售第" +(tickets--) + "张票 "); 

       } 

 

public class SellTicketDemo { 

   public static void main(String[] args) { 

       // 创建资源对象 

       SellTicket st = new SellTicket(); 

 

       // 创建三个线程对象 

       Thread t1 = new Thread(st, "窗口1"); 

       Thread t2 = new Thread(st, "窗口2"); 

       Thread t3 = new Thread(st, "窗口3"); 

 

       // 启动线程 

       t1.start(); 

       t2.start(); 

       t3.start(); 

   } 

 

四、死锁问题

 

1.死锁问题的描述和代码体现

两个或两个以上的线程在争夺资源的过程中,发生的一种相互等待的现象。

public class MyLock { 

   // 创建两把锁对象 

   public static final Object objA = new Object(); 

   public static final Object objB = new Object(); 

 

public class DieLock extends Thread { 

 

   private boolean flag; 

 

   public DieLock(boolean flag) { 

       this.flag = flag; 

   } 

 

   @Override 

   public void run() { 

       if (flag) { 

           synchronized (MyLock.objA) { 

                System.out.println("ifobjA"); 

                synchronized (MyLock.objB){ 

                    System.out.println("ifobjB"); 

                } 

           } 

       } else { 

           synchronized (MyLock.objB) { 

               System.out.println("elseobjB"); 

                synchronized (MyLock.objA){ 

                   System.out.println("else objA"); 

                } 

           } 

       } 

   } 

 

public class DieLockDemo { 

    public static void main(String[] args){ 

       DieLock dl1 = new DieLock(true); 

       DieLock dl2 = new DieLock(false); 

 

       dl1.start(); 

       dl2.start(); 

   } 

}

 

2.等待唤醒:

Object类中提供了三个方法:

wait():等待

notify():唤醒单个线程

notifyAll():唤醒所有线程

 

3.生产者和消费者多线程体现(线程间通信问题)

以学生作为资源来实现

资源类:Student

设置数据类:SetThread(生产者)

获取数据类:GetThread(消费者)

测试类:StudentDemo

 

wait()

notify()

notifyAll() (多生产多消费)

class Student { 

   String name; 

   int age; 

   boolean flag; // 默认情况是没有数据,如果是true,说明有数据 

 

class SetThread implements Runnable { 

 

   private Student s; 

   private int x = 0; 

 

   public SetThread(Student s) { 

       this.s = s; 

   } 

 

   @Override 

   public void run() { 

       while (true) { 

           synchronized (s) { 

                //判断有没有 

                if(s.flag){ 

                    try { 

                        s.wait(); //t1等着,释放锁 

                    } catch(InterruptedException e) { 

                        e.printStackTrace(); 

                    } 

                } 

                 

                if (x % 2 == 0) { 

                    s.name = "林青霞"; 

                    s.age = 27; 

                } else { 

                    s.name = "冯佳"; 

                    s.age = 30; 

                } 

                x++; //x=1 

                 

                //修改标记 

                s.flag = true; 

                //唤醒线程 

                s.notify(); //唤醒t2,唤醒并不表示你立马可以执行,必须还得抢CPU的执行权。 

           } 

           //t1有,或者t2有 

       } 

   } 

 

class GetThread implements Runnable { 

   private Student s; 

 

   public GetThread(Student s) { 

       this.s = s; 

   } 

 

   @Override 

    public void run() { 

       while (true) { 

           synchronized (s) { 

                if(!s.flag){ 

                    try { 

                        s.wait(); //t2就等待了。立即释放锁。将来醒过来的时候,是从这里醒过来的时候 

                    } catch(InterruptedException e) { 

                       e.printStackTrace(); 

                    } 

                } 

                 

                System.out.println(s.name +"---" + s.age); 

                //林青霞---27  

                //冯佳---30 

                 

                //修改标记 

                s.flag = false; 

                //唤醒线程 

                s.notify(); //唤醒t1 

           } 

       } 

   } 

 

public class StudentDemo { 

   public static void main(String[] args) { 

       //创建资源 

       Student s = new Student(); 

         

       //设置和获取的类 

       SetThread st = new SetThread(s); 

       GetThread gt = new GetThread(s); 

 

       //线程类 

       Thread t1 = new Thread(st); 

       Thread t2 = new Thread(gt); 

 

       //启动线程 

       t1.start(); 

       t2.start(); 

   } 

 

 

4.线程组

把多个线程组合到一起。

它可以对一批线程进行分类管理,Java允许程序直接对线程组进行控制。

具体实现代码如下

public class ThreadGroupDemo { 

   public static void main(String[] args) { 

       // method1(); 

 

       // 我们如何修改线程所在的组呢? 

       // 创建一个线程组 

       // 创建其他线程的时候,把其他线程的组指定为我们自己新建线程组 

       method2(); 

 

       // t1.start(); 

       // t2.start(); 

   } 

 

   private static void method2() { 

       // ThreadGroup(String name) 

       ThreadGroup tg = new ThreadGroup("这是一个新的组"); 

 

       MyRunnable my = new MyRunnable(); 

       // Thread(ThreadGroup group, Runnable target, String name) 

       Thread t1 = new Thread(tg, my, "林青霞"); 

       Thread t2 = new Thread(tg, my, "冯佳"); 

         

       System.out.println(t1.getThreadGroup().getName()); 

       System.out.println(t2.getThreadGroup().getName()); 

         

       //通过组名称设置后台线程,表示该组的线程都是后台线程 

       tg.setDaemon(true); 

   } 

 

   private static void method1() { 

       MyRunnable my = new MyRunnable(); 

       Thread t1 = new Thread(my, "林青霞"); 

       Thread t2 = new Thread(my, "冯佳"); 

       // 我不知道他们属于那个线程组,我想知道,怎么办 

       // 线程类里面的方法:public final ThreadGroup getThreadGroup() 

       ThreadGroup tg1 = t1.getThreadGroup(); 

       ThreadGroup tg2 = t2.getThreadGroup(); 

       // 线程组里面的方法:public final String getName() 

       String name1 =tg1.getName(); 

       String name2 = tg2.getName(); 

       System.out.println(name1); 

       System.out.println(name2); 

       // 通过结果我们知道了:线程默认情况下属于main线程组 

       // 通过下面的测试,你应该能够看到,默任情况下,所有的线程都属于同一个组 

       System.out.println(Thread.currentThread().getThreadGroup().getName()); 

   } 

 

5.线程池(实现多线程的第三种方案)

线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。

1)线程池的实现

A:创建一个线程池对象,控制要创建几个线程对象。

public static ExecutorServicenewFixedThreadPool(int nThreads)

B:可以执行线程池的线程:

可以执行Runnable对象或者Callable对象代表的线程

做一个类实现Runnable接口。

C:调用如下方法即可实现线程的执行

Future<?> submit(Runnable task)

<T> Future<T>submit(Callable<T> task)

D:线程池也可以结束。

shutdown()

2)案例

 //Callable:是带泛型的接口。 

//这里指定的泛型其实是call()方法的返回值类型。 

public class MyCallable implements Callable{ 

 

   @Override 

   public Object call() throws Exception { 

       for (int x = 0; x < 100; x++) { 

           System.out.println(Thread.currentThread().getName() + ":" +x); 

       } 

       return null; 

   } 

 

 

public class ExecutorsDemo { 

   public static void main(String[] args) { 

       // 创建一个线程池对象,控制要创建几个线程对象。 

       // public static ExecutorService newFixedThreadPool(int nThreads) 

       ExecutorService pool = Executors.newFixedThreadPool(2); 

 

       // 可以执行Runnable对象或者Callable对象代表的线程 

       pool.submit(new MyCallable()); 

       pool.submit(new MyCallable()); 

 

       //结束线程池 

       pool.shutdown(); 

   } 

}


0 0