1.进程和线程
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
2.线程的生命周期及五种基本状态
(1)新建状态(New):当线程对象对创建后,即进入了新建状态,如:Thread t = new MyThread();
(2)就绪状态(Runnable):当调用线程对象的start()方法(t.start();),线程即进入就绪状态。处于就绪状态的线程,只是说此线程已经做好了准备,随时等待CPU调度执行,并不是说执行了t.start()此线 程立即就会执行;
(3)运行状态(Running):当CPU开始调度处于就绪状态的线程时,此时线程才得以真正执行,即进入到运行状态。注:就 绪状态是进入到运行状态的唯一入口,也就是说,线程要想进入运行状态 执行,首先必须处于就绪状态中;
(4)阻塞状态(Blocked):处于运行状态中的线程由于某种原因,暂时放弃对CPU的使用权,停止执行,此时进入阻塞状态,直到其进入就绪状态,才有机会再次被CPU调用以进入到运行状态。根据阻 塞产生的原因不同,阻塞状态又可以分为三种:
①等待阻塞:运行状态中的线程执行wait()方法,使本线程进入到等待阻塞状态;
②同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态;
③其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
(5)死亡状态(Dead):线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
3.线程的创建方式
(1)继承Thread类
①定义一个类继承Thread类
②覆盖Thread类中的方法
③直接创建Thread的子类对象创建线程
④调用start方法开启线程并调用线程的任务run方法执行
class Demo extends Thread { private String name; Demo(String name) { this.name=name; } public void run() { for(int i=0;i<10;i++) { System.out.println("i="+i+" name="+Thread.currentThread().getName()); } } } class ThreadDemo { public static void main(String[] args) { Demo demo1=new Demo("С?"); Demo demo2=new Demo("κ?"); demo1.start(); demo2.start(); for(int i=0;i<10;i++) { System.out.println("i="+i+" name="+Thread.currentThread().getName()); } } }
(2)实现Runnable接口
①定义类实现Runnable接口。
②覆盖接口种的run方法,将线程的任务代码封装到run方法中。
③通过Thread类创建线程对象,并将Runnable接口的子类对象作为Thread类的构造参数进行传递。
④调用线程对象的start方法开启线程。
class Demo implements Runnable { public void run() { show(); } public void show() { for(int x=0; x<20; x++) { System.out.println(Thread.currentThread().getName()+"....."+x); } } } class ThreadDemo { public static void main(String[] args) { Demo d = new Demo(); Thread t1 = new Thread(d); Thread t2 = new Thread(d); t1.start(); t2.start(); } }
4.线程的安全问题(同步)
(1)同步代码块
synchronized(对象)
{
需要被同步的代码;
}
class Ticket implements Runnable { private int num=100; private Object obj=new Object(); public void run() { while(true) { synchronized(obj) { if(num>0) { try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println(Thread.currentThread().getName()+"...sale..."+num--); } } } } } class TicketDemo { public static void main(String[] args) { Ticket t=new Ticket(); Thread t1=new Thread(t); Thread t2=new Thread(t); t1.start(); t2.start(); } }
(2)同步函数
使用synchronized关键字
class Bank { private static int sum=0; public synchronized void add(int num) { sum+=num; try{Thread.sleep(10);}catch(InterruptedException e){} System.out.println("sum="+sum); } } class Customer implements Runnable { private Bank b=new Bank(); public void run() { for(int i=1;i<=3;i++) { b.add(100); } } } class BankDemo { public static void main(String[] args) { Customer c=new Customer(); Thread t1=new Thread(c); Thread t2=new Thread(c); t1.start(); t2.start(); } }
(3)同步代码块和同步函数的区别
①同步函数的使用的锁是this,同步代码块的锁是任意的对象,建议使用同步代码块
②静态的同步函数使用的锁是该函数所属的字节码文件对象,可以用getClass方法获取,也可以用当前类名.class表示
5.线程间的通信
(1)等待唤醒机制
①wait():让线程处于冻结状态,被wait的线程会被存储到线程池中
②notify():唤醒线程池中的一个线程(任意)
③notifyAll():唤醒线程池中的所有线程
注意:这些方法都必须定义在同步中,必须要明确到底操作的是哪个锁上的线程。
锁可以是任意的对象,任意的对象调用的方式一定定义在Object类中