黑马程序员—Java基础—多线程

来源:互联网 发布:linux 查看ftp用户 编辑:程序博客网 时间:2024/05/21 23:55
-----------android培训java培训、java学习型技术博客、期待与您交流!------------ 
多线程
多线程概述
进程:正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
多进程的意义:可以提高CPU的使用率。
线程:进程的执行单元,执行路径。
单线程:一个应用程序只有一条执行路径。
多线程:一个应用程序有多条执行路径
多线程的意义:提高应用程序的使用率。

Java程序运行原理

java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。

思考: jvm虚拟机的启动是单线程的还是多线程的?

答:A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。

多线程的实现方案

方式一:

继承Thread类。

步骤:

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

B:MyThread类里面重写run()?

C:创建对象

D:启动线程

代码:

 多线程的实现方式一 22行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22
//定义类继承自Thread类
class MyThread extends Thread {

@Override
//重写run()方法
public void run() {
for (int x = 0; x < 200; x++) {
System.out.println(x);
}
}
}
public class MyThreadDemo {
public static void main(String[] args) {
// 创建两个线程对象
//创建线程
MyThread my1 = new MyThread();
MyThread my2 = new MyThread();
//启动线程
my1.start();
my2.start();
}
}
思考:为什么B步骤需要重写run()方法?

答:不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,java提供了Thread类中的run()用来包含那些被线程执行的代码。

run()和start()的区别?

答:  run():仅仅是封装被线程执行的代码,直接调用是普通方法
 start():首先启动了线程,然后再由jvm去调用该线程的run()方法。

线程不能多次启动,否则会报错(IllegalThreadStateException:非法的线程状态异常)

获取和设置线程名称
Thread类的基本获取和设置方法 
public final String getName()
public final void setName(String name)
其实通过构造方法也可以给线程起名字 
public static Thread currentThread()
这样就可以获取任意方法(包括main方法所在的main线程)所在的线程名称 

代码:

 设置、获取线程的名称 29行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
/*
* public final String getName():获取线程的名称。
* public final void setName(String name):设置线程的名称
*
* public static Thread currentThread():返回当前正在执行的线程对象
* Thread.currentThread().getName()
*/
public class MyThreadDemo {
public static void main(String[] args) {
// 创建线程对象
//无参构造+setXxx()
// MyThread my1 = new MyThread();
// MyThread my2 = new MyThread();
// //调用方法设置名称
// my1.setName("陈奕迅");
// my2.setName("周杰伦");
// my1.start();
// my2.start();
//带参构造方法给线程起名字
// MyThread my1 = new MyThread("陈奕迅");
// MyThread my2 = new MyThread("周杰伦");
// my1.start();
// my2.start();
//public static Thread currentThread():返回当前正在执行的线程对象
System.out.println(Thread.currentThread().getName());
}
}
方式二:

实现Runnable接口

步骤:

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

B:重写run()方法

C:创建MyRunnable类的对象

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

代码:

 多线程的实现方式二 33行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
//定义类实现Runnable接口
class MyRunnable implements Runnable {

@Override
//重写run()方法
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();
}
}

为什么要有Runnable接口的出现?

1:通过继承Thread类的方式,可以完成多线程的建立。但是这种方式有一个局限性,如果一个类已经有了自己的父类,就不可以继承Thread类,因为java单继承的局限性。可是该类中的还有部分代码需要被多个线程同时执行。这时怎么办呢?只有对该类进行额外的功能扩展,java就提供了一个接口Runnable。这个接口中定义了run方法,其实run方法的定义就是为了存储多线程要运行的代码。所以,通常创建线程都用第二种方式。因为实现Runnable接口可以避免单继承的局限性。

2:其实是将不同类中需要被多线程执行的代码进行抽取。将多线程要运行的代码的位置单独定义到接口中。为其他类进行功能扩展提供了前提。

所以Thread类在描述线程时,内部定义的run方法,也来自于Runnable接口。实现Runnable接口可以避免单继承的局限性。而且,继承Thread,是可以对Thread类中的方法,进行子类复写的。但是不需要做这个复写动作的话,只为定义线程代码存放位置,实现Runnable接口更方便一些。所以Runnable接口将线程要执行的任务封装成了对象。

线程调度

线程有两种调度模型:

分时调度模型   所有线程轮流使用 CPU 的使用权,平均分配每个线程占用CPU 的时间片

抢占式调度模型   优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个,优先级高的线程获取的CPU 时间片相对多一些。

Java使用的是抢占式调度模型。

设置和获取线程优先级

public final intgetPriority()

public finalvoid setPriority(int newPriority)

代码:

 设置和获取线程优先级 40行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40
//定义类继承自Thread
class ThreadPriority extends Thread {
@Override
public void run() { //重写run()方法
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*
* public final int getPriority():返回线程对象的优先级
* public final void setPriority(int newPriority):更改线程的优先级。
*/
public class ThreadPriorityDemo {
public static void main(String[] args) {
ThreadPriority tp1 = new ThreadPriority();
ThreadPriority tp2 = new ThreadPriority();
ThreadPriority tp3 = new ThreadPriority();

tp1.setName("陈奕迅");
tp2.setName("周杰伦");
tp3.setName("田馥甄");

// 获取默认优先级
// System.out.println(tp1.getPriority());
// System.out.println(tp2.getPriority());
// System.out.println(tp3.getPriority());

// 设置线程优先级
// tp1.setPriority(100000);
//设置正确的线程优先级
tp1.setPriority(10);
tp2.setPriority(1);

tp1.start();
tp2.start();
tp3.start();
}
}
注意:

线程默认优先级是5。

线程优先级的范围是:1-10。(设置如果不在这个范围内,则会报IllegalArgumentException:非法参数异常。)

线程优先级高仅仅表示线程获取的 CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到比较好的效果。

线程控制

线程休眠

     publicstatic void sleep(long millis)     在指定的毫秒数内让当前正在执行的线程休眠(暂停执行)。

代码:

 线程控制—线程休眠 32行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
import java.util.Date;

class ThreadSleep extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x + ",日期:" + new Date());
// 线程睡眠
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/*
* 线程休眠
*public static void sleep(long millis)
*/
public class ThreadSleepDemo {
public static void main(String[] args) {
ThreadSleep ts1 = new ThreadSleep();
ThreadSleep ts2 = new ThreadSleep();
ts1.setName("陈奕迅");
ts2.setName("周杰伦");

ts1.start();
ts2.start();
}
}

线程加入

public final void join()  等待该线程终止后执行其他线程

代码:

 线程控制之—线程加入 32行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32
public class ThreadJoin extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*
* public final void join():等待该线程终止。
*/
public class ThreadJoinDemo {
public static void main(String[] args) {
ThreadJoin tj1 = new ThreadJoin();
ThreadJoin tj2 = new ThreadJoin();
ThreadJoin tj3 = new ThreadJoin();

tj1.setName("朱元璋");
tj2.setName("朱标");
tj3.setName("朱棣");

tj1.start();
try {
tj1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
//执行完线程"朱元璋"后才执行"朱标"和"朱棣";
tj2.start();
tj3.start();
}
}

线程礼让

public static void yield()

代码:

 线程控制—线程礼让 15行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
/*
* public static void yield():暂停当前正在执行的线程对象,并执行其他线程。
*/
public class ThreadYieldDemo {
public static void main(String[] args) {
ThreadYield ty1 = new ThreadYield();
ThreadYield ty2 = new ThreadYield();

ty1.setName("陈奕迅");
ty2.setName("周杰伦");

ty1.start();
ty2.start();
}
}

后台线程

public final void setDaemon(boolean on)

代码:

 线程控制—后台线程 33行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
class ThreadDaemon extends Thread {
@Override
public void run() {
for (int x = 0; x < 100; x++) {
System.out.println(getName() + ":" + x);
}
}
}
/*
* public final void setDaemon(boolean on):将该线程标记为守护线程或用户线程。
* 当正在运行的线程都是守护线程时,Java 虚拟机退出。 该方法必须在启动线程前调用。
*/
public class ThreadDaemonDemo {
public static void main(String[] args) {
ThreadDaemon td1 = new ThreadDaemon();
ThreadDaemon td2 = new ThreadDaemon();

td1.setName("徐达");
td2.setName("常遇春");

// 设置收获线程
td1.setDaemon(true);
td2.setDaemon(true);

td1.start();
td2.start();

Thread.currentThread().setName("朱元璋");
for (int x = 0; x < 5; x++) {
System.out.println(Thread.currentThread().getName() + ":" + x);
}
}
}

中断线程

public final void stop() 已过时

public void interrupt()

代码:

 线程控制—线程中断 35行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
import java.util.Date;

class ThreadStop extends Thread {
@Override
public void run() {
System.out.println("开始执行:" + new Date());

try {
Thread.sleep(10000);
} catch (InterruptedException e) {
// e.printStackTrace();
System.out.println("线程被终止了");
}

System.out.println("结束执行:" + new Date());
}
}
/*
* public final void stop():让线程停止,过时了,但是还可以使用。
* public void interrupt():中断线程。 把线程的状态终止,并抛出一个InterruptedException。
*/
public class ThreadStopDemo {
public static void main(String[] args) {
ThreadStop ts = new ThreadStop();
ts.start();

try {
Thread.sleep(3000);
// ts.stop();
ts.interrupt();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}

线程的生命周期图


线程安全问题

出现线程安全问题的原因:

发现一个线程在执行多条语句时,并运算同一个数据时,在执行过程中,其他线程参与进来,并操作了这个数据。导致到了错误数据的产生。

1、多线程环境

2、多个线程间有共享数据

3、有多条语句对共享数据进行操作

解决多线程的安全问题:

基本思想:让程序没有安全问题的环境。

操作:把多个语句操作共享数据的代码给锁起来,让任意时刻只能有一个线程执行即可

解决线程安全问题实现方式一

同步代码块

格式:

synchronized(对象) {  //任意对象都可以。这个对象就是锁。

    需要被同步的代码;

}

同步的特点:

同步前提:

1,必须要有两个或者两个以上的线程,才需要同步。

2,多个线程必须保证使用的是同一个锁。

同步的好处:解决了线程安全问题。Synchronized

同步的弊端:相对降低性能,因为判断锁需要消耗资源,产生了死锁。

代码:

 多线程卖票 36行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36
class SellTicket implements Runnable {
//定义100张票
private int ticket = 100;
//重写run()方法
public void run() {
while (true) {
//给多个操作共享数据的语句加锁
synchronized (this) {
if (ticket > 0) {
try {
//模拟现实线程休息100毫秒
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//售票
System.out.println(Thread.currentThread().getName()
+ "正在出售第" + (ticket--) + "张票");
}

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

解决线程安全问题实现方式二

同步方法 :其实就是将同步关键字定义在方法上,让方法具备了同步性。

同步函数是用this做锁

当同步函数被static修饰时,这时的同步用的锁是该类的字节码文件对象

同步代码块和同步函数的区别?

同步代码块使用的锁可以是任意对象。

同步函数使用的锁是this,静态同步函数的锁是该类的字节码文件对象。

 在一个类中只有一个同步的话,可以使用同步函数。如果有多同步,必须使用同步代码块,来确定不同的锁。所以同步代码块相对灵活一些。

死锁问题

同步死锁:通常只要将同步进行嵌套,就可以看到现象。同步函数中有同步代码块,同步代码块中还有同步函数。

 线程间通信:思路:多个线程在操作同一个资源,但是操作的动作却不一样。

1:将资源封装成对象。

2:将线程执行的任务(任务其实就是run方法。)也封装成对象。

 等待唤醒机制:涉及的方法:

wait:将同步中的线程处于冻结状态。释放了执行权,释放了资格。同时将线程对象存储到线程池中。

notify:唤醒线程池中某一个等待线程。

notifyAll:唤醒的是线程池中的所有线程。

代码:

 

 线程之间通信代码 88行 Java
Raw
  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
public class Test1 {
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();
}
}
//共享数据来源
class Student {
String name;
int age;
boolean flag;
}
//对共享数据操作的线程之一
class SetThread implements Runnable {
private Student s;

public SetThread(Student s) {
this.s = s;
}

int x = 0;

public void run() {
while (true) {
synchronized (s) {
if (s.flag) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (x % 2 == 0) {
s.name = "陈奕迅";
s.age = 41;
} else {
s.name = "周杰伦";
s.age = 38;
}
x++;
s.flag = true;
s.notify();
}
}

}
}
//对共享数据操作的线程之一
class GetThread implements Runnable {
private Student s;

public GetThread(Student s) {
this.s = s;
}
public void run() {
while (true) {
synchronized (s) {
if (!s.flag) {
try {
s.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(s.name+":"+s.age);
s.flag = false;
s.notify();
}
}

}
}

注意:

1:这些方法都需要定义在同步中。

2:因为这些方法必须要标示所属的锁。

      你要知道 A锁上的线程被wait了,那这个线程就相当于处于A锁的线程池中,只能A锁的notify唤醒。

3:这三个方法都定义在Object类中。为什么操作线程的方法定义在Object类中?

      因为这三个方法都需要定义同步内,并标示所属的同步锁,既然被锁调用,而锁又可以是任意对象,那么能被任意对象调用的方法一定定义在Object类中。

线程的状态转换图


wait和sleep区别: 分析这两个方法:从执行权和锁上来分析:

wait:可以指定时间也可以不指定时间。不指定时间,只能由对应的notify或者notifyAll来唤醒。sleep:必须指定时间,时间到自动从冻结状态转成运行状态(临时阻塞状态)。wait:线程会释放执行权,而且线程会释放锁。Sleep:线程会释放执行权,但不是不释放锁。


                    -----------android培训java培训、java学习型技术博客、期待与您交流!------------










0 0
原创粉丝点击