- /*
- 进程:是一个正在执行中的程序。
- 每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。
- 线程:就是进程中的一个独立的控制单元。
- 线程在控制着进程的执行。
- 一个进程中至少有一个线程。
- Java VM 启动的时候会有一个进程java.exe.
- 该进程中至少一个线程负责java程序的执行。
- 而且这个线程运行的代码存在于main方法中。
- 该线程称之为主线程。
- 扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。
- 1,如何在自定义的代码中,自定义一个线程呢?
- 通过对api的查找,java已经提供了对线程这类事物的描述。就Thread类。
- 创建线程的第一种方式:继承Thread类。
- 步骤:
- 1,定义类继承Thread。
- 2,复写Thread类中的run方法。
- 目的:将自定义代码存储在run方法。让线程运行。
- 3,调用线程的start方法,
- 该方法两个作用:启动线程,调用run方法。
- 发现运行结果每一次都不同。
- 因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。
- 明确一点,在某一个时刻,只能有一个程序在运行。(多核除外)
- cpu在做着快速的切换,以达到看上去是同时运行的效果。
- 我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
- 这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长,cpu说的算。
- 为什么要覆盖run方法呢?
- Thread类用于描述线程。
- 该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是run方法。
- 也就是说Thread类中的run方法,用于存储线程要运行的代码。
- */
- class Demo extends Thread
- {
- public void run()
- {
- for(int x=0; x<60; x++)
- System.out.println("demo run----"+x);
- }
- }
- class ThreadDemo
- {
- public static void main(String[] args)
- {
- //for(int x=0; x<4000; x++)
- //System.out.println("Hello World!");
- Demo d = new Demo();//创建好一个线程。
- //d.start();//开启线程并执行该线程的run方法。
- d.run();//仅仅是对象调用方法。而线程创建了,并没有运行。
- for(int x=0; x<60; x++)
- System.out.println("Hello World!--"+x);
- }
- }
- /*
- 练习:
- 创建两个线程,和主线程交替运行。
- */
------------------------------------------------------------------------------------------------------------------------------------
创建线程方式一
继承Thread类
1. 子类覆盖父类中的run方法,将线程运行 的代码存放在run中。
2. 建立子类对象的同时线程也被创建。
3. 通过调用start方法开启线程。
- /*
- 练习:
- 创建两个线程,和主线程交替运行。
- 原来线程都有自己默认的名称。
- Thread-编号 该编号从0开始。
- static Thread currentThread():获取当前线程对象。
- getName(): 获取线程名称。
- 设置线程名称:setName或者构造函数。
- */
- class Test extends Thread
- {
- //private String name;
- Test(String name)
- {
- //this.name = name;
- super(name);
- }
- public void run()
- {
- for(int x=0; x<60; x++)
- {
- System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
- }
- }
- }
- class ThreadTest
- {
- public static void main(String[] args)
- {
- Test t1 = new Test("one---");
- Test t2 = new Test("two+++");
- t1.start();
- t2.start();
- // t1.run();
- // t2.run();
- for(int x=0; x<60; x++)
- {
- System.out.println("main....."+x);
- }
- }
- }
-----------------------------------------------------------------------------------------------------------------------------------
创建线程方式二
实现Runnable接口
1. 子类覆盖接口中的run方法。
2. 通过Thread类创建线程,并将实现了 Runnable接口的子类对象作为参数传递给 Thread类的构造函数。
3. Thread类对象调用start方法开启线程。
思考:为什么要给Thread类的构造函数传递
Runnable的子类对象?
- /*
- 需求:简单的卖票程序。
- 多个窗口同时买票。
- 创建线程的第二种方式:实现Runable接口
- 步骤:
- 1,定义类实现Runnable接口
- 2,覆盖Runnable接口中的run方法。
- 将线程要运行的代码存放在该run方法中。
- 3,通过Thread类建立线程对象。
- 4,将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数。
- 为什么要将Runnable接口的子类对象传递给Thread的构造函数。
- 因为,自定义的run方法所属的对象是Runnable接口的子类对象。
- 所以要让线程去指定指定对象的run方法。就必须明确该run方法所属对象。
- 5,调用Thread类的start方法开启线程并调用Runnable接口子类的run方法。
- 实现方式和继承方式有什么区别呢?
- 实现方式好处:避免了单继承的局限性。
- 在定义线程时,建立使用实现方式。
- 两种方式区别:
- 继承Thread:线程代码存放Thread子类run方法中。
- 实现Runnable,线程代码存在接口的子类的run方法。
- */
- class Ticket implements Runnable//extends Thread
- {
- private int tick = 100;
- public void run()
- {
- while(true)
- {
- if(tick>0)
- {
- System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
- }
- }
- }
- }
- class TicketDemo
- {
- public static void main(String[] args)
- {
- Ticket t = new Ticket();
- Thread t1 = new Thread(t);//创建了一个线程;
- Thread t2 = new Thread(t);//创建了一个线程;
- Thread t3 = new Thread(t);//创建了一个线程;
- Thread t4 = new Thread(t);//创建了一个线程;
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- /*
- Ticket t1 = new Ticket();
- //Ticket t2 = new Ticket();
- //Ticket t3 = new Ticket();
- //Ticket t4 = new Ticket();
- t1.start();
- t1.start();
- t1.start();
- t1.start();
- */
- }
- }
----------------------------------------------------------------------------------------------------------------------------------
线程安全问题
导致安全问题的出现的原因:
多个线程访问出现延迟。
线程随机性 。
注:线程安全问题在理想状态下,不容易出 现,但一旦出现对软件的影响是非常大 的。
---------------------------------------------------------------------------------------------------------------------------------
同步(synchronized)
格式:
synchronized(对象)
{
需要同步的代码;
}
同步可以解决安全问题的根本原因就在那个对象上。
该对象如同锁的功能。
同步的特点
同步的前提:
同步需要两个或者两个以上的线程。
多个线程使用的是同一个锁。 未满足这两个条件,不能称其为同步。 同步的弊端:
当线程相当多时,因为每个线程都会去判断 同步上的锁,这是很耗费资源的,无形 中会降低程序的运行效率。
- /*
- 通过分析,发现,打印出0,-1,-2等错票。
- 多线程的运行出现了安全问题。
- 问题的原因:
- 当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,
- 另一个线程参与进来执行。导致共享数据的错误。
- 解决办法:
- 对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
- Java对于多线程的安全问题提供了专业的解决方式。
- 就是同步代码块。
- synchronized(对象)
- {
- 需要被同步的代码
- }
- 对象如同锁。持有锁的线程可以在同步中执行。
- 没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
- 火车上的卫生间---经典。
- 同步的前提:
- 1,必须要有两个或者两个以上的线程。
- 2,必须是多个线程使用同一个锁。
- 必须保证同步中只能有一个线程在运行。
- 好处:解决了多线程的安全问题。
- 弊端:多个线程需要判断锁,较为消耗资源,
- */
- class Ticket implements Runnable
- {
- private int tick = 1000;
- Object obj = new Object();
- public void run()
- {
- while(true)
- {
- synchronized(obj)
- {
- if(tick>0)
- {
- //try{Thread.sleep(10);}catch(Exception e){}
- System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
- }
- }
- }
- }
- }
- class TicketDemo2
- {
- public static void main(String[] args)
- {
- Ticket t = new Ticket();
- Thread t1 = new Thread(t);
- Thread t2 = new Thread(t);
- Thread t3 = new Thread(t);
- Thread t4 = new Thread(t);
- t1.start();
- t2.start();
- t3.start();
- t4.start();
- }
- }
-----------------------------------------------------------------------------------------------------------------------------------
停止线程
1. 定义循环结束标记
因为线程运行代码一般都是循环,只要控制了循环即 可。
2. 使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到
运行状态中来。
注:stop方法已经过时不再使用。
-----------------------------------------------------------------------------------------------------------------------------------
线程类的其他方法
setPriority(int num)
setDaemon(boolean b)
join()
自定义线程名称
toString()