进程是一个程序的一次执行过程,是动态的。
线程是进程的一个执行路径,当进程需要执行多项任务,就需要多线程。
Java程序的线程至少有main线程和垃圾回收线程。
多线程的创建:
一 . 继承java.lang.Thread(这样就不能继承其他的类,单继承),重写run(),方法体就是线程执行的任务。
创建该类对象,调用start()启动该线程并调用相应的run()。
class TestThread{
public static void main(String[] args){
SubThread st1 = new SubThread();
st1.start();
}
}
class SubThread extends Thread{
static int ticket =100;
static Object obj =new Object(); //同步监视器
public void run(){
while(true){
synchronized(obj){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+": "+ ticket--);
}else{
break;
}
}
}
}
}
注意: 如果直接使用对象调用run(),那就不是多线程,只是普通地调用成员方法,仍然是主线程在执行。
Thread常用方法: start() //启动线程并调用run()
run()// 被重写,包含线程执行的任务体
currentThread()// 静态方法,调取当前的线程
getName()// 获取线程名字
setName(String)// 设置线程的名字
boolean isAlive()//判断线程是否仍然存活
sleep(long )//显式地让线程睡眠一段时间
yield()// 释放CPU的执行权
join()//在A线程中调用B线程的join(),表示执行到此方法时,强制停止A线程,
调用B线程直至B线程执行完毕。
二. 实现Runnable接口
1.创建一个实现java.lang.Runnable接口的类,实现run()方法;
2.创建一个该类对象,并将其作为Thread构造器的形参,创建线程对象
实际上Thread类也是实现的Runnable接口,显然实现接口的方式要优于继承的方式,
第一,实现的方式避免了java单继承的局限性;
第二,如果多个线程需要操作同一份资源,更适合使用实现的方式。
import java.lang.Runnable;
import java.lang.Thread;
class TestRunnable{
public static void main(String[] args){
threadRun thr = new threadRun();
Thread t1 = new Thread(thr);//两个线程对象共享的是同一个对象thr,thr.ticket也是一样共享
t1.start();
Thread t2 = new Thread(thr);//两个线程对象共享的是同一个对象
t2.start();
}
}
class threadRun implements Runnable{
int ticket=100;
Object obj = new Object();
public void run(){
while(true){
synchronized(obj){
if(ticket > 0){
System.out.println(Thread.currentThread().getName()+": "+ ticket--);
}else{
break;
}
}
}
}
}
使用多线程的优点:
1.提高应用程序的响应,增强用户体验。
2.提高CPU的利用率。
3.改善程序结构,将既长又复杂的进程分为多个线程独立运行,利于理解和修改。
Java中线程分为: 守护线程和用户线程。
线程的生命周期: 新建(线程对象被创建),
就绪(start(),进入线程队列等待CPU时间片),
运行(就绪的线程得到CPU执行权,run()),
阻塞(被人为挂起或者执行输入输出操作,让出CPU并临时终止自己的执行),
死亡(线程完成全部工作或线程被强制性终止)
新建-----start()--->就绪<----获得执行权(失去执行权)----->运行-------正常执行完/出现异常未处理----->死亡
运行---sleep() / 等待同步锁 /wait() / join() / suspend()---> 阻塞
阻塞---时间到/获取同步锁/notify()notifyAll()/join执行完/resume()----> 就绪
线程安全问题:
由于一个线程在操作共享数据过程中,未执行完毕的情况下,另外的线程参与进来,导致共享数据存在安全问题。
线程的同步机制:
方式一: 同步代码块 synchronized(对象){//需要被同步的代码},该对象充当共享监视器,要保证只有一个线程可以获取该监视器
对于一般方法内,使用同步代码块,可以考虑使用this,对于静态方法使用当前类(类名.class)充当监视器
方式二:同步方法 将操作共享数据的方法声明为synchronized,非静态同步方法的锁就是当前对象(只适用于实现接口方式),
静态同步方法的锁为当前类本身(类名.class)。
方式三:同步锁(JDK5.0以后增加)
当前线程的同步方法,同步代码块执行结束,或者遇到break,return结束,以及异常未处理,都会释放锁。
当前线程wait()也释放锁,但是yield(),sleep()不会释放锁。
死锁示例: 两个线程分别持有对方需要的锁,都不释放,只能一直等待
public static void main(String[] args){
StringBuffer sb1 = new StringBuffer();
StringBuffer sb2 = new StringBuffer();
new Thread(){
public void run(){
synchronized(sb1){
try{// 让线程sleep()是为了让问题更明显
Thread.currentThread().sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
sb1.append("A");
synchronized(sb2){
sb2.append("B");
System.out.println("sb1: "+ sb1+ " sb2: "+sb2);
}
}
}
}.start();
new Thread(){
public void run(){
synchronized(sb2){
try{// 让线程sleep()是为了让问题更明显
Thread.currentThread().sleep(10);
}catch(InterruptedException e){
e.printStackTrace();
}
sb1.append("C");
synchronized(sb1){
sb2.append("D");
System.out.println("sb1: "+ sb1+ " sb2: "+sb2);
}
}
}
}.start();
}
}