文章目录
1.基本概念
程序(program)是对数据描述与操作的代码的集合,是应用程序执行的脚本。
进程(process)是程序的一次执行过程,是系统运行程序的基本单位。程序是静态的,进程是动态的。系统运行一个程序即是一个进程从创建、运行到消亡的过程。
多任务(multi task)在一个系统中可以同时运行多个程序,即有多个独立运行的任务,每个任务对应一个进程。
线程(thread):比进程更小的运行单位,是程序中单个顺序的流控制。一个进程中可以包含多个线程。
简单来讲,线程是一个独立的执行流,是进程内部的一个独立执行单元,相当于一个子程序。
2.创建进程
可以通过以下两种方式自定义线程类:
创建 java.lang.Thread 类的子类,重写该类的 run方法
创建 java.lang.Runnable接 口的实现类,实现接口中的 run方法
2.1 继承Thread 类
先了解一下Thread类的构造方法
public class SummaryThread extends Thread{
//调用父类的构造方法Thread(String name)来设置名字
public SummaryThread(String name) {
super(name);
}
@Override
public void run() {
for(int i=0;i<10;i++){
System.out.println(getName()+"正在打印第"+i+"次");
}
}
public static void main(String[] args) {
SummaryThread thread1 = new SummaryThread("线程1");
SummaryThread thread2 = new SummaryThread("线程2");
thread1.start();//这里不要写成run()方法,不然就是一个普通的方法
thread2.start();//线程方法是start()!!
}
}
2.2 实现Runnable接口
Runnable 接口中只有一个未实现的 run 方法,实现该接口的类必须重写该方法。
Runnable 接口与 Thread 类之间的区别
Runnable 接口必须实现 run 方法,而 Thread 类中的run 方法是一个空方法,可以不重写
Runnable 接口的实现类并不是真正的线程类,只是线程运行的目标类。要想以线程的方式执行 run 方法,必须依靠 Thread 类
Runnable 接口适合于资源的共享
改写上述代码
public class SummaryThread implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<10;i++){
System.out.println(Thread.currentThread().getName()+" 第i次执行");
}
}
public static void main(String[] args) {
SummaryThread st = new SummaryThread();
Thread thread1 = new Thread(st, "线程1");
Thread thread2 = new Thread(st, "线程2");
thread1.start();
thread2.start();
}
}
3. 线程的生命周期
线程有5个生命周期:
1.新建(new)
2.可执行(Runnable)
3.运行(Running)
4.阻塞(Blocking)
5.死亡(Deading)
3.1 新建状态(New)
当创建了一个Thread对象时,该对象就处于“新建状态”
没有启动,因此无法运行
3.2 可执行状态(Runnable)
其他线程调用了处于新建状态线程的start方法,该线程对象将转换到“可执行状态”
线程拥有获得CPU控制权的机会,处在等待调度阶段。
3.3 运行状态(Running)
处在“可执行状态”的线程对象一旦获得了 CPU 控制权,就会转换到“执行状态”
在“执行状态”下,线程状态占用 CPU 时间片段,执行run 方法中的代码
处在“执行状态”下的线程可以调用 yield 方法,该方法用于主动出让 CPU 控制权。线程对象出让控制权后回到“可执行状态”,重新等待调度。
3.4 阻塞状态(Blocking)
线程在“执行状态”下由于受某种条件的影响会被迫出让CPU控制权,进入“阻塞状态”。
进入阻塞状态的三种情况
调用sleep方法
调用join方法
执行I/O操作
3.5 死亡状态(Dead)
处于“执行状态”的线程一旦从run方法返回(无论是正常退出还是抛出异常),就会进入“死亡状态”。
已经“死亡”的线程不能重新运行,否则会抛出IllegalThreadStateException
可以使用 Thread 类的 isAlive 方法判断线程是否活着
4.线程同步
4.1 数据共享
问题:通过多线程解决小朋友分苹果的问题:一共有5个苹果,2个小朋友同时拿苹果,每次拿一个,拿完为止
如果使用继承Thread类的方法,我们会发现以下结果
代码
public class GetApple extends Thread {
private int appleCount=5;
public GetApple(String name) {
super(name);
}
@Override
public void run() {
while(appleCount>0){
System.out.println(Thread.currentThread().getName()+"拿走一个苹果,还剩下"+(--appleCount));
}
}
public static void main(String[] args) {
GetApple g1 = new GetApple("小明");
GetApple g2 = new GetApple("小强");
g1.start();
g2.start();
}
}
小明和小强两个线程并没有共享苹果的数量,它们各自都有0个苹果。
为了数据共享,我们需要使用Runnable接口
public class GetApple implements Runnable{
private int appleCount=5;
@Override
public void run() {
while(appleCount>0){
System.out.println(Thread.currentThread().getName()+"拿走一个苹果,还剩下"+(--appleCount));
}
}
public static void main(String[] args) {
GetApple g = new GetApple();
Thread thread1 = new Thread(g,"小明");
Thread thread2 = new Thread(g,"小强");
thread1.start();
thread2.start();
}
}
虽然这一次数量是正确的了,但是显示还是有问题。最后为什么是小明拿走一个苹果还剩2个苹果呢?
从上图我们可以看出,当小明进程还在执行的时候,小强进程已经修改了好几次苹果的数量导致最后输出出现问题。
4.2 线程同步与互斥
为了防止共享对象在并发访问时出现错误,Java中提供了“synchronized”关键字。
synchronized关键字
确保共享对象在同一时刻只能被一个线程访问,这种处理机制称为“线程同步”或“线程互斥”。Java中的“线程同步”基于“对象锁”的概念
将之前的代码修改
public class GetApple implements Runnable{
private int appleCount=5;
public synchronized void getApple(){
if(appleCount>0)
System.out.println(Thread.currentThread().getName()+"拿走一个苹果,还剩下"+(--appleCount));
}
@Override
public void run() {
while(appleCount>0){
getApple();
}
}
public static void main(String[] args) {
GetApple g = new GetApple();
Thread thread1 = new Thread(g,"小明");
Thread thread2 = new Thread(g,"小强");
thread1.start();
thread2.start();
}
}
5 进程通信
当一个线程正在使用同步方法时,其他线程就不能使用这个同步方法,而有时涉及一些特殊情况:
当一个人在一个售票窗口排队买电影票,如果没有票了,就需要补充票的数量才能购买
当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用 wait() 方法
wait()方法:
中断方法的执行,使本线程等待,暂时让出 cpu 的使用权,并允许其他线程使用这个同步方法。
notify()方法:
唤醒由于使用这个同步方法而处于等待线程的 某一个结束等待
notifyall()方法:
唤醒所有由于使用这个同步方法而处于等待的线程结束等待
package testThread;
class Ticket{
int count;
int num=0;
boolean avaliable=false;
public Ticket(int count) {
this.count = count;
}
public synchronized void sell(){
if(!avaliable)//可以买票
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("售出第["+num+"]张票");
avaliable = false;
notify();
}
public synchronized void put(){
if(avaliable)//可以存票
try {
wait();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("存入第["+(num++)+"]张票");
avaliable = true;//
notify();
}
}
class Produce extends Thread{
Ticket t=null;
public Produce(Ticket t) {
this.t = t;
}
@Override
public void run() {
while(t.num<=t.count)
t.put();
}
}
class Consumer extends Thread{
Ticket t=null;
public Consumer(Ticket t) {
this.t = t;
}
@Override
public void run() {
while(t.num<=t.count)
t.sell();
}
}
public class WaitAndNotify{
public static void main(String[] args) {
// TODO Auto-generated method stub
Ticket t = new Ticket(10);
Produce p = new Produce(t);
Consumer c = new Consumer(t);
p.start();
c.start();
}
}