一、概述
多线程就是一个应用程序有多条执行路径
1.进程与线程
1)进程:
正在运行的程序,是系统进行资源分配和调用的独立单位。
每一个进程都有它自己的内存空间和系统资源。
2)线程:
是进程中的单个顺序控制流,是一条执行路径。
一个进程如果只有一条执行路径,则称为单线程程序。
一个进程如果有多条执行路径,则称为多线程程序。
2.多进程和多线程
1)意义:
单线程:一个应用程序只有一条执行路径
多线程:一个应用程序有多条执行路径
2)作用
多进程:提高CPU的使用率
多线程:提高应用程序的使用率
3.Java程序的运行原理
1)Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
4.线程Thread2)JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
构造方法:
public Thread()
public Thread(Runnable target)
public Thread(Runnable target, String name)
二、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(int newPriority):更改线程的优先级。
范围是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.运行问题
多线程共享资源时会发生问题。
程序会产生小于0的票数等问题。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(); } }
三、多线程安全问题
1.产生的原因
也是我们以后判断一个程序是否有线程安全问题的依据
1)是否有多线程环境
2)是否有共享数据
3)是否有多条语句操作共享数据
1)和2)的问题改变不了,所以只能想办法去把3)改变一下
2.同步解决线程安全问题
把多条语句操作共享数据的代码给包成一个整体,让某个线程在执行的时候,别人不能来执行。
问题是我们不知道怎么包啊?其实我也不知道,但是Java给我们提供了:同步机制。
1)同步代码块
synchronized(对象) {
需要被同步的代码;
}
这里的锁对象可以是任意对象。
2)同步方法
把同步加在方法上。
这里的锁对象是this
3)静态同步方法
把同步加在方法上。
这里的锁对象是当前类的字节码文件对象
4)注意:
同步可以解决安全问题的根本原因就在那个对象上。该对象如同锁的功能。
多个线程必须是同一把锁。
3.线程安全的一些类
StringBuffer,Vector,Hashtable
查看原码之后,发现这些类都加了线程锁
4.同步的弊端:把一个线程不安全的集合类变成一个线程安全的集合类的方法:
用Collections工具类的方法:public static <T> List<T> synchronizedList(List<T> list)
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 (InterruptedException e) { // 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 (InterruptedException e) { // 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 (InterruptedException e) { 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("if objA"); synchronized (MyLock.objB) { System.out.println("if objB"); } } } else { synchronized (MyLock.objB) { System.out.println("else objB"); 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 ExecutorService newFixedThreadPool(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(); } }