Java多线程

进程与线程
1. 进程一个进程包含1个以上线程,每个进程都有独立的代码和数据空间即进程上下文,进程间的切换开销比较大。进程是资源分配的最小单位。
2. 线程:每个线程有独立的运行栈和程序计数器,多个线程共享代码和数据空间,线程切换开销小。线程是CPU调度的最小单位。
3. 多进程是指操作系统能同时运行多个任务(程序)。
4. 多线程是指在同一个程序中有多个顺序流在执行。
5. 进程和线程一样分为五个阶段:创建、就绪、运行、阻塞、终止。
6. Java程序在运行中至少启动2个线程,一个是main线程,一个是垃圾收集线程


Java实现多线程的两种方法
Java实现多线程主要有两种方法,继承Thread类和实现Runable接口。还有另外一种方法:实现Callable接口,本文不做介绍。
1.继承Thread类

public class Thread1 extends Thread {
    private String name;
    private int tickets = 10;

    public Thread1(String name) {
        this.name = name;
    }

    public void run() {
        if(tickets > 0) {
            System.out.println(name + "抢到了第" + tickets-- + "张票");
        } else {
            System.out.println(name + "没有抢到票");
        }
    }

    public static void main(String[] args) {
        for(int i=0; i<10; i++) {
            new Thread1("黄牛"+i).start();
        }
    }
}

运行结果:

黄牛0抢到了第10张票
黄牛4抢到了第10张票
黄牛1抢到了第10张票
黄牛5抢到了第10张票
黄牛2抢到了第10张票
黄牛3抢到了第10张票
黄牛6抢到了第10张票
黄牛7抢到了第10张票
黄牛8抢到了第10张票
黄牛9抢到了第10张票
黄牛10抢到了第10张票
黄牛11抢到了第10张票

1)从运行结果可以看出,12个线程之间不是顺序执行,是交互执行;
2)每个线程的run()方法是独立的,tickets变量不是共享的,所以继承Thread类很难实现资源共享。

将代码修改如下,就可以实现资源共享,该方法跟实现Runnable接口类似,因为Thread也实现了Runnable接口。

public class Thread1 extends Thread {
    private String name;
    private int tickets = 10;

    public Thread1() {
    }

    public Thread1(String name) {
        this.name = name;
    }

    public void run() {
        if(tickets > 0) {
            System.out.println(Thread.currentThread().getName() + "抢到了第" + tickets-- + "张票");
        } else {
            System.out.println(Thread.currentThread().getName() + "没有抢到票");
        }
    }

    public static void main(String[] args) {
        Thread1 th1 = new Thread1();
        for(int i=0; i<12; i++) {
            new Thread(th1,"黄牛"+i).start();
        }
    }
}

运行结果:

黄牛0抢到了第10张票
黄牛2抢到了第8张票
黄牛1抢到了第9张票
黄牛3抢到了第7张票
黄牛4抢到了第6张票
黄牛5抢到了第5张票
黄牛6抢到了第4张票
黄牛7抢到了第3张票
黄牛8抢到了第2张票
黄牛9抢到了第1张票
黄牛10没有抢到票
黄牛11没有抢到票

2.实现Runnable类

public class Thread2 implements Runnable {
    private String name;
    private int tickets = 10;

    public Thread2() {
    }

    public Thread2(String name, int tickets) {
        this.name = name;
        this.tickets = tickets;
    }

    @Override
    public synchronized void run() {
        if(tickets > 0) {
            System.out.println(Thread.currentThread().getName()+"抢到了第"+tickets-- + "张票");
        } else {
            System.out.println(Thread.currentThread().getName() + "没抢到票");
        }
    }

    public static void main(String[] args) {
        Thread2 th = new Thread2();
        for(int i=0; i<12; i++) {
            new Thread(th,"黄牛"+i).start();
        }
    }
}

运行结果:

黄牛0抢到了第10张票
黄牛2抢到了第9张票
黄牛3抢到了第8张票
黄牛6抢到了第7张票
黄牛1抢到了第6张票
黄牛7抢到了第5张票
黄牛4抢到了第4张票
黄牛5抢到了第3张票
黄牛8抢到了第2张票
黄牛10抢到了第1张票
黄牛9没抢到票
黄牛11没抢到票

1)从结果看,完美实现抢票机制;
2)通过构造方法Thread(Runnable target, String name)传递一个Runnable的实例以及为线程设置名字;
3)每个线程调用的都是同一个对象的run()方法,变量tickets是线程共享的。

3.实现Runnable接口比继承Thread的好处
1)实现Runnable接口可以避免单继承的限制;
2)实现Runnable接口便于资源共享。


线程的状态
线程状态转换图如下:
线程状态转换

线程分为五个阶段:创建、就绪、运行、阻塞、终止,具体如下:
1.创建状态(new):新建一个线程对象。
2.就绪状态(runnable):调用start()方法后,线程变为可运行状态,此时线程位于线程池中,等待获取CPU的使用权。
3.运行状态(running):线程获得CPU的使用权,执行run()方法。
4.阻塞状态(blocked):线程因为某种原因放弃CPU的使用权,暂时停止运行,直到重新进入就绪状态,才有机会运行。
1)等待阻塞:线程执行wait()方法,线程进入等待池中并释放所有的锁,可以通过notify()和notifyAll()唤醒。
2)同步阻塞:线程在获取对象的同步锁时,如果锁被其它线程占用,则会进入锁池中。
3)其它阻塞:如线程执行sleep()或join()方法,会进入阻塞状态,sleep()时间到了或者join()等待的线程已经终止,线程会重新进入就绪状态。


常用函数
1.static void sleep(long millis)
Thread类中的方法,让当前线程站厅执行millis毫秒,线程不释放锁。

2.void join()
Thread类中的方法,等待该线程终止,通过例子来解释。

public class Thread3 extends Thread {
    private String name;
    public Thread3(String name) {
        super(name);
        this.name = name;
    }

    @Override
    public void run() {
        for(int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+ "--->" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"主线程开始运行");
        Thread3 th1 = new Thread3("A");
        Thread3 th2 = new Thread3("B");
        th1.start();
        th2.start();

        System.out.println(Thread.currentThread().getName()+"主线程结束运行");
    }
}

运行结果:

main主线程开始运行
A--->0
main主线程结束运行
B--->0
B--->1
A--->1
A--->2
B--->2
A--->3
B--->3
A--->4
B--->4

从运行结果可以看到,main线程比子线程A和B结束的要早,而我们期望的是main线程在所有子线程结束后才能结束,这里就可以用到join()方法了,件如下代码。

public class Thread3 extends Thread {
    private String name;
    public Thread3(String name) {
        super(name);
        this.name = name;
    }

    @Override
    public void run() {
        for(int i = 0; i < 5; i++) {
            System.out.println(Thread.currentThread().getName()+ "--->" + i);
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        System.out.println(Thread.currentThread().getName()+"主线程开始运行");
        Thread3 th1 = new Thread3("A");
        Thread3 th2 = new Thread3("B");

        th1.start();
        th2.start();

        try {
            th1.join();
            th2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println(Thread.currentThread().getName()+"主线程结束运行");
    }
}

运行结果:

main主线程开始运行
A--->0
B--->0
A--->1
B--->1
A--->2
B--->2
A--->3
B--->3
B--->4
A--->4
main主线程结束运行

3.static void yield()
Thread类中的方法,可以让当前线程从运行状态进入到就绪状态,把CPU使用权交给相同或者更高优先级的其它线程,下一个运行线程可能仍然是自己。
sleep()方法在一段时间内是不可运行的,这段时间是其他线程在运行。

4.void setPriority(int newPriority)
Thread类中的方法,可以更改线程的优先级。java线程的优先级用整数表示,取值范围是1~10,Thread类有以下三个静态常量:
static int MAX_PRIORITY,值为10
static int NORM_PRIORITY,分配给线程的默认优先级,值为5
static int MIN_PRIORITY,值为1

线程的优先级有继承关系,如线程A继承了线程B,那么B和A的优先级是一样的。
JVM虽然提供了10个优先级,但与常见的操作系统都不能很好的映射。如果希望程序能够移植到各个操作系统中,应该仅仅使用三个静态常量作为优先级,这样能保证同样的优先级采用了同样的调度方式。

5.void interrupt()
Thread类中的方法,该方法可以用来终止一个线程,但调用该方法只是将线程的中断状态设置为true,并不是直接中断线程。

Thread类中s还定义了stop()方法,这种方法不提倡使用。该方法会在代码的运行处直接抛出一个ThreadDeath错误,这是一个java.lang.Error的子类,所以直接使用stop()方法就有可能造成对象的不一致性。

如果线程在调用 Object 类的 wait()、wait(long) 或 wait(long, int) 方法,或者该类的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法过程中受阻,则其中断状态将被清除,它还将收到一个InterruptedException异常。这个时候,我们可以通过捕获InterruptedException异常来终止线程的执行,具体可以通过return等退出或改变共享变量的值使其退出。
看下面例子:

public class InterruptTest {

    public static void main(String[] args) {
        Runnable ra1 = new Runnable() {

            @Override
            public void run() {
                for(int i=0; i<10; i++) {
                    System.out.println("ra1:"+i);

                    if(Thread.currentThread().isInterrupted()) {
                        System.out.println("ra1 is interrupted...");
                        break;//中断线程
                    }
                    try {
                        //当interrupt()被调用后,中断状态为true,
                        //这时再调用sleep方法不会执行,会抛出异常InterruptedException,并清除中断状态
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        System.out.println("ra1 isInterrupted:"+Thread.currentThread().isInterrupted());
                        //重新设置中断状态为true
                        Thread.currentThread().interrupt();
                        //这里加return,可以直接中断线程
                        //return;
                    }
                }
            }
        };
        Thread th1 = new Thread(ra1);

        Runnable ra2 = new Runnable() {

            @Override
            public void run() {
                try {
                    Thread.sleep(3000);
                    th1.interrupt();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };

        Thread th2 = new Thread(ra2);

        th1.start();
        th2.start();
    }
}

运行结果:

ra1:1
ra1:2
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at lifeixu.com.multithread.InterruptTest$1.run(InterruptTest.java:18)
    at java.lang.Thread.run(Thread.java:745)
ra1 isInterrupted:false
ra1:3
ra1 is interrupted...

也可以自定义一个中断变量如“boolean stop = true/false”的方式来中断线程,这里不做介绍。

6.final void wait()
Object类中的方法,与notify()/notifyAll()必须要与synchronized一起使用。

7.final void notifyAll() & final void notify()
Object类中的方法,与wait()必须要与synchronized一起使用。
下面的代码是用来介绍wait()和notify()的,通过这两个方法来实现10次ABCD顺序输出。

public class Threadabc implements Runnable {
    private boolean stop = false;
    private String name;
    private Object prev;
    private Object self;

    public Threadabc(String name, Object prev, Object self) {
        this.name = name;
        this.prev = prev;
        this.self = self;
    }

    @Override
    public void run() {
        int count = 10;
        while(count>0) {
            synchronized(prev) {
                synchronized(self) {
                    System.out.print(name);
                    count--;
                    self.notify();//唤醒等待self的线程,同步块synchronized(self){}结束后释放self锁
                }
                if(count==0) {//循环结束后终止线程,否则线程一直处于wait状态
                    return;
                }

                try {
                    prev.wait();//本线程处于等待prev的状态,释放prev锁
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Object a = new Object();
        Object b = new Object();
        Object c = new Object();
        Object d = new Object();

        Threadabc tha = new Threadabc("A",d,a);
        Threadabc thb = new Threadabc("B",a,b);
        Threadabc thc = new Threadabc("C",b,c);
        Threadabc thd = new Threadabc("D",c,d);

        new Thread(tha).start();
        Thread.sleep(100);//保证输出的是ABCD的顺序
        new Thread(thb).start();
        Thread.sleep(100);
        new Thread(thc).start();
        Thread.sleep(100);
        new Thread(thd).start();
    }

运行结果:

ABCDABCDABCDABCDABCDABCDABCDABCDABCDABCD

关键字synchronized
推荐链接:Java中Synchronized的用法

线程间的通信

线程安全

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值