------- android培训、java培训、期待与您交流! ----------
一,多线程概述:
1,进程(通俗的说就是正在执行的程序,是程序的动态表现形式)。
2,线程(例:FlashGet——快车下载工具)(线程是进程中的内容)。
3,多线程存在的意义。
4,线程的创建的方式。
5,多线程的创建特点。
6,每个程序中都至少有一个线程,因为线程是程序中的控制单元或是执行路径。
7,进程是一个正在执行中的程序。每一个进程执行都至少有一个执行顺序,该顺序是一个执行路径或者叫一个控制单元。线程就是进程中的一个独立控制单元,线程在控制着进程的执行。所以,一个进程中至少有一个线程。进程执行完就不占内存了。
8,JVM启动时会有一个进程:java.exe。该进程中至少有一个线程负责java程序的执行,而且这个线程运行的代码存在于main方法中,该线程称为主线程。主线程是由JVM开启的,而别的自定义的线程由线程对象.start()开启的。
9,其实更细节说明JVM,JVM启动不止一个线程,还有负责垃圾回收机制的线程。
10,有多个线程执行的程序称之为多线程程序。多线程可以使多个部分的代码同时执行(代码的执行大多是从上往下顺序执行的)。
二,创建线程——继承Thread类【public long getId() 返回该线程的标识符。线程 ID 是一个正的 long 数,在创建该线程时生成。线程 ID 是唯一的,并终生不变。线程终止时,该线程 ID 可以被重新使用。 返回:该线程的 ID。从以下版本开始:1.5】:
1,如何在自定义的代码中自定义一个线程呢?通过对api的查找,java已经提供了对线程这类事物的描述,就是Thread类。
2,创建线程的第一种方式:继承Thread类。并且要覆盖Thread类的run方法。步骤:
(1),继承Thread类。
(2),重写Thread类中的run方法。目的是将自定义的代码存储在run方法中,让线程运行。
(3),调用线程的start方法,start方法有两个作用:一,启动线程;二,调用run方法。
其实多任务操作系统切换的是进程中的线程【进程中的调度是通过调度线程来实现的!】。所以哪个线程中的控制单元抢到Cpu中的使用权cpu就执行哪个线程。
3,发现运行结果每一次都不同,是因为多个线程都在获取cpu的执行权。Cpu执行到哪个线程,哪个线程就运行。明确一点,在某一时刻,只能有一个线程在运行(多核除外)。Cpu在做着快速地切换,以达到看上去是同时运行的效果。我们可以形象的把多线程的运行行为想象成在互相抢夺cpu的执行权。这就是多线程的一个特性:随机性,哪个线程抢到cpu的执行权,cpu就执行哪个线程,至于执行多长时间是受cpu控制的。
三,相关代码:
class Demo extends Thread {// 创建一个线程的类。
public void run() {
for (int i = 0; i < 60; i++) {
System.out.println("dmeo run---->" + i);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo();// 建立好一个对象,就创建了一个线程。
d.start();// 在这里是调用start方法启动线程并调用new Demo()对象里的run方法。这里的执行路径为:d.
// 如上面写d.run();的话是按主线程的顺序执行的,即先执行:d.run();。
// 虽然创建了一个线程,但是没有运行,要用d.start()来运行线程并同时运行run方法。
for (int i = 0; i < 60; i++) {
System.out.println("hello world---->" + i);
}
}// 从打印的结果可以看出:本程序有两个线程同时执行。
}
四,创建线程——run和start特点。
1,为什么要覆盖run方法呢?Thread类用于描述线程。该类就只定义了一个功能,存储线程要运行的代码,该存储功能就是run方法。也就是说,Thread类中的run方法是用于存储线程要运行的代码。在java中主线程要运行的代码是存储在main方法中的,因此main方法才是程序的入口。
2,比如 : Thread t = new Thread();//创建一个名为t的线程。
t.start();//是运行不产生任何效果的,因为这里调用的是Thread类中的run方法,里面是没有内容的。
所以我们创建线程时才创建Thread类的子类,为的就是重写Thread类中的run方法,把要与主线程(main中存的代码)并行运行的代码存在于run方法。因此start方法是很重要的,start调用到了底层。面试是经常会问到:调用run方法结果是什么?调用start方法结果是什么?
五:线程运行状态:
1,线程的五种状态:有执行资格,但没有执行权的是临时状态;没有执行资格(当然没有执行权)的是冻结状态;有执行资格、有执行权的是运行状态;彻底没了执行资格的是消亡状态。只要还有活着的线程,进程就没有结束。
六,获取线程对象以及名称。
1,获取线程的名称:能通过Thread类中的方法 public final String getName()返回的是一个字符串。
2,原来线程都有自己默认的名称:Thread-编号【从getName方法中返回的形式!】该编号从0 开始。
3,static Thread currentThread() 返回当前正在执行的线程对象的引用。该方法的存在是因为线程的创建还要实现Runnable接口!!因为只有extends Thread时才this才代表当前线程的引用,所以当implements Runnable时,要获取当前线程的引用就只能使用Thread.currentThread()方法来返回Thread类型的引用了!
4,getName():获取线程的名称。
5,设置线程的名称:setName或者通过构造函数。起一个有意的名称是很重要的,因为可能会很多个线程在同时运行,所以为了获取当前正在运行的线程的一个直观的名称可以通过设置线程的名称来完成。
6,相关代码1:
class ThreadDemo {
public static void main(String[] args) {
Demo d1 = new Demo("one");// 通过构造函数来设置一个线程的名称。
d1.setName("oh no");// 通过setName来重新设置d1这个线程的名称。RuntimeException!!
Demo d2 = new Demo("two");
d1.start();
d2.start();
for (int i = 0; i < 50; i++) {
System.out.println("ThreadDemo" + "..." + i);
}
}
}
class Demo extends Thread {
public Demo(String name) {
super(name);// 给线程起个名字
}
public void run() {
for (int i = 0; i < 50; i++)// 这里的i是在栈内存中的,每同时start几个线程,就各存在几个i。
{// 通过getName()来获得线程的名字。
System.out.println((Demo.currentThread() == this) + this.getName()
+ "..." + i);// true
}// Demo.currentThread()的Demo也可用Thread,因为currentThread()方法是静态的。
}// 而Demo extends Thread 所以根据继承的特性。
}
7,相关代码2:
package reviews;
public class Review {
public static void main(String[] args) throws Exception {
Demo d = new Demo();
d.run();// 这个是main这个线程里的执行路径。
d.start();// 这个是d这个线程里的执行路径。
}
}
class Demo extends Thread {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
七,创建线程——实现Runnable接口。
0,Thread类的构造函数都是调用下面这个函数进行相应成员变量的初始化的:
1,创建线程的另一种方法是声明实现 Runnable 接口的类。然后实现Runnalbe的 run 方法。然后可以分配该类的实例,在创建Thread时作为一个参数来传递并启动。
2,能创建线程对象的只有Thread类和其子类。Runnable不能创建线程对象。
3,创建线程的第二种方式:实现Runnable接口。
3,1, 标准实现步骤:
(1),定义类实现Runnable接口。
(2),实现Runnable中的run方法。将线程要运行的代码存放在该run方法中。
(3),通过Thread类建立线程对象。
(4),将Runnable接口的子类对象的引用作为实际参数传递给Thread 类的对象的构造函数。为什么要将Runnable接口的子类对象的引用传递给Thread类的构造函数?因为自定义的run方法所属的对象是Runnable接口的子类对象,与Thread类没关系,而Thread类已经实现了Runnable中的方法,所以要让线程去执行指定的run方法,就必须明确该run方法所属的对象。
(5),调用Thread类的start方法开启线程并调用Runnable接口子类对象的run方法。
4,实现Runnable接口方式和继承Thread类方式有什么区别呢(这是关键的部分,面试题经常考)?实现Runnable的好处:避免了单继承的局限性。在定义线程时建议使用实现Runnable 方式,这种方式是最常用的,所以当要定义的线程类中有要继承父类时,只能用实现Runnable接口的方式。继承Thread类:线程代码存放在Thread子类的run方法中;实现Runnable:线程的代码存放在接口的实现类的run方法中。
5,例1:
class Thread implements Runnable {// 创建线程实现Runnable的原理!!!
private Runnable runnable;
public Thread(Runnable runnable) {// 这里用到了多态。
this.runnable = runnable;
}
public void run() {
if (null != runnable) {
runnable.run();
}
}
}
6,例2:
class ThreadDemo {
public static void main(String args[]) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);// 这里用到多态,因为t为Runnable接口的子类,而Runable和Thread类有联系。
Thread t2 = new Thread(t);// Thread(Runnalbe args)
Thread t3=new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Ticket implements Runnable {
private int tike = 100;
public void run() {
while (tike > 0 && tike <= 100) {
System.out.println(Thread.currentThread().getName() + " sale:"
+ tike--);
}
}
}
八,多线程同步代码块。
1,synchronized(对象的引用),用在只让一个线程执行的多线程中。现实生活中例子比如:火车里的厕所,一个人一个人排着队上厕所。
2,同步代码块: synchronized (对象的引用)
{
需要被同步的代码。
}
对象的引用如同锁,持有锁的线程可以在同步代码块中执行,没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。要等到持有锁的线程释放锁的所有权后,才可以让别的线程执行被同步的代码!
3,写同步的前提:
(1),必须要有两个或者两个以上的线程。
(2),必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。同步的好处:解决了多线程的安全问题;弊端:多个线程需要判断锁,较为消耗资源。
九,多线程——同步函数。
1,目的:该程序是否有安全问题,如果有,如何解决?
2,如何找问题:
(1),明确哪些代码是多线程运行的代码。
(2),明确共享数据。
(3),明确多线程运行代码中哪些语句是操作共享数据的。
十,多线程——同步函数的锁是this。
1,同步函数用的是哪一个锁呢?函数需要被对象的引用调用,那么函数都有一个所属对象的引用,就是:this。所以同步函数使用的锁是:this。
2,相关代码:
/*
* 同步函数用的是哪一个锁呢? 函数需要被对象调用。那么函数都有一个所属对象引用。
* 就是this。所以同步函数使用的锁是this。通过该程序进行验证。使用两个线程来买 票。
* 一个线程在同步代码块中。一个线程在同步函数中。都在执行买票动作。
*/
class Ticket implements Runnable{
private int tick = 100;
Object obj = new Object();
boolean flag = true;
public void run(){
if(flag){
while(true){
synchronized(this){//同步函数的锁是:this。
if(tick>0){
try{
Thread.sleep(10);
}catch(Exception e){
}
System.out.println(Thread.currentThread().getName()+"....code : "+ tick--);
}
}
}
}else
while(true)
show();
}
public synchronized void show(){//同步函数的锁是:this
if(tick>0){
try{
Thread.sleep(10);
}catch(Exception e){
}
System.out.println(Thread.currentThread().getName()+"....show.... : "+ tick--);
}
}
}
class ThisLockDemo{
public static void main(String[] args) {
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{
Thread.sleep(10);
}catch(Exception e){
}//让主线程停10ms。确保执行t1.start()先。
t.flag = false;
t2.start();
// Thread t3 = new Thread(t);
// Thread t4 = new Thread(t);
// t3.start();
// t4.start();
}
}