黑马程序员—Java基础—多线程
来源:互联网 发布:linux 查看ftp用户 编辑:程序博客网 时间:2024/05/21 23:55
Java程序运行原理
java 命令会启动 java 虚拟机,启动 JVM,等于启动了一个应用程序,也就是启动了一个进程。该进程会自动启动一个 “主线程” ,然后主线程去调用某个类的 main 方法。所以main方法运行在主线程中。在此之前的所有程序都是单线程的。
思考: jvm虚拟机的启动是单线程的还是多线程的?
答:A:Java命令去启动JVM,JVM会启动一个进程,该进程会启动一个主线程。
B:JVM的启动是多线程的,因为它最低有两个线程启动了,主线程和垃圾回收线程。
多线程的实现方案:
方式一:
继承Thread类。
步骤:
A:自定义类MyThread继承Thread类。
B:MyThread类里面重写run()?
C:创建对象
D:启动线程
代码: 多线程的实现方式一 22行 JavaRaw
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();}}
答:不是类中的所有代码都需要被线程执行的。而这个时候,为了区分哪些代码能够被线程执行,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行 JavaRaw
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行 JavaRaw
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行 JavaRaw
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
//定义类继承自Threadclass ThreadPriority extends Thread {@Overridepublic 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行 JavaRaw
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 {@Overridepublic 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行 JavaRaw
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 {@Overridepublic 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行 JavaRaw
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行 JavaRaw
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 {@Overridepublic 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行 JavaRaw
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 {@Overridepublic 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行 JavaRaw
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行 JavaRaw
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学习型技术博客、期待与您交流!------------
答
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础多线程
- 黑马程序员——Java基础->多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础--多线程
- 黑马程序员——java基础--多线程
- 黑马程序员——Java基础--- 多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——Java基础---多线程
- 黑马程序员——java基础-多线程
- 黑马程序员——Java基础:多线程
- 对android虚拟机的理解,包括内存管理机制垃圾回收机制。dalvik和art区别
- 修改系统的TIME_WAIT等待时间和其它的TCP属性
- 2015年11月小结
- java希尔排序算法
- Eclipse 屏蔽鼠标移动提示类信息
- 黑马程序员—Java基础—多线程
- 第十四周实践项目1—验证算法(4)平衡二叉树
- 【APUE】4、Unix环境高级编程——解惑篇
- java快速排序算法
- Android中Task任务栈的分配
- 汉诺塔II
- vector容器
- Android系统中GC什么情况下会出现内存泄露呢?
- acm心路1