------- android培训、java培训、期待与您交流! ----------
定义线程
一.继承Thread类
1.步骤:
1).定义类继承Thread类
2).覆写Thread类中的run()方法
目的:将自定义的代码存储在run方法中,让线程运行。
3).调用线程的start方法,该方法启动线程,并且调用run方法
eg:
class Demo extends Thead{
public void run(){
System.out.println("demo run");
}
}
class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();
d.start();
}
}
结果发现每一次的结果都不同
因为多个线程都获取CPU的执行权,cpu执行到谁,谁就运行
明确一点,在某一个时刻,只能有一个程序在运行(多核除外)
cpu在做着快速的切换,已达到看上去是同时运行的效果。
我们可以形象的把多线程的运行行为在互相抢夺cpu的执行权
这就是多线程的一个特征:随机性。谁抢到谁执行,至于执行多长时间,cpu说的算。
2.为什么要覆写run方法?
Thread类用于描述线程。
该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
也就是Tread类中的run方法,用于存储线程要运行的代码。
public static void main(String[] args){
Demo d = new Demo();
d.run(); // 仅仅是对象调用run方法,线程创建了,但没运行
d.start(); // 启动线程,并且调用run方法
}
3.线程生命周期
如图:
4.线程中常见方法:
1).static Thread currentThread() 获取当前线程对象
2).getName():获取线程名称
3).设置线程名称:setName或者构造函数
eg:
class Test extends Thread{
Test(String name){
super(name);
}
public void run(){
System.out.println((Thread.currentThread()==this)+"..."+this.getName()+"...run...");
}
}
class ThreadDemo{
public static void main(String[] args){
Test t1 = new Test("one");
Test t2 = new Test("two");
t1.start();
t2.setName("TWO");
t2.start();
}
}
结果:
true...one...run...
true...TWO...run...
5.举例:简单的卖票程序,多个窗口同时卖票
class Ticket extends Thread{
private int tick = 3;
public void run(){
while(tick > 0){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
}
class ThreadDemo{
public static void main(String[] args){
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
t1.start();
t2.start();
t3.start();
}
}
结果:
Thread-0...3
Thread-2...3
Thread-1...3
Thread-2...2
Thread-0...2
Thread-2...1
Thread-1...2
Thread-1...1
Thread-0...1
发现每个窗口都卖了三张票
解决方法:tick定义为静态
private static int tick = 3;
但是因为static的生命周期太长,占用资源,所以通过线程的另一种创建方式解决,接口。
二.实现runnable接口
1.步骤:
1).定义类实现Runnable接口
2).覆盖Runnable接口中的run方法
将线程要运行的代码存放在run方法中
3).通过Thread类建立线程对象
4).将Runnable接口的子类对象作为实际参数传递给Thread类的构造函数
为什么要将Runnable接口的子类对象传递给Thread的构造方法?
因为自定义的run方法所属的对象时Runnable接口的子类对象,所以要让线程取指定对象的run方法,就必须明确该run方法所属的对象。
5).通过Thread类的start方法开启线程并调用Runnable接口子类的run方法
2.实现接口方式和继承方式有什么区别?
1).避免了单继承的局限性,如Student类需要继承Person,并且类中部分代码还需要多线程处理
2).继承Thread类:线程代码存放在Thread子类run方法中
实现Runnable接口:线程代码存放在接口子类的run方法中
3.解决卖票问题
class Ticket implements Runnable{
private int tick = 6;
public void run(){
while(tick>0){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
}
public class ThreadDemo02 {
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();
}
}
结果:
Thread-1...5
Thread-2...4
Thread-0...5
Thread-0...3
Thread-0...2
Thread-2...1
以上结果不稳定,每次的结果可能都不一样。但是足以说明问题。
原因?
当多条语句在操作同一个线程共享数据时,一个线程对多条语句只执行了一部分,还没有执行完,另一个线程参与进来执行,导致共享数据的错误
解决办法:对多条操作共享数据的语句,只能让一个线程都执行完,在执行过程中其他线程不可以参与执行。
java对于多线程安全问题提供了同步代码块解决方式。
synchronized(对象){
需要被同步的代码;
}
class Ticket implements Runnable{
private int tick = 6;
Object obj = new Object();
public void run(){
while(tick>0){
synchronized(obj){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
}
}
}
相当于锁的功能
经典实例:火车上卫生间,一个乘客进去上锁、出来开锁,另一个人乘客才能进去,上锁。。。
同步的前提:
1).必须要有两个或者两个以上的线程
2).必须是多个线程使用同一个锁
弊端:多个线程需要判断锁,较为消耗资源,但是在可接受范围内
开发中代码结果出现问题,需要同步,如何判断同步位置
1).明确哪些代码是多线程运行代码
2).明确共享数据
3).明确多线程运行代码中哪些语句是操作共享数据的
函数是用来装一些需要全部执行的代码,而同步代码块也是用来装一些需要全部执行的代码,只不过同步代码块拥有了同步的特性,如果函数也赋予同步的特性,那么它也可以实现同步的功能,即同步函数
eg:public void run(){
while(tick>0){
show();
}
}
public synchronized void show(){
System.out.println(Thread.currentThread().getName()+"..."+tick--);
}
同步函数用的是哪一个锁呢?
函数需要被对象调用,那么函数都有一个所属对象引用,就是this
所以同步函数使用的锁是this.
验证:使用两个线程来卖票,一个线程在同步代码块中,一个线程在同步函数中,都在执行卖票动作。
class Ticket implements Runnable{
private int tick = 10;
Object obj = new Object();
boolean flag = true;
public void run(){
if (flag) {
while(true){
synchronized (obj) {
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(){
if(tick>0){
try{Thread.sleep(10);}catch(Exception e){};
System.out.println(Thread.currentThread().getName()+"...show"+tick--);
}
}
}
public class ThreadDemo02 {
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){};
t.flag = false;
t2.start();
}
}
结果:
Thread-0...code10
Thread-0...code9
Thread-1...show8
Thread-1...show7
Thread-0...code6
Thread-1...show5
Thread-0...code4
Thread-0...code3
Thread-1...show2
Thread-1...show1
Thread-0...code0
最后出现0票的问题
此时run方法用的锁是obj,如果,把obj的锁改成this,结果如下:
Thread-0...code10
Thread-1...show9
Thread-1...show8
Thread-1...show7
Thread-1...show6
Thread-1...show5
Thread-1...show4
Thread-1...show3
Thread-1...show2
Thread-1...show1
此时发现另一个现象,如果把show方法定义为静态的,执行结果又出现0票的问题,结果显示:略。
说明,如果同步函数被静态修饰后,使用的锁不再是this了,同时静态方法中也不可以定义使用this,那么此时的锁是什么呢?
类进入内存首先要变成字节码文件对象,类.class文件
静态进入内存时,内存中没有本类对象,但是一定有该类对应的字节码文件对象,所以此时的锁就是Ticket.class。
所以静态的同步方法没使用的锁是该方法所在类的字节码文件对象,即类名.class。
三.多线程死锁
偶尔面试的时候,会要求写一段死锁程序
eg:
class Test implements Runnable{
private boolean flag;
Test(boolean flag){
this.flag = flag;
}
public void run(){
if(flag){
synchronized(MyLock.Locka){
System.out.println("if locka");
synchronized(MyLock.Lockb){
System.out.println("if lockb");
}
}
}else{
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){
Thread t1 = new Thread(new Test(true));
Thread t2 = new Thread(new Test(false));
t1.start();
t2.start();
}
}
结果:
if locka
else lockb
四.线程间通信-等待唤醒机制
wait();
notify();
notifyAll();
此三种方法都使用在同步中,因为要对持有监视器(锁)的线程操作。
所以要使用在同步中,因为只有同步才具有锁。
为什么这些操作线程的方法要定在在Object类中呢?
因为这些方法在操作同步中线程时,都必须要标识它们所操作线程只有的锁,只有同一个锁上的被等待线程,可以被同一个锁上notify唤醒,不可以对不同锁中的线程进行唤醒,也就是说,等待和唤醒必须是同一个锁。
而锁可以是任意对象,所以可以被任意对象调用的方法定义在Object类中。
案例:生产者消费者
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
public synchronized void producer(String name){
while (flag) {
try{wait();}catch(Exception e){}
}
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"+++++生产者+++++"+this.name);
notifyAll();
flag = true;
}
public synchronized void consumer(){
while (!flag) {
try{wait();}catch(Exception e){}
}
System.out.println(Thread.currentThread().getName()+"-----消费者------------"+this.name);
notifyAll();
flag = false;
}
}
对于多个生产者和消费者,为什么要定义while判断标记?
原因:让被唤醒的线程再一次判断标记
为什么定义notifyAll?
因为需要唤醒对方线程,只用notity,容易出现只唤醒本方线程的情况,导致程序中的所有线程都在等待。
生产者消费者jdk1.5升级版
Lock接口和Condition接口
显式lock操作替代synchronized,lock()拿锁,unlock()解锁
将object中的wait,notify,notifyAll替换成了condition中await,signal,signalAll
升级后:
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
Lock lock = new ReentrantLock();
Condition condition_pro = lock.newCondition();
Condition condition_con = lock.newCondition();
public void producer(String name) throws InterruptedException {
lock.lock();
try{
while (flag) {
condition_pro.await();
}
this.name = name+"---"+count++;
System.out.println(Thread.currentThread().getName()+"+++++生产者+++++"+this.name);
flag = true;
condition_con.signal();
}finally{
lock.unlock();// 释放锁的动作一定要执行
}
}
public void consumer(){
lock.lock();
try{
while (!flag) {
condition_con.await();
}
System.out.println(Thread.currentThread().getName()+"-----消费者------------"+this.name);
flag = false;
condition_pro.signal();
}finally{
lock.unlock();
}
}
}
五.多线程-停止线程
stop()方法已经过时
如何停止线程?
只有一种,run()方法结束
开启多线程运行,运行代码通常是循环结构
只要控制住循环,就可以让run方法结束,也就是线程结束
添加标记,判断.
当线程处于冻结状态,就不会读取到标记,那么线程就不会结束
当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结状态进行清除,强制让线程恢复到运行状态中,就可以操作标记,让线程结束
Thread中提供了该方法,interrupt().
六.守护线程
线程.setDaemon(true);
当正在运行的线程都是守护线程时,jvm退出。
如当前运行的线程都是守护线程,并且全部wait了,则虚拟机退出。
该方法必须在启动线程前调用
七.多线程-join方法
join可以用来临时加入线程执行
当A线程执行到了B线程的join()方法,A线程就会等待,等B线程都执行完,A才会执行
八.优先级和yield方法
线程覆写object的toString()方法
此方法返回该线程的字符串表现形式,包括线程名称,优先级和线程组。
setPriority()更改线程优先级,默认等级是5
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
yield()可以减缓线程执行频率
九.多线程创建示例
三个for循环,顺序执行效率低,封装成线程提高效率
class ThreadTest{
new Thread(){
public void run(){
for(int i = 0; i<=50; i++){
System.out.println("Hello World!!");
}
}
}.start();
for(int i = 0; i<=50; i++){
System.out.println("Hello World!!");
}
Runnable r = new Runnable(){
public void run(){
for(int i = 0; i<=50; i++){
System.out.println("Hello World!!");
}
}
};
new Thread(r).start();
}