13.1 概述-进程&线程
进程:就是应用程序在内存中分配空间(正在运行中的程序)
线程:是进程中负责程序执行的执行单元,也成为执行路径。
- 一个进程中至少有一个线程在负责该进程的运行
- 如果一个进程中出现了多个线程,就称该程序为多线程程序。
13.2 多线程技术原理
举例:运动场—鸟巢。水立方。
多线程技术:解决多部分代码同时执行的需求。可以合理使用CPU资源。
CPU某一时刻只能处理一个线程。用时间片的方式,非常快速的切换线程执行。
13.3 JVM中的多线程&垃圾回收
多线程的运行根据CPU的切换完成的,怎么切换CPU说了算,所以多线程运行有一个随机性(CPU快速切换造成的)
jvm中的多线程。至少有两个线程:
一个是负责自定义代码运行的,这个从main方法开始执行的线程称之为主线程。
一个是负责垃圾回收的。
package day13;
class Demo1{
//定义垃圾回收方法
public void finalize(){
System.out.println("demo ok");
}
}
public class FinalizeDemo {
public static void main(String[] args){
new Demo1();
new Demo1();
new Demo1();
System.gc();//启动垃圾回收器。
System.out.println(".............");
}
}
输出结果之一:(输出的顺序每次都是不确定的)
demo ok
.............
demo ok
demo ok
或者
.............
demo ok
demo ok
demo ok
或者....
通过实验,发现每次得结果不一定相同,因为随机性造成的。
而且每一个线程都有运行的代码内容,这个称之为线程的任务。
之所以创建一个线程就是为了去运行指定的任务代码。
而线程的任务都封装在特定的区域中。
比如:
主线程运行的任务都是定义在main方法中
垃圾回收线程在收垃圾都会运行finalize方法。
13.4 单线程的问题
package day13;
class Demo2{
private String name;
Demo2(String name){
this.name = name;
}
public void show(){
for(int i=0; i<10; i++){
System.out.println(name+",i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args){
Demo2 d1 = new Demo2("A");
Demo2 d2 = new Demo2("B");
d1.show();
d2.show();
}
}
输出结果:
A,i=0
A,i=1
A,i=2
A,i=3
A,i=4
A,i=5
A,i=6
A,i=7
A,i=8
A,i=9
B,i=0
B,i=1
B,i=2
B,i=3
B,i=4
B,i=5
B,i=6
B,i=7
B,i=8
B,i=9
可以看出主线程在执行的时候,是先执行A 再执行B
可是A和B的执行是没有关系的,如何同时执行呢??
13.5 创建线程方式-继承Thread类
如何开辟一个执行路径?
通过查阅API文档 java.lang.Thread类。
该类的描述中有创建线程的两种方式:
1.继承Thread类
- 继承Thread类。
- 覆盖run方法
- 创建子类对象,就是创建线程对象
- 调用Thread类中的start方法就可以执行线程,并会调用run方法。
start()开启线程后,都会执行run方法,说明run方法存储的是线程要运行的代码。
所以,记住,自定义线程的任务代码都存储在run方法中。
package day13;
class Demo2 extends Thread{
private String name;
//覆盖run 方法
public void run(){
show();
}
Demo2(String name){
this.name = name;
}
public void show(){
for(int i=0; i<10; i++){
System.out.println(name+",i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args){
Demo2 d1 = new Demo2("A");
Demo2 d2 = new Demo2("B");
d1.start();
d2.start();
for (int i=0; i<10; i++){
System.out.println("mian"+i);
}
}
}
输出结果:
mian0
mian1
mian2
mian3
mian4
mian5
mian6
mian7
mian8
mian9
B,i=0
A,i=0
B,i=1
A,i=1
B,i=2
A,i=2
A,i=3
B,i=3
A,i=4
B,i=4
A,i=5
B,i=5
A,i=6
B,i=6
A,i=7
B,i=7
A,i=8
B,i=8
A,i=9
B,i=9
主线程,d1和d2启动线程,三个线程同时执行。
13.6 调用start和run的区别
获取线程的名字:
在run()方法里面直接getName()就可以得到线程的名字。
如果获取主线程的名字,就先获取当前线程,然后使用getName()
start()方法做了两件事:
- 开启线程
- 调用run方法
package day13;
class Demo2 extends Thread{
private String name;
//覆盖run 方法
public void run(){
for(int i=0; i<40; i++ ){
System.out.println(getName()+"......"+name+"...."+i);
}
}
Demo2(String name){
this.name = name;
}
public void show(){
for(int i=0; i<10; i++){
System.out.println(name+",i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args){
Demo2 d1 = new Demo2("A");
Demo2 d2 = new Demo2("B");
d1.start();
d2.start();
for (int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName()+"----------mian"+i);
}
}
}
输出:
main----------mian0
main----------mian1
main----------mian2
main----------mian3
main----------mian4
main----------mian5
main----------mian6
main----------mian7
main----------mian8
main----------mian9
Thread-0......A....0
Thread-1......B....0
Thread-0......A....1
Thread-1......B....1
Thread-0......A....2
Thread-1......B....2
Thread-0......A....3
Thread-1......B....3
Thread-1......B....4
Thread-1......B....5
Thread-1......B....6
Thread-1......B....7
Thread-1......B....8
Thread-1......B....9
Thread-0......A....4
Thread-0......A....5
Thread-0......A....6
Thread-0......A....7
Thread-0......A....8
Thread-0......A....9
将start改成run:
package day13;
class Demo2 extends Thread{
private String name;
//覆盖run 方法
public void run(){
for(int i=0; i<10; i++ ){
System.out.println(Thread.currentThread().getName()+"......"+name+"...."+i);
}
}
Demo2(String name){
this.name = name;
}
public void show(){
for(int i=0; i<10; i++){
System.out.println(name+",i="+i);
}
}
}
public class ThreadDemo {
public static void main(String[] args){
Demo2 d1 = new Demo2("A");
Demo2 d2 = new Demo2("B");
d1.run();
d2.run();
for (int i=0; i<10; i++){
System.out.println(Thread.currentThread().getName()+"----------mian"+i);
}
}
}
输出:
main......A....0
main......A....1
main......A....2
main......A....3
main......A....4
main......A....5
main......A....6
main......A....7
main......A....8
main......A....9
main......B....0
main......B....1
main......B....2
main......B....3
main......B....4
main......B....5
main......B....6
main......B....7
main......B....8
main......B....9
main----------mian0
main----------mian1
main----------mian2
main----------mian3
main----------mian4
main----------mian5
main----------mian6
main----------mian7
main----------mian8
main----------mian9
调用start和run的区别:
- 调用start会开启线程,让开启的线程去执行run方法中的线程任务。
- 直接调用run方法,线程并未开启,去执行run方法的只有主线程。
13.7 多线程的内存图
13.8 多线程的运行状态
13.9 售票示例
package day13;
class SaleTicket extends Thread{
private int tickets = 10;
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
while (tickets>0){
System.out.println(getName()+"........."+tickets--);
}
}
}
public class TicketDemo {
public static void main(String[] args){
//创建四个线程,会创建40张票,不建议票变成静态的。
// 所以如何共享这10张票。需要将资源和线程分离。
SaleTicket t1 = new SaleTicket();
SaleTicket t2 = new SaleTicket();
SaleTicket t3 = new SaleTicket();
SaleTicket t4 = new SaleTicket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出结果:
Thread-1.........10
Thread-2.........10
Thread-3.........10
Thread-0.........10
Thread-3.........9
Thread-2.........9
Thread-1.........9
Thread-2.........8
Thread-3.........8
Thread-0.........9
Thread-3.........7
Thread-2.........7
Thread-1.........8
Thread-2.........6
Thread-3.........6
Thread-0.........8
Thread-3.........5
Thread-2.........5
Thread-1.........7
Thread-2.........4
Thread-3.........4
Thread-0.........7
Thread-3.........3
Thread-2.........3
Thread-1.........6
Thread-2.........2
Thread-3.........2
Thread-0.........6
Thread-3.........1
Thread-2.........1
Thread-1.........5
Thread-1.........4
Thread-1.........3
Thread-0.........5
Thread-1.........2
Thread-0.........4
Thread-1.........1
Thread-0.........3
Thread-0.........2
Thread-0.........1
发现一件奇怪的事情,明明总共只有十张票,为什么卖出去了10*4 = 40张票?
因为线程对象和线程任务捆绑在一起,每个线程都是在处理自己的tickets。
怎么解决这种问题??——声明实现Runnable接口的类。
13.10 创建线程的方式二——实现Runnable接口
使用方法:
- 定义一个类实现Runnable
- 覆盖Runnable接口中的run方法,将线程要运行的任务代码存在到该方法中。
- 通过Thread类创建线程对象,并将先实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。
- 调用Thread类的start方法,开启线程。
Runnable接口的出现:
解耦:降低了线程对象和线程任务的耦合性。
package day13;
//1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
private static int tickets = 10;
//2. 覆盖Runnable接口中的run方法,将线程要运行的任务代码存在到该方法中。
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
while (tickets>0){
System.out.println(Thread.currentThread().getName()+"........."+tickets--);
}
}
}
public class TicketsDemo2 {
public static void main(String[] args){
SaleTicket1 s = new SaleTicket1();
//3. 通过Thread类创建线程对象,并将先实现了Runnable接口的对象作为Thread类的构造函数的参数进行传递。
//创建四个线程。通过Thread类对象
Thread t1 = new Thread(s);
Thread t2 = new Thread(s);
Thread t3 = new Thread(s);
Thread t4 = new Thread(s);
// 4. 调用Thread类的start方法,开启线程。
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果:
Thread-0.........10
Thread-0.........6
Thread-0.........5
Thread-0.........4
Thread-0.........3
Thread-0.........2
Thread-0.........1
Thread-2.........8
Thread-1.........9
Thread-3.........7
13.11 实现Runnable接口的好处
实现Runnable接口的好处:
- 避免了继承Thread类的单继承的局限性
- Runnable接口出现等符合面向对象,将线程单独进行对象的封装
- Runnable接口的出现,降低了线程对象和线程任务的耦合性
所以以后创建线程都是用第二种方式。
13.12 安全问题的-原因&同步
package day13;
//1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
private static int tickets = 10;
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
while (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+"........."+tickets--);
}
}
}
public class TicketsDemo2 {
public static void main(String[] args){
SaleTicket1 s1 = new SaleTicket1();
//SaleTicket1 s2 = new SaleTicket1();
//创建四个线程。通过Thread类对象
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
Thread t4 = new Thread(s1);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出:
Thread-0.........8
Thread-3.........7
Thread-1.........9
Thread-2.........10
Thread-0.........6
Thread-1.........5
Thread-3.........6
Thread-2.........4
Thread-3.........3
Thread-1.........2
Thread-0.........1
Thread-2.........0
Thread-3.........-1
Thread-1.........-1
警告:竟然买到了第-1张票。
多线程的安全问题,产生的原因是什么?
- 线程任务中有处理到共享的数据。
- 线程任务中有多条对共享数据的操作。一个县城在操作共享数据的过程中,其他线程参与了运算,造成了数据的错误。
解决的思想: 只要保证多条操作共享数据的代码在某一时间段,被一条线程执行,在执行期间不允许其他线程参与运算。
怎么体现?
用到了同步代码块。
synchronized(对象){
// 需要被同步的代码
}
package day13;
//1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
private static int tickets = 10;
Object obj = new Object();
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
{
while (true){
synchronized (obj){
if (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+"........."+tickets--);
}
}
}
}
}
}
public class TicketsDemo2 {
public static void main(String[] args){
SaleTicket1 s1 = new SaleTicket1();
//SaleTicket1 s2 = new SaleTicket1();
//创建四个线程。通过Thread类对象
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
Thread t4 = new Thread(s1);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
输出:
Thread-0.........10
Thread-0.........9
Thread-0.........8
Thread-2.........7
Thread-2.........6
Thread-3.........5
Thread-3.........4
Thread-1.........3
Thread-1.........2
Thread-1.........1
13.13 同步的好处和弊端
例子:
火车上的卫生间
同步在目前情况下保证了一次只能有一个线程在执行,其他线程进不来。这就是同步的机制。
好处:解决了多线程的安全问题。
弊端:降低效率。
13.14 同步的前提
有可能出现这样的情况:
多线程安全问题出现后,加入了同步机制,没有想到的是,安全问题依旧存在,怎么办?
只要遵守了同步的前提,就可以解决了。
同步的前提:
多个线程在同步中必须使用同一个锁,这才是对多个线程同步。
以下同步代码中,有多个锁:
package day13;
//1. 定义一个类实现Runnable
class SaleTicket1 implements Runnable{
private static int tickets = 10;
Object obj = new Object();
//卖票的代码需要被多个线程执行,所以要将这些代码定义在线程任务中。run方法。
public void run(){
{
while (true){
synchronized (new Object()){
if (tickets>0){
try{
Thread.sleep(10); //让线程到这里稍微停一下。
}catch (InterruptedException e){
}
System.out.println(Thread.currentThread().getName()+"........."+tickets--);
}
}
}
}
}
}
public class TicketsDemo2 {
public static void main(String[] args){
SaleTicket1 s1 = new SaleTicket1();
//SaleTicket1 s2 = new SaleTicket1();
//创建四个线程。通过Thread类对象
Thread t1 = new Thread(s1);
Thread t2 = new Thread(s1);
Thread t3 = new Thread(s1);
Thread t4 = new Thread(s1);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
结果:
Thread-2.........9
Thread-3.........7
Thread-0.........8
Thread-1.........10
Thread-1.........6
Thread-3.........4
Thread-2.........3
Thread-0.........5
Thread-1.........2
Thread-2.........1
Thread-0.........0
Thread-3.........2
Thread-1.........-1
13.15 同步的小问题
线程任务里面,多条操作共享数据的代码才需要同步,其余不需要同步。