-------android培训、java培训、java学习型技术博客、期待与您交流! ------------
1.概述
进程:是一个正在执行中的程序。每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元。一个进程中至少有一个线程。
线程:就是进程中的一个独立的控制单元,线程在控制着进程的执行。
多线程存在的意义:充分利用cpu的空闲时间,提高进程的整体运行效率。
2.创建线程的两种方式
第一种:继承Thread类
步骤:
1.子类覆盖父类中的run方法,将线程运行的代码存放在run中。
2.建立子类对象的同时线程也被创建。
3.通过调用start方法开启线程。
示例代码:
class Demo extends Thread
{
public void run()
{
for(int i = 0;i<60;i++)
{
System.out.println(“demo run---+”i);
}
}
}
class ThreadDemo
{
public static void main(String[] args)
{
Demo d = new Demo();//创建一个线程
d.start();//调用start方法执行该线程的run方法
}
}
第二种:实现Runnable接口
步骤:
1.子类覆盖接口中的run方法
2.通过Thread类创建线程,并将实现了Runnable接口的子类对象作为参数传递给Thread类的构造函数。
3.Thread类对象调用start方法开启线程。
代码示例:
class Test implement Runnable
{
public void run()//子类覆盖接口中的run方法
{
for(int x=0; x<60; x++)
{
System.out.println(“run..."+x);
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Test te = new Test();实现Runnable接口的对象
Thread t1 = new Thread(te);将那个对象作为参数传递到Thread构造函数
Thread t2 = new Thread(te);
t1.start();//调用Thread类的start方法开启线程
t2.start();
}
}
两种线程创建方式的区别:
继承Thread类创建对象:
1.Thread子类无法再从其它类继承(java语言单继承)。
2.编写简单,run()方法的当前对象就是线程对象,可直接操作。
使用Runnable接口创建线程:
1.可以将CPU,代码和数据分开,形成清晰的模型。
2.线程体run()方法所在的类可以从其它类中继承一些有用的属性和方法。
3.有利于保持程序的设计风格一致。
继承Thread类线程代码存放于Thread子类run方法中;
实现Runnable接口线程代码存在于接口的子类run方法中,而且这种方式避免了单继承的局限性,在实际应用中,几乎都采取第二种方式。
问题:为什么要将Runnable接口的子类对象传递给Thread的构造函数?
答:因为自定义的run方法所属的对象是Runnable接口的子类对象,所以要让线程去执行指定对象的run方法,就必须明确该run方法所属的对象。
3.线程的四种状态
sleep方法需要指定睡眠时间,单位是毫秒。
冻结与运行之间的状态:准备就绪。具备了执行资格,但是没有执行权。
4.线程的对象获取与名称的操作
线程都有自己的默认名称,格式为:Thread-编号 该编号从0开始。
操作方法:
static Thread currentThread():获取当前线程对象。
getName(): 获取线程名称。
setName或者构造函数:设置线程名称。
示例代码:
class Test extends Thread
{
Test(String name)//构造方法设置线程名称
{
super(name);//继承父类方法
}
public void run()//执行代码
{
for(int x=0; x<60; x++)
{
System.out.println((Thread.currentThread()==this)+"..."+this.getName()+" run..."+x);
}
}
}
class ThreadTest
{
public static void main(String[] args)
{
Test t1 = new Test("one---");//调用构造函数设置线程名称
Test t2 = new Test("two+++");
t1.start();
t2.start();
for(int x=0; x<60; x++)
{
System.out.println("main....."+x);
}
}
}
5.线程的安全
导致安全问题的出现的原因:
1.多个线程访问出现延迟
2.线程随机性
线程安全问题在理想状态下,不容易出现,但是一旦出现对软件的影响是非常大的
解决方法:同步(synchronized)
格式:
synchronized(对象)
{
需要同步的代码;
}
同步可以解决安全问题的根本原因就是在那个对象上,该对象如同锁的功能。
4个窗口售票示例:
class Ticket implements Runnable
{
private int tick = 1000;
Object obj = new Object();//建立一个obj类对象,供synchronized作参数
public void run()
{
while(true)
{
synchronized(obj)//同步锁
{
if(tick>0)
System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--);
}
}
}
}
class TicketDemo2
{
public static void main(String[] args)
{
Ticket t = new Ticket();//创建Ticket对象供Thread对象作构造函数的参数用
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
//开始4个线程
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步的前提:
|---->同步需要两个或者两个以上的线程
|---->多个线程使用的是同一个锁
未满足这两个条件,不能称其同步。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行的效率。
同步函数
格式:在函数上加上synchronized修饰符即可。
同步函数使用的锁:函数需要被对象调用,那么函数都有一个所属对象引用,就是this。
所以同步函数使用的锁是this。
关于死锁
同步中嵌套同步,但是锁却不同,就会产生死锁
如:A线程持有A锁,B线程持有B锁,让A线程去抢夺B锁,B线程去抢夺A锁
class Dead implements Runnable
{
private booleanb = false;
Dead(booleanb)
{
this.b = b;
}
public void run()
{
while(true)
{
if(b)
{
synchronized(Locks.locka)
{//0线程,持有了A锁
System.out.println(Thread.currentThread().getName()+"locka...+..if");
//等待B锁
synchronized(Locks.lockb)
{
System.out.println(Thread.currentThread().getName()+"lockb...+..if");
}
}
}
else
{
synchronized(Locks.lockb)
{
//1线程就进来了,持有了B锁
System.out.println(Thread.currentThread().getName()+"lockb...+..else");
synchronized(Locks.locka)//等待获得A锁
{
System.out.println(Thread.currentThread().getName()+"locka...+..else");
}
}
}
}
}
}
//创造锁
class Locks
{
public static Object locka = new Object();
public static Object lockb = new Object();
}
class DeadLock
{
public static void main(String[]args)
{
Dead d1 = new Dead(true);
Dead d2 = new Dead(false);
Thread t1 = new Thread(d1);
Thread t2 = new Thread(d2);
t1.start();
t2.start();
}
}
6.线程间通信
多个线程操作同一个资源,但是操作的动作不同
注意:wait();notify();notifyAll()都使用在同步中,因为要对持有监视器的线程操作,所以要使用在同步中,因为只有同步才具有锁。
问题:wait(),notify(),notifyAll(),用来操作线程为什么定义在了Object类中?
1.这些方法存在与同步中。
2.使用这些方法时必须要标识所属的同步的锁。
3.锁可以是任意对象,所以任意对象调用的方法一定定义Object类中。
注意:不可以对不同锁中的线程进行唤醒。也就是说,等待和唤醒必须是同一个锁。
问题:wait(),sleep()有什么区别?
wait():释放cpu执行权,释放锁。
sleep():释放cpu执行权,不释放锁。
新特性:JDK1.5之后新增了java.until.concurrent.locks这个包为锁和等待条件提供一个框架的接口和类,它不同于内置同步和监视器。该框架允许更灵活地使用锁和条件,但以更难用的语法为代价。
生产和消费实例中升级:
JDK1.5 中提供了多线程升级解决方案。
将同步Synchronized替换成现实Lock操作。
将Object中的wait,notify notifyAll,替换了Condition对象。
该对象可以Lock锁 进行获取。
该示例中,实现了本方只唤醒对方操作。
代码如下:
import java.util.concurrent.locks.*;
class ProducerConsumerDemo
{
public static void main(String[] args)
{
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(pro);
Thread t3 = new Thread(con);
Thread t4 = new Thread(con);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
class Resource
{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();
private Condition condition_pro = lock.newCondition();
private Condition condition_con = lock.newCondition();
public void set(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 out()throws InterruptedException
{
lock.lock();
try
{
while(!flag)
condition_con.await();
System.out.println(Thread.currentThread().getName()+"...消费者........."+this.name);
flag = false;
condition_pro.signal();
}
finally
{
lock.unlock();
}
}
}
class Producer implements Runnable
{
private Resource res;
Producer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.set("+商品+");
}
catch (InterruptedException e)
{
}
}
}
}
class Consumer implements Runnable
{
private Resource res;
Consumer(Resource res)
{
this.res = res;
}
public void run()
{
while(true)
{
try
{
res.out();
}
catch (InterruptedException e)
{
}
}
}
}
7.停止线程
1.定义循环结束标记(run方法结束)
因为线程运行代码一般都是循环,只要控制了循环即可。
2.使用interrupt(中断)方法。
该方法是结束线程的冻结状态,使线程回到运行状态中来。
注:stop方法已经过时不再使用。
特殊情况:
当线程处于了冻结状态,就不会读取到标记。那么线程就不会结束。当没有指定的方式让冻结的线程恢复到运行状态时,这时需要对冻结进行清除,强制让线程恢复到运行状态中来。这样就可以操作标记让线程结束。
Thread类提供该方法 interrupt();
测试代码:
class StopThread implements Runnable
{
private boolean flag =true;
public synchornized void run()
{
while(flag)
{
try
{
wait();//调用线程等待
}
catch(InterruptedException e)
{
System.out.println(Thread.currentThread().getName()+"...Exception");
flag = false;//在异常中控制线程的运行
}
System.out.println(Thread.currentThread().getName()+"....run");
}
}
public void changeFlag()
{
flag = false;
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
//st.changeFlag();
t1.interrupt();//调用interrupt方法来中断线程,抛出异常
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
8.线程类的其他方法
守护线程
setDaemon():将该线程标记为守护线程或用户线程,守护线程就相当于后台线程,当前台线程结束时,后台线程跟着也结束。
注意:该方法必须在启动线程前调用。
该方法首先调用该线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException(在当前线程中)。
用法示例代码如下:
class StopThread implements Runnable
{
public void run()//原本run方法是一个死循环,但是将线程定义为守护线程后,主线程结束,访问run方法的线程立即也结束了
{
while(true)
{
System.out.println(Thread.currentThread().getName()+"....run");
}
}
}
class StopThreadDemo
{
public static void main(String[] args)
{
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
//将t1,t2定义为守护线程
t1.setDaemon(true);
t2.setDaemon(true);
t1.start();//开始线程
t2.start();
int num = 0;
while(true)
{
if(num++ == 60)
{
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");//主线程结束,t1,t2也结束
}
}
join()
抢夺cpu执行权,适用于临时加入线程用,先执行完,其他线程才执行。
测试代码如下:
class Demo implements Runnable
{
public void run()
{
for(int x=0; x<70; x++)
{
System.out.println(Thread.currentThread().getName()+"....."+x);
}
}
}
class JoinDemo
{
public static void main(String[] args) throws Exception
{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.join();//t1获取cpu的执行权,主线程处于冻结状态,只有t1结束主线程才能恢复运行状态
t2.start();
//t1.join();主线程冻结,t1,t2交替运行,t1结束,主线程才继续
for(int x=0; x<80; x++)
{ }
System.out.println("over");
}
}
setPriority()
更改线程的优先级。三个优先级分别为:
MAX_PRIORITY(最高优先级,10)
MIN_PRIORITY(最低优先级,1)
NORM_PRIORITY(默认优先级,5)
首先调用线程的 checkAccess 方法,且不带任何参数。这可能抛出 SecurityException。在其他情况下,线程优先级被设定为指定的newPriority
和该线程的线程组的最大允许优先级相比较小的一个。
yeild()
暂停当前正在执行的线程对象,并执行其他线程。(使线程交替执行)
个人总结:
线程的运行状态与线程之间的通信个人很难理解,可能还得多多写代码操练才行,线程的操作给我的感觉就是你必须要考虑得很全面,每个线程的执行状态,每个线程读取的数据什么的都必须要考虑清楚,这要才能避免线程的安全问题。还是觉得线程这一块比较难,得多下点工夫。