------- android培训、java培训、java学习型技术博客期待与您交流! ----------
多线程
1 创建线程
方式一:继承Thread类,覆盖run方法。
步骤:1、定义类继承Thread。
2、复写Thread类中的run方法。
3、调用线程的start方法,该方法两个作用:启动线程,调用run方法。
package cn.itcast.thread;
//写一个类继承Thread,模拟单线程的
class Demo extends Thread{
Demo(String name){
super(name);
}
public void run(){
for(int x = 0 ; x < 10;x++){
System.out.println("demoRun..."+x+getName());
}
}
}
public class ThreadDemo {
public static void main(String[] args){
Demo d = new Demo("旺财");
d.start();
for(int x = 0 ; x < 10 ; x++){
System.out.println("main..."+x+Thread.currentThread().getName());
}
}
}
发现运行结果每次都不同。
因为多个线程都获得cpu的执行权,cpu执行到谁,谁就运行。
明确一点,在某一个时刻,只能有一个线程在运行,cpu咋做着快速的切换,已达到看上去是同时运行的效果。
我们可以形象把多线程的运行行为在互相抢夺cpu的执行权。
这就是多线程的一个特性:随机性,谁抢到谁执行,至于执行多长,cpu说了算。
为什么要覆盖run方法呢?
Thread类用于描述线程。该类就定义了一个功能,用于存储线程要运行的代码,该存储功能就是run方法。
也就是说Thread类中的run方法,用于存储线程要运行的代码。
线程默认名称,this.getName(),是从0开始。
用继承Thread的创建多线程模拟售票
package cn.itcast.thread;
/*
* 利用多线程,实现模拟多窗口同时售票
*/
//定义类,票,模拟售票
class Ticket extends Thread{
private static int tickets = 100;
Object obj = new Object();
public void run(){
while(true){
synchronized (obj) {
if(tickets>0){
try{
Thread.sleep(5);
}catch(Exception e){}
System.out.println(getName()+"..出售第"+tickets--+"张票");
}
}
}
}
}
public class TicketDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
方式二:实现Runable接口
步骤:1,定义类实现Runable接口。
2,覆盖Runable接口中的run方法。
3,通过Thread类建立线程对象。
4,将Runable接口的子类对象作为实际参数传递给Thread的构造函数。
5,调用Thread类的start方法开启线程并调用Runable接口子类的run方法。
package cn.itcast.thread;
//写一个类实现Runnable,模拟单线程的
class Demo implements Runnable{
public void run(){
for(int x = 0 ; x < 10;x++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("demoRun..."+x);
}
}
}
public class ThreadDemo {
public static void main(String[] args){
Demo d = new Demo();
Thread t = new Thread(d);
t.start();
for(int x = 0 ; x < 10 ; x++){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("main..."+x+Thread.currentThread().getName());
}
}
}
实现方式和继承方式有什么不同?
实现方式的好处:避免了单继承的局限性。在定义线程时,建议使用实现方式。
继承Thread:线程代码存放Thread子类run方法中。
实现Runable,线程代码存在接口的子类的run方法中。
线程的五种状态:
创建:start()
临时阻塞:CPU在执行某个线程时,其它线程都不会运行,具备执行资格,但是没有执行权
冻结:sleep()或wait(),时间到notify()
运行:run()或stop()
消亡:run()方法结束
sleep()需要指定睡眠时间,单位是毫秒;
特殊的状态:临时阻塞。具备了执行资格,但是还没有获取资源;
2 解决线程安全问题
解决原理:让多条操作共享数据的代码在某一时间段被一个线程执行,执行过程中其它线程不参与运算;
同步代码块: synchronized(对象) {
需要同步的代码;
}
将访问数据的代码锁住,在同步代码块中的内容同一时间内只能一个线程执行;
同步可以解决安全问题的根本原因就在那个对象上;该对象如同锁的功能;
同步方法:方法上加synchronized,整个方法的代码都是同步的,只能一个线程运行,同步方法使用this作为锁;
同步的前提:要两个或者两个以上的线程;多个线程使用的是同一个锁;
同步的好处:同步的出现解决了多线程的安全问题;
同步的弊端:当线程相当多时,每个线程都会去判断同步上的锁耗费资源,降低程序的运行效率;
死锁:在多个线程并发执行使用多个锁来同步时,有可能互相冲突,导致程序无法继续执行(同步嵌套);
同步出现解决了线程安全问题,但是非常消耗资源,效率会降低并且容易引发死锁;
3 线程间通信
在同步代码中可以使用锁对象的wait()方法让当前线程等待;
使用锁对象的notify()方法可以将正在等待的线程唤醒;
如果多个线程都在等待,notify()唤醒随机1个;
notifyAll()方法可以唤醒所有在等待的线程;
wait(),notify(),notifyAll(),这些用来操作线程存在与同步中,使用这些方法时必须要标识所属的同步的锁,
锁可以是任意对象,所以任意对象调用的方法一定定义Object类中;
wait(),sleep()区别:
wait():释放cpu执行权,释放锁;
sleep():释放cpu执行权,不释放锁;
4 JDK5之后的线程同步与通信
同步:
使用java.util.concurrent.locks.Lock接口的实现类对象来进行同步;
ReentrantLock就是Lock的实现类,可以实现synchronized的功能;
在需要同步的代码块前后使用lock()和unlock()方法来完成同步;
unlock()最好放在finally中,因为如果上面代码抛出异常没有解锁;,会导致其它线程无法运行,程序卡死;
通信:
使用Lock对象的newCondition()方法获取一个Condition对象,Condition对象可以控制指定线程的等待与唤醒;
await()方法可以控制线程等待;
signal()方法可以唤醒等待的线程;
signalAll()方法可以唤醒所有等待线程;
5 停止线程
定义循环结束标记:因为线程运行代码一般都是循环,只要控制了循环即可;
使用interrupt()中断:该方法是结束线程的冻结状态,使线程回到运行状态中来;
注:stop()已经过时不再使用;
6 线程中方法
currentThread():静态方法,返回当前线程对象的引用;
getName(),setName():用来获取、设置当前线程的名字;
sleep():控制线程休眠,单位为毫秒;
setDaemon():将线程设置为守护线程。线程默认是非守护线程,守护线程不能单独执行;
join():让出执行权,当前线程暂停,等待加入的线程运行结束,当前线程再执行;
setPriority(int num):设置线程优先级;
toString():返回该线程的字符串表示形式,包括线程名称、优先级和线程组;
Thread(String name):构造函数,线程对象一建立就可以指定名称;
setName(String name):自定义线程名字