一.进程
说到多线程,线程是依赖于进程存在的,所以我们首先应该想到是进程。
A:什么是进程?
答:进程就是正在运行的应用程序,进程与线程的区别就是,进程是系统进行资源分配和调用的最小单位。而线程是程序运行的最小单位,即使用cpu的最小单位。一旦进程被创建,就会有自己的内存空间和系统资源。
B:多进程的意义?
答:如果一个计算机是单进程计算机,那么这个计算机只能处理一个进程,但是实际生活中我们通常需要计算同时完成多项功能比如一边听音乐一边打游戏等等,这就需要多进程计算机,这些进程在高速的切换使用cpu,因此在宏观上是并行,实际在使用cpu上是串行的,提高了cpu的使用率。
二.多线程
A:什么是线程?
答:一个进程的内部又可以执行多个任务,每一个任务都可以看做是一个线程,线程是程序使用cpu的最小单位。
B:多线程的意义?
答:多线程的意义不是为了提高cpu的使用率,而是为了提高程序的使用率。我们可以这样理解,程序的执行就是在抢占cpu,多线程程序在运行时,那么抢到cpu的几率就会更大一些,那么cpu在多线程进程中使用的时间就会比单线程高一些,因此程序的使用率就会高一些,但是那个线程抢到cpu,这个是不确定的,因为多线程具有随机性。
C:Java程序的运行原理
答:java命令会启动Java虚拟机,即jvm,也即启动了一个应用程序,也就是启动一个进程,该进程会启动一个主线程,该主线程会调用某个类的main方法,因此main方法运行在主线程中。
问:jvm的启动是多线程吗?
答:是多线程,因为jvm的启动至少会启动一个主线程和一个垃圾回收线程,因此是多线程。
三.多线程的实现方式一
A:问:怎么实现多线程?
答:继承Thread类,该类是一个线程类。
B:1.问:启动线程使用的是那个方法?
答:使用的是start()方法
2.问:线程能不能多次启动?
答:不能,会发生异常
3.问:为什么要重写run方法?
答:run方法中封装的是多线程要执行的代码,该类是线程类,在这个类中,也可以写一些其他的方法,但是其他的方法封装的代码不都是需要多线程所执行的代码,因此我们必须重写run方法,run方法中封装的代码应该是多线程必须执行的代码。
run方法的书写规则:一般封装的是比较耗时的代码。
C:案例演示
1.获取和设置线程对象名称
a.public final String getName()//获取线程名称
public final void setName(String name)//设置线程名称b.获取和设置线程对象名称
public class Test {
public static void main(String[] args) {
//获取当前主线程的名称
Thread thread = Thread.currentThread();
System.out.println(thread.getName());//main
//自己也可以修改主线程的名字
thread.setName("主线程");
System.out.println(thread.getName());//主线程
MyThread th1=new MyThread();
th1.setName("线程一");
MyThread th2=new MyThread();
th2.setName("线程二");
//开启一个线程,注意不能使用对象去调用方法来开启一个线程,即不能直接调用run方法。
th1.start();
th2.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<100;i++){
//方法一,获取当前正在运行的进程对象,然后调用getName()来获取进程名字
// Thread thread = Thread.currentThread();
//System.out.println(thread.getName());
System.out.println(this.getName()+i);
}
}
}
2.线程调度及获取和设置线程优先级
a.public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级
b.线程的调度:分为分时调度和抢占式调度
分时调度:平均分配cpu的时间片是相同的,每个线程轮流的来使用cpu。
抢占式调度: 优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的 CPU 时间片相对多一些。
注:java中使用的是抢占式调度,那么优先级高的线程是不是就意味着,会严格按照这个优先级来执行线程,不是这样的,因为优先级只是说明该线程使用cpu的几率很大,但是多线程具有随机性,因此我们会看到有些优先级低的线程在优先级高的线程之前执行。
c.获取和设置线程优先级
public class Test {
public static void main(String[] args) {
MyThread th1 = new MyThread();
th1.setName("刘昊然");
MyThread th2 = new MyThread();
th2.setName("王宝强");
MyThread th3 = new MyThread();
th3.setName("陈思成");
//获取线程默认的优先级 5
System.out.println(th1.getPriority());
System.out.println(th2.getPriority());
System.out.println(th3.getPriority());
//重新设置线程的优先级 范围为1--10
// th1.setPriority(1000);IllegalArgumentException非法的参数异常
th2.setPriority(5);
th3.setPriority(3);
System.out.println(th1.getPriority());
System.out.println(th2.getPriority());
System.out.println(th3.getPriority());
th1.start();
th2.start();
th3.start();
}
}
public class MyThread extends Thread{
@Override
public void run() {
super.run();
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"-----"+i);
}
}
}
注:线程的优先级的范围为1-10,默认的线程的优先级为5,自己定义线程优先级的时候如果超过了范围会出现异常(
IllegalArgumentException非法参数异常)
3.线程休眠
a.public static void sleep(long millis) 线程休眠
b.线程休眠
public class Mythread extends Thread{
@Override
public void run() {
super.run();
//线程休眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
public class Test {
public static void main(String[] args) {
Mythread th1=new Mythread();
th1.start();
}
}
4.加入线程
a.public final void join()//就是该线程先执行,其他线程必须等到线程执行完成后才能执行
b.加入线程
public class Test {
public static void main(String[] args) {
MyThread th1 = new MyThread();
th1.setName("刘昊然");
MyThread th2 = new MyThread();
th2.setName("王宝强");
MyThread th3 = new MyThread();
th3.setName("陈思成");
th1.start();
//加入进程
try {
th1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
th2.start();
th3.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<10;i++) {
System.out.println(Thread.currentThread().getName() + "-----" + i);
}
}
}
结果:
刘昊然-----0
刘昊然-----1
刘昊然-----2
刘昊然-----3
刘昊然-----4
刘昊然-----5
刘昊然-----6
刘昊然-----7
刘昊然-----8
刘昊然-----9
王宝强-----0
陈思成-----0
陈思成-----1
陈思成-----2
陈思成-----3
陈思成-----4
陈思成-----5
陈思成-----6
陈思成-----7
陈思成-----8
陈思成-----9
王宝强-----1
王宝强-----2
王宝强-----3
王宝强-----4
王宝强-----5
王宝强-----6
王宝强-----7
王宝强-----8
王宝强-----9
注:加入线程必须在程序启动之后再调用该方法。
5.礼让线程
a.public static void yield()//暂停当前正在执行的线程对象,并执行其他线程。
b.礼让线程
public class MyTest6 {
public static void main(String[] args) {
MyThread6 th1= new MyThread6();
MyThread6 th2= new MyThread6();
th1.setName("张三");
th2.setName("李四");
th1.start();
th2.start();
}
}
public class MyThread6 extends Thread{
@Override
public void run() {
super.run();
//线程礼让
Thread.yield();
for(int i=0;i<100;i++){
System.out.println(this.getName()+"==="+i);
}
}
}
截取部分结果:
public class MyThread6 extends Thread{
@Override
public void run() {
super.run();
//线程礼让
Thread.yield();
for(int i=0;i<100;i++){
System.out.println(this.getName()+"==="+i);
}
}
}
问:按照礼让的理解,张三和李四应该是每人执行一次,但是为什么实际运行中不是我们所想的样子?
答:礼让线程是要暂停当前正在执行的线程,这个时间是非常短的,如果在这个时间段内,其他的线程还没有抢到cpu的使用权,那么该线程就会和其他线程一起来继续抢占使用cpu,因此会出现我们看到的现象。即张三处理多次或者李四处理多次。
6.守护线程
a.public final void setDaemon(boolean on)//当正在运行的线程都是守护线程时,Java 虚拟机退出
b.守护线程
public class Test {
public static void main(String[] args) {
System.out.println("我是主线程贝贝");
System.out.println("我是主线程西西");
// public final void setDaemon(boolean on)
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.setName("刘昊然");
th2.setName("王宝强");
//定义为守护线程
th1.setDaemon(true);
th2.setDaemon(true);
th1.start();
th2.start();
}
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
for(int i=0;i<100;i++){
System.out.println(Thread.currentThread().getName()+"---"+i);
}
}
}
结果:
我是主线程贝贝
我是主线程西西
注:该方法必须在启动线程前调用,有时守护线程不会立马死掉,可能还会执行一段时间。
7.中断线程
a.public final void stop()//停止线程的运行
public void interrupt()//中断线程,打断线程的阻塞状态, 它还将收到一个 InterruptedException。
b.中断线程
public class Test {
public static void main(String[] args) {
MyThread th = new MyThread();
th.start();
//打断线程的一个阻塞状态,会抛出一个异常
th.interrupt();
}
}
public class MyThread extends Thread {
@Override
public void run() {
super.run();
System.out.println("程序开始执行了");
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println(i);
// if (i == 5) {
// this.stop();
// }
}
System.out.println("程序执行完了");
}
}
结果:
程序开始执行了
0
1
2
3
4
5
6
7
8
9
程序执行完了
java.lang.InterruptedException: sleep interrupted
四.多线程程序实现的方式2
public class Test {
public static void main(String[] args) {
MyThread th = new MyThread();
//方法一 直接在参数中定义
Thread th1 = new Thread(th, "西西");
Thread th2 = new Thread(th, "贝贝");
//方法二 调用setName(Sring s)方法
Thread th3 = new Thread(th);
th3.setName("哈哈");
th1.start();
th2.start();
th3.start();
}
}
public class MyThread implements Runnable{
@Override
public void run() {
for (int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+"===="+i);
}
}
}
西西====0
西西====1
西西====2
西西====3
西西====4
西西====5
西西====6
西西====7
西西====8
西西====9
贝贝====0
贝贝====1
贝贝====2
贝贝====3
贝贝====4
贝贝====5
贝贝====6
贝贝====7
贝贝====8
贝贝====9
哈哈====0
哈哈====1
哈哈====2
哈哈====3
哈哈====4
哈哈====5
哈哈====6
哈哈====7
哈哈====8
哈哈====9
B:某电影院目前正在上映贺岁大片,共有100张票,而它有3个售票窗口售票,请设计一个程序模拟该电影院售票
分析: a.3个售票窗口售票,三个售票窗口要共享一个变量100,因此可以用三个线程来表示
b. 售票时网络是不能实时传输的,总是存在延迟的情况,所以,在出售一张票以后,需要一点时间的延迟
c.为了防止多个线程出售同一张票,我们可以加入同步代码块,来解决这问题,关键字为synchronized(obj)
意味着只要一个线程先抢到cpu,此时它就会加锁,别的进程进不来,当它处理完自己的事情时,他才会释放这个锁对象, 然后继续和其他的线程来争抢cpu,为了保证数据的安全性,我们必须把这个锁对象定义成共享变量,保证每个进程使用相 同的锁,不然每个进程都有其自己的锁,数据还是会出现不安全。
案例:
public class MyThread extends Thread{
//定义一个静态共享变量,多个线程共享此数据
public static int num=100;
//定义一个锁,锁对象要共享
public static final Object obj=new Object();
@Override
public void run() {
super.run();
while(true) {
synchronized (obj) {//同步代码块 参数就是一个锁对象
//单线程环境
try {
//模拟网络延迟
Thread.sleep(1000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(num>0) {
System.out.println(Thread.currentThread().getName()+"正在出售"+num--+"张票");
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
MyThread th3 = new MyThread();
th1.setName("一号售票厅");
th2.setName("二号售票厅");
th3.setName("三号售票厅");
th1.start();
th2.start();
th3.start();
}
}
C:线程安全问题的产生原因分析
a.判断一个多线程应用程序是否有问题的标准
1.是否存在多线程环境
2.是否存在共享数据
3.是否存在代码操作共享数据
b.那么我们该如何解决这个数据安全问题呢?
答:首先肯定是要保证让程序处在没有安全问题的环境,可以把那些对操作共享数据的代码封锁起来,保证在某个时刻只有一个线程在操作
同步代码块的格式:
格式:
synchronized(对象){
要被同步的代码 ;
}
注意:同步代码块保证安全性的主要因素就是这个对象,这个对象必须被定义为静态成员变量,才能被多个线程共享,这个对象其实就是一把锁,也就是监视器。
D:同步代码块的锁问题以及同步方法的应用和锁问题
a. 同步代码块的锁对象: 任意一个对象
同步方法的锁对象: 是this
静态同步方法的锁对象: 就是当前类对应的字节码文件对象
public class MyThread implements Runnable{
// 同步代码块的锁对象: 任意一个对象
// 同步方法的锁对象: 是this
// 静态同步方法的锁对象:就是当前类对应的字节码文件对象
private static int tickets = 100 ;
private static final Object obj = new Object() ;
private int n = 0 ;
@Override
public void run() {
// 卖票
while(true){
if( n % 2 == 0 ){
synchronized (MyThread.class) {
if( tickets > 0 ){
// 模拟网络延迟
try {
Thread.sleep(100) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) + "张票");
}
}
}else {
sellTickets() ;
//sellpiao();
//sellpiao2();
}
n++ ;
}
}
public void sellTickets(){
synchronized (obj) {//this//MyThread.class
if( tickets > 0 ){
// 模拟网络延迟
try {
Thread.sleep(100) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) + "张票");
}
}
}
//同步方法 同步方法的锁对象: 是this
public synchronized void sellpiao() {
if( tickets > 0 ){
// 模拟网络延迟
try {
Thread.sleep(100) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) + "张票");
}
}
//静态同步方法 静态同步方法的锁对象:就是当前类对应的字节码文件对象
public static synchronized void sellpiao2() {
if( tickets > 0 ){
// 模拟网络延迟
try {
Thread.sleep(100) ;
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售" + (tickets--) + "张票");
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread th1 = new Thread(myThread,"售票窗口一");
Thread th2 = new Thread(myThread,"售票窗口二");
Thread th3 = new Thread(myThread,"售票窗口三");
th1.start();
th2.start();
th3.start();
}
}
b.说了这么多,那么同步的优点和缺点是什么?
答:优点:解决了数据安全问题
缺点:当线程数很大时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
五.Lock锁
A:
a.虽然我们可以理解同步代码块和同步方法的锁对象问题,但是我们并没有直接看到在哪里加上了锁,在哪里释放了锁,为了更清晰的表达如何加锁和释放锁,JDK5以后提供了一个新的锁对象Lock
b.Lock和ReentrantLock
Lock是一个接口,ReentrantLock是一个实现Lock接口的类
c.void lock()//加锁
void unlock()//解锁
d.案例演示
public class Test {
public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread th1 = new Thread(myThread,"售票窗口一");
Thread th2 = new Thread(myThread,"售票窗口二");
Thread th3 = new Thread(myThread,"售票窗口三");
th1.start();
th2.start();
th3.start();
}
}
public class MyThread implements Runnable {
//定义一个共享变量
public static int num=100;
@Override
public void run() {
ReentrantLock lock = new ReentrantLock();
while(true) {
lock.lock();//加锁
//模拟网络延迟
try {
Thread.sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if(num>0) {
System.out.println(Thread.currentThread().getName()+"正在出售"+num--+"张票");
}
//解锁
lock.unlock();
}
}
}
六.死锁
A:死锁概述:
如果出现了同步嵌套,就容易产生死锁问题
是指两个或者两个以上的线程在执行的过程中,因争夺资源产生的一种互相等待现象
同步代码块的嵌套案例
死锁: 两个或者两个以上的线程,在抢占CPU的执行权的时候,都处于等待状态
B:
public interface MyLock {
public static Object oa=new Object();
public static Object ob=new Object();
}
public class MyThread extends Thread {
boolean flag=true;
public MyThread(boolean o) {
super();
this.flag=o;
}
public void run() {
// TODO Auto-generated method stub
super.run();
if (flag) {
synchronized (MyLock.oa) {
System.out.println("第一层进来了" + "A");
synchronized (MyLock.ob) {
System.out.println("第二层进来了" + "B");
}
}
} else {
synchronized (MyLock.ob) {
System.out.println("第一层进来了" + "B");
synchronized (MyLock.oa) {
System.out.println("第二层进来了" + "A");
}
}
}
}
}
public class Test {
public static void main(String[] args) {
MyThread th1 = new MyThread(true);
MyThread th2 = new MyThread(false);
th1.start();
th2.start();
}
}