一、多线程——概述
进程和线程: 进程:正在执行中的程序。 每一个进程执行都有一个执行顺序。该顺序是一个执行路径,或者叫一个控制单元。 线程:就是进程中的一个独立的控制单元。线程在控制着进程的执行。 一个进程中至少有一个线程。 参阅: http://jingyan.baidu.com/article/624e74598efcc834e9ba5a66.html Java VM 启动的时候会有一个进程java.exe. 该进程中至少有一个线程负责java程序的执行。而且这个线程运行的代码存在于main方法中。该线程称之为主线程。 扩展:其实更细节说明jvm,jvm启动不止一个线程,还有负责垃圾回收机制的线程。二、多线程——创建线程——继承 Thread 类
java.lang.Object -Thread public class Thread extends Object implements Runnable 1,如何在自定义的代码中,自定义一个线程呢? 通过对API的查找,java已经提供了对线程这类事物的描述。就是 Thread 类。 创建线程的第一种方式:继承 Thread 类。 步骤: A,定义类继承 Thread 。 B,复写 Thread 类中的run方法。 目的:将自定义代码存储在run方法中,给线程运行。 C,调用线程的start方法, 该方法两个作用:启动线程,并调用run方法。 ======主线程执行的是main函数中的代码;自定义线程执行的是run()方法中的代码。 发现运行结果每一次都不同。 因为多个线程都获取cpu的执行权。cpu执行到谁,谁就运行。 明确一点,在某一个时刻,只能有一个程序在运行。(多核除外) cpu在做着快速的切换,以达到看上去是同时运行的效果。 我们可以形象把多线程的运行理解为在互相抢夺cpu的执行权。 这就是多线程的一个特性:随机性。谁抢到谁执行,至于执行多长时间,cpu说的算。 代码:class Demo extends Thread { public void run() //自定义线程运行的代码 { for (int x=0 ;x<20 ;x++ ) { System.out.println("demo: x="+x); } } } class ThreadDemo { public static void main(String[] args) //主线程运行的代码 { Demo d = new Demo(); //创建好一个线程 //Thread d = new Demo(); //也可以这样写(多态) d.start(); //启动线程,并调用run()方法。 //主线程运行的for循环 for (int x=0 ;x<20 ;x++ ) { System.out.println("x="+x); } } }
三、多线程——创建线程——run和start特点
为什么要覆盖run方法呢?
Thread 类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码。该存储功能就是 run() 方法。
也就是说 Thread 类中的run方法,用于存储线程要运行的代码。
start方法的两个作用:
启动线程,并调用run方法。
======主线程要运行的代码存在main方法中,这是由虚拟机定义的。四、多线程——线程练习
练习:创建两个线程,和主线程交替运行。
代码一:class Test extends Thread { private String name; Test(String name) { this.name = name; } public void run() { for (int x=0 ;x<10 ;x++) { System.out.println(name+"test run -- x="+x); } } } class ThreadTest { public static void main(String[] args) { Test t1 = new Test("one "); Test t2 = new Test("twooooooo"); t1.start(); t2.start(); for (int x=0 ;x<10 ;x++ ) { System.out.println("main x="+x); } } }
五、多线程——线程运行状态
六、多线程——获取线程对象及名称
1、线程都有自己默认的名称: Thread-编号 该编号从0开始。
2、currentThread()方法:返回对当前正在执行的线程对象的引用。该方法为静态方法,可以直接被类名调用。 Thread.currentThread();
static Thread currentThread()
返回对当前正在执行的线程对象的引用。
3、getName()方法:返回该线程的名称。
4、设置线程的名称:
setName(String name) 或者构造函数(String name)
示例: Thread.currentThrad().getName();
代码二:class Demo extends Thread { //private String name; Test(String name) { //this.name = name; super(name); } public void run() { for (int x=0; x<10 ;x++ ) System.out.println(Thread.currentThread().getName()+": demo run--x="+x); //System.out.println(this.getName()+"demo: x="+x); //Thread.currentThread()=this //Thread.currentTread()是标准通用方法。 } } class ThreadDemo { public static void main(String[] args) { Demo d1 = new Demo(); Demo d2 = new Demo(); d1.start(); d2.start(); for (int x=0; x<10 ;x++ ) System.out.println("main--x="+x); } }
七、多线程——售票的例子
该程序出现重复票。原因是每创建一个对象就创建30张票。如果tick加上static,可以解决此问题。但是一般不建议定义静态(生命周期太长)。所以用该方法创建线程解决此问题不可行。class Ticket extends Thread { private int tick = 30; 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 t1 = new Ticket(); Ticket t2 = new Ticket(); Ticket t3 = new Ticket(); t1.start(); t2.start(); t3.start(); } }
八、多线程——创建线程——实现 Runnable
java.lang.Runnable
public interface Runnable
通过查阅API发现,Thread类的除了有空参数才构造函数外,还有一个可以接收Runnable接口子类对象的构造函数。那么,创建多线程就有了第二种方式。
(1)创建线程方法二:
实现 Runnable 接口
(2)步骤:
1,定义类实现 Runnable 接口。
2,覆盖 Runnable 接口中的run()方法。
将线程要运行的代码存放在该run方法中。
3,通过 Thread 类建立线程对象。
4,将 Runnable 接口的子类对象作为实际参数传递给 Thread 类的构造函数。
为什么要将 Runnable 接口的子类对象传递给 Thread 的构造函数?
因为,自定义的run()方法所属的对象是 Runnable 接口的子类对象。所以要让线程去执行指定对象的run()方法,就必须明确该run()方法所属对象。
5,调用 Thread 类的start()方法开启线程并调用 Runnable 接口子类的run()方法。
(3)实现方式和继承方式有什么区别呢?
实现方式好处:避免了单继承的局限性。
在定义线程时,建议使用实现方式。
两种方式区别:
继承 Thread:线程代码存放 Thread 子类run()方法中。
实现 Runnable ,线程代码存在接口的子类的run()方法。
代码:class Ticket implements Runnable { private int tick=40; 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); t1.start(); t2.start(); t3.start(); //new Thread(t).start(); //new Thread(t).start(); //new Thread(t).start(); } }
九、多线程——多线程的安全问题
上面售票的例子中,由于多线程的出现,存在安全问题。
问题的原因:
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完;另一个线程参与进来执行。导致共享数据的错误。
解决办法:
对多条操作共享数据的语句,只能让一个线程都执行完。在执行过程中,其他线程不可以参与执行。
如何判断哪些语句需要同步,就看哪些语句在操作共享数据。
代码:class Ticket implements Runnable { private int tick=40; Object obj = new Object();//定义一个对象 public void run() { while(true) { synchronized(obj)//将需要同步执行的代码放在同步代码块中,同时需要往同步中传入一个对象。 { if(tick>0) { try{Thread.sleep(10);}catch (InterruptedException e){} 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); t1.start(); t2.start(); t3.start(); } }
十、多线程——多线程同步代码块
Java对于多线程的安全问题提供了专业的解决方式。就是——同步代码块。
synchronized(对象)
{
需要被同步的代码
}
对象如同锁。持有锁的线程可以在同步中执行。没有持有锁的线程即使获取cpu的执行权,也进不去,因为没有获取锁。
======锁也叫监视器。
经典例子:火车上的卫生间。
同步的前提:
1,必须要有两个或者两个以上的线程。
2,必须是多个线程使用同一个锁。
必须保证同步中只能有一个线程在运行。
利弊:
好处:解决了多线程的安全问题。
弊端:多个线程需要判断锁,较为消耗资源,
十一、多线程——同步函数
因为同步代码块和函数都是用来封装代码的。
那么,当函数封装的代码和同步代码块封装的代码相同时,就可以使用同步函数。
示例:public synchronized void add(int n) { sum = sum + n; //try{Thread.sleep(10);}catch(Exception e){}; System.out.println("sum="+sum); }
如何确定同步区域?
1,明确哪些代码是多线程运行代码。
2,明确共享数据。
3,明确多线程运行代码中哪些语句是操作共享数据的。
代码:
/*
需求:
银行有一个金库;
有两个储户,分别存300元,每次存100,存3次。
*/class Bank { private int sum; public synchronized void add(int n) { sum = sum + n; //调用sleep方法有可能出现异常,所以需要try catch处理。 try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println("sum="+sum); } } class Cus implements Runnable { private Bank b = new Bank(); public void run() { for (int x=0 ;x<3 ;x++ ) { b.add(100); } } } class BankDemo { public static void main(String[] args) { Cus c = new Cus(); Thread t1 = new Thread(c); Thread t2 = new Thread(c); t1.start(); t2.start(); } }
以上代码为毕老师讲课时所写,但个人认为该代码不严谨。个人改写代码如下(如有错误请指正):class Bank { private int sum; //构造函数私有,因为只有一个银行,使用单例设计模式 private Bank(){} private static final Bank b = new Bank(); public static Bank getInstance() { return b; } public synchronized void add(int n) { sum = sum + n; //调用sleep方法有可能出现异常,所以需要try catch处理。 try{Thread.sleep(40);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+": sum="+sum); } } class Cus extends Thread { //创建一个储户对象的同时,指向了同一家银行 private Bank b = Bank.getInstance(); public void run() { for (int x=0 ;x<3 ;x++ ) { b.add(100); } } } class Test1 { public static void main(String[] args) { Cus c1 = new Cus(); Cus c2 = new Cus(); c1.start(); c2.start(); } }
十二、多线程——同步函数的锁是 this
同步函数示例代码:class Ticket implements Runnable { private int tick = 100; public void run() { while(true) { this.show(); } } public synchronized void show() { if(tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"--sale: "+tick--); } } }
同步函数用的是哪一个锁呢?
函数需要被对象调用。那么函数都有一个所属对象引用。就是 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(obj)//这里使用obj,会出现0号票;如果将obj改为this,则不会出现错误票 { if(tick>0) { try{Thread.sleep(10);}catch (InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...code : "+ tick--); } } } } else { while(true) show(); } } public synchronized void show() { if(tick>0) { try{Thread.sleep(10);}catch (InterruptedException 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(InterruptedException e){} t.flag = false; t2.start(); } }
十三、多线程——静态同步函数的锁是 Class
同步函数被静态修饰后,使用的锁不是 this ,因为静态方法中也不可以定义 this 。
静态进内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象。类名.class 该对象的类型是 Class
静态的同步函数,使用的锁是该函数所在类的字节码文件对象。 类名.class
验证代码(原理同上):class Ticket implements Runnable { private static int tick=100; Object obj = new Object(); boolean flag = true; public void run() { if(flag) { while(true) { synchronized(Ticket.class)//如果该为obj则会出现0号票 { if(tick>0) { try{Thread.sleep(10);}catch (InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...code : "+ tick--); } } } } else { while(true) show(); } } public static synchronized void show() { if(tick>0) { try{Thread.sleep(10);}catch (InterruptedException 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(InterruptedException e){} t.flag = false; t2.start(); } }
十四、多线程——单例设计模式——懒汉式
面试中常会使用懒汉式。class Single { private static Single s = null; private Single(){} public static Single getInstance() { if(s==null) { synchronized(Single.class) { if(s==null) s = new Single(); } } return s; } }
懒汉式的特点:
1、实例的延时加载。
2、在多线程访问时会出现安全问题,解决办法:加同步。
3、加同步的方式:同步代码块和同步函数的方式都可以,但是比较低效;用双重判断可以解决低效的问题。.
4、加同步时,使用的锁是:该类所属的字节码文件对象。十五、多线程——死锁
在程序中请避免出现死锁。但是,面试中有时候需要写出一个死锁的程序。
代码一:class Ticket implements Runnable { private int tick=100; Object obj = new Object(); boolean flag = true; public void run() { if(flag) { while(true) { synchronized(obj) { show(); } } } else { while(true) show(); } } public synchronized void show() { synchronized(obj) { if(tick>0) { try{Thread.sleep(10);}catch (InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...code : "+ tick--); } } } } class DeadLockDemo { 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(InterruptedException e){} t.flag = false; t2.start(); } }
代码二:class Test implements Runnable { private boolean flag; Test(boolean flag) { this.flag = flag; } public void run() { if (flag) { while(true) { synchronized(MyLock.locka) { System.out.println("if locka"); synchronized(MyLock.lockb) { System.out.println("if lockb"); } } } } else { while(true) { synchronized(MyLock.lockb) { System.out.println("else lockb"); synchronized(MyLock.locka) { System.out.println("else locka"); } } } } } } class MyLock { static Object locka = new Object(); static Object lockb = new Object(); } class DeadLockTest { public static void main(String[] args) { new Thread(new Test(true)).start(); new Thread(new Test(false)).start(); } }