一、进程和线程的概念
进程:正在执行的应用程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源。多进程提高了CPU的资源使用率,但多进程并不是并发执行的,而是在很小的时间片内切换进程执行。
线程:进程的执行单元,执行路径。多线程提高了应用程序的使用率,由于Java的线程调度是抢占式调度,在没有设置优先级的情况下,多个线程随机的占用应用程序执行。
二、多线程的实现方式
- 继承Thread类
1.自定义类MyThread继承Thread类
2.MyThread类里面重写run()
不是所有类中的代码都需要被多线程执行的,为了区分哪些代码能够别线程执行,Java提供了Thread类中的run()用来包含那些被线程执行的代码。
3.创建对象
获取线程对象名
Public final String getName();获取线程名称
设置线程对象名
Public final void setName(String name);设置线程名称
MyThread mt=new MyThread("张建辉");构造方法内设置
4.启动线程
Run()仅仅是封装被线程执行的代码,直接调用是普通方法
Start()首先启动了线程对象,然后再由jvm调用该线程的run() 方法
模拟窗口卖票
public class SellTicket extends Thread{
//定义票在run方法外,static使得类在加载的时候只初始化 一次,保证多个对象共享100张票
private static int tickets=100;
@Override
public void run() {
while(true){
//实际中票是有延迟的,而且如果不加睡眠线程票数的变化不是顺序的。
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(tickets>0){
//继承Thread类,所以可以直接使用getName()方法
System.out.println(getName()+"正在出售第"+(tickets--)+"张票");
}
}
}
}
public class SellTicketDemo {
public static void main(String[] args) {
//创建资源对象
SellTicket s1=new SellTicket();
SellTicket s2=new SellTicket();
SellTicket s3=new SellTicket();
//创建线程对象
s1.setName("窗口1");
s2.setName("窗口2");
s3.setName("窗口3");
s1.start();
s2.start();
s3.start();
}
}
- 实现Runnable接口
1.自定义类MyRunnable实现Runnable接口
2.重写run()方法:run()里面封装了被线程执行的代码
3.创建MyRunnable()类的对象
4.创建Thread类的对象,并把3步骤的对象作为构造参数传递
模拟窗口卖票
public class SellTickets implements Runnable {
private int tickets=100;
//创建锁对象
private Object obj=new Object();
@Override
public void run() {
while(true){
//同步代码块,括号中相当于锁的功能,实现够共享100张票。
synchronized(obj){ //发现这里代码将来是会被锁上的,所以t1进来后,就锁了。
if(tickets>0){
try {
Thread.sleep(100);//t1睡眠了
} catch (InterruptedException e) {
e.printStackTrace();
}
//由于实现的是Runnable接口,所以间接的用Thread类的getName()方法
System.out.println(Thread.currentThread().getName()+"正在出售第"+(tickets--)+"张票");
}
}
//t1出来后锁就开了
}
}
}
public class SellTicketsDemo {
public static void main(String[] args) {
SellTickets s=new SellTickets();
Thread t1=new Thread(s,"窗口1");
Thread t2=new Thread(s,"窗口2");
Thread t3=new Thread(s,"窗口3");
t1.start();
t2.start();
t3.start();
}
}
- 两种方式的比较
1.方式2可以避免由于Java单继承带来的局限性(可以多实现)
2.方式2适合多个相同程序的代码去处理同一个资源的情况,把线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。
三、同步与锁的问题
我们知道多个线程是抢占式的占用程序资源,如果一个线程进入方法后,一旦被其他线程抢占,其他线程就能够同样执行同一个方法,但是如果上一个线程还没有结束,而方法中又包含有共享数据,就会出现线程安全问题。如果有一种工具可以使一个线程使用共享数据,只有这个线程结束使用共享数据之后,其他线程才能够使用,这就是Java提供的同步和锁。
Synchronized关键字
A:同步代码块:同步可以解决安全问题的根本原因就是在对象上,对象如同锁的功能一样,多线程必须是同一把锁。synchronized(对象) {需要被同步的代码;}
这里的锁对象可以是任意对象。
B:同步方法:把同步加在方法上。
synchronized(this) {需要被同步的代码;}
这里的锁对象是this
C:静态同步方法
synchronized(类.class) {需要被同步的代码;}
这里的锁对象是当前类的字节码文件对象
Lock接口(使用ReenTranLock实现类)
Voidlock()加锁
Voidunlock()释放锁
直接在所要加锁的代码前后使用lock 和unlock方法
四、线程组、线程池
线程组ThreadGroup是由多个线程组成的集合,ThreadGroup类提供了一些方法,可以对组内的所有线程执行相同的操作统一调用,而不需对单个线程单独写方法。
线程池里的每一个线程代码结束后,并不会死亡,而是再次回到线程池中成为空闲状态,等待下一个对象来使用。
五、线程的生命周期
新建 -- 就绪 -- 运行 -- 死亡
新建 -- 就绪 -- 运行 -- 阻塞 -- 就绪 -- 运行 -- 死亡