——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();
}
}