进程和线程、多线程的实现方式、设置和获取线程名称、线程调度、线程控制、线程生命周期表、Runnable(附案例)、synchronized、线程安全的类、Lock、Wait、生产者消费者案例
目录
进程和线程
进程:
概述:正在运行的程序,是系统进行资源分配和调用的独立单位,每一个进程都有它自己的内存空间和系统资源
线程:
概述:进程中的单个顺序控制流,是一条执行路径
单线程:一个程序如果只有一条执行路径,则称为单线程程序
多线程:一个程序如果有多条执行路径,则称为多线程程序
多线程的实现方式
Thread
Thread
public class Thread
extends Object
implements Runnable
实现过程:
- 创建一个新的执行线程有两种方法,一个是将一个类声明为一个Thread子类
- 这个子类应该重写Thread中的run()方法,然后可以分配并启动子类的实例
代码示例:
public class MyThread extends Thread{
//定义类继承Thread类
@Override
public void run(){
for(inti=0;i<100;i++){
System.out.println(i);
}
}
}
public static void main(String[] args){
MyThread t1=new MyThread();
MyThread t2=new MyThread();
//void start():启动线程;然后由JVM调用此线程的run()方法
t1.start();
t2.start();
}
注意:
- 为什么要重写run()方法?
因为run()是java用来封装被线程执行的代码 - run()方法和start()方法的区别:
run():封装线程执行的代码,直接调用,相当于普通方法的调用
start():启动线程;然后由JVM调用此线程的run()方法
设置和获取线程名称
Thread类中设置和获取线程名称的方法
void setName(String name):将此线程的名称更改为等于参数name
String getName(); 返回此线程的名称
通过构造方法也可以设置线程名称
代码示例:
public class MyThread extends Thread{
/* MyThread(){} 要调用Thread带参构造时需要将String Name传入父类 MyThread(String Name){ super(Name); } */
@Override
public void run(){
for(int i=0;i<100;i++){
System.out.println(getName()+"="+i);
}
}
}
public static void main(String[]args){
MyThread t1 = new MyThread();
//调用MyThread t1 = new Mythread("线程1");的带参构造方法后
//需要在MyThread类中写带参构造方法将String Name传给Thread父类
MyThread t2 = new MyThread();
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
线程调度
线程有两种调度模型
- 分时调度模型:所有线程轮流使用CPU的使用权。平均分配每个线程占用CPU的时间片
- 抢占式调度模型:让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个线程使用,优先级高的线获得的CPU时间片相对会多一些
- JAVA使用的是抢占式线程调度
注:假如计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU的时间片,也就是使用权,才可以执行指令。所以说多线程程序的执行顺序是有随机性的,因为谁抢到CPU的使用权是不一定的
Thread类设置线程优先级的方法
| 方法名 | 作用 |
|---|---|
| public final int getPriority() | 返回此线程的优先级 |
| public final void setPriority(int newPriority) | 更改此线程的优先级 |
代码示例:
public static void main(Strin g[] args){
MyThread t1 = new MyThread("高铁");
MyThread t2 = new MyThread("飞机");
MyThread t3 = new MyThread("汽车");
//System.out.println(t1.getPriority()); //线程默认线程为5
//System.out.println(t2.getPriority()); //线程优先级的MAX_PRIORITY==10
//System.out.println(t3.getPriority()); //线程优先级的MIN_PRIORITY==1
t1.setPriority(5);
t2.setPriority(10);
t3.setPriority(1);
t1.start();
t2.start();
t3.start();
}
注意:线程默认优先级是5;线程优先级范围是1-10,线程优先级高仅仅表示线程获取的CPU时间片的几率高,但是要在次数比较多,或者多次运行的时候才能看到你想要的效果
线程控制(Thread类方法)
| 方法名 | 作用 |
|---|---|
| static void sleep(long millis) | 使当前正在执行的线程停留(暂停执行)指定的毫秒数 sleep是线程中的方法、wait()是Object类中的方法 |
| void join() | 等待这个线程死亡才能执行下一个线程 |
| void setDaemon(boolean on) | 将此线程标记为守护线程,当运行的线程都是守护线程时,Java虚拟机将退出 |
代码示例:
Thread t1=new Thread("刘备");
Thread.sleep(1000); //表示线程停留一秒 (1m=1000ms)
t1.join(); //表示该进程结束后才会进入下面的进程
Thread.currentThread.setName("张飞");
//设置一个主线程 Thread.currentThread :表示获取当前正在执行的线程
t1.setDaemon(); //设置t1为守护线程
线程的生命周期
注:start()方法调用后线程进去就绪状态
t1.start();
t2.start(); //此时t1、t2线程进入就绪状态等待CPU分配时间片
Runnable
多线程实现的方式有两种:
1. 继承Thread类重写run()方法
2. 实现Runnable接口重写run()方法实现
注:和继承Thread相比实现Runnable接口可以避免Java无法多继承的局限性,适合多个相同程序的代码去处理同一个资源的情况,把线程的程序的代码、数据有效分离,较好的体现了面向对象的设计思想
卖票案例
需求:某电影院目前正在上映国产大片,共有100张票,而它拥有3个窗口卖票,请设计一个程序模拟该电影院卖票
思路:
- 定义一个SellTicket类实现Runnable接口,里面定义:private int Ticket=100;
- 在SellTicket类中重写run()方法
- 定义一个测试类SellTicketDemo类,里面有main()方法
代码示例:
public class SellTicketDemo {
public static void main(String[] args) {
SellTickle s1=new SellTickle();
Thread t1=new Thread(s1,"线程1");
Thread t2=new Thread(s1,"线程2");
Thread t3=new Thread(s1,"线程3");
t1.start();
t2.start();
t3.start();
}
}
public class SellTickle implements Runnable{
private int Tickle=100;
@Override
public void run() {
while(true){
//加入同步代码块避免一对象重复运行的情况
synchronized(this){
if(Tickle>0){
try {
Thread.currentThread().sleep(100);
//t1线程休息100毫秒
//t2线程抢到了CPU的执行权,t2线程就开始执行,执行到这里的时候t2线程休息100毫秒
//t3线程抢到了CPU的执行权,t3线程就开始执行,执行到这里的时候t3线程休息100毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
//假设线程按照顺序醒过来
//t1抢到CPU的执行权,在控制台输出:窗口1正在售出第100张票
System.out.println(Thread.currentThread().getName()+"窗口卖出第"+Tickle+"张票");
//t2抢到CPU的执行权,在控制台输出:窗口3正在售出第100张票
//t3抢到CPU的执行权,在控制台输出:窗口3正在售出第100张票
Tickle--;
}
}
}
}
}
synchronized
案例思考:
- 在线程中延迟卖票的动作,每次出票事件100毫秒,用sleep()方法实现
卖票出现了问题:
- 相同的票出现了多次
- 出现了负数的票
问题原因:
- 线程执行的随机性导致的
出现线程安全问题的条件:
1.是否是多线程环境
2.是否有共享数据
3.是否有多条语句操作共享数据
解决办法:
使用同步代码块(synchronized)
锁多条语句操作共享数据,可以使用同步代码块实现
格式:
synchronized(任意对象名){
多条语句操作共享数据的代码
}
注:就相当于给代码加锁了,任意对象就可以看成是一把锁
好处:解决了多线程的数据安全问题
坏处:当线程很多时,每个线程都会去判断同步上的锁,非常耗费时间,无形中降低了程序运行的效率
同步方法
同步方法:就是把synchronized关键字加到方法上
格式:
修饰符 synchronized 返回值类型 方法名(方法参数){…}
同步方法的锁对象是什么?
this
同步静态方法:就是把synchronized关键字加到静态方法上
格式:
修饰符 static synchronized 返回值类型 方法名(方法参数){…}
例:
同步方法:public synchronized void SellTickets(){…} //里面锁的对象名是this this是非静态方法的锁对象
同步静态方法:public static synchronized void SellTickets(){…} //静态和类相关 里面的锁是 (类名.class) :得到该类的字节码对象
线程安全的类
- StringBuffer:线程安全的类,支持同步。而StringBuilder的功能支持StringBuffer所有相同的操作,StringBuilder相比StringBuffer更快,因为StringBuilder不执行同步
- (一般不用)Vector:线程安全的类,支持同步。实现List接口,与新的集合实现不同,Vector被同步。如果不需要线程安全的实现,建议使用ArrayList代替Vector
- (一般不用)Hashtable:线程安全的类,支持同步。实现Map<K,V>接口,与新的集合实现不同,Hashtable被同步。如果不需要线程安全的实现,建议使用HashMap代替Hashtable。
将线程不安全的类转换为线程安全类的方法
- 调用Collections静态类中的static List synchronized(List list); 方法可将List list转换为线程安全的类 返回一个集合对象
例:List list = Collections.synchronized(new ArrayList()); //将ArrayList()这个线程不安全的方法转换为了线程安全的方法然后赋值给list - Hashtable和Vector的转换格式同理
Lock(interface)
Lock实现提供了获得锁和释放锁的方法:
| 方法名 | 作用 |
|---|---|
| void lock() | 获得锁 |
| void unlock() | 释放锁 |
代码示例:
Lock是接口不能直接实例化,这里采用它的实现类ReentrantLock来实例化ReetrantLock实现类来实现Lock锁功能
ReentrantLock():创建一个ReentrantLock的实例
private Lock lock=new ReentrantLock();
try{
lock.lock();
if(tickets>0){
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"正在卖第:"+tickets+"张票");
tickets--;
}
}finally{
lock.unlock();
}
//在使用Lock锁时,一般使用ReentrantLock进行实例化
//在需要上锁的地方进行 try{上锁语句}finally{释放锁} 语句格式,这样可以避免在上锁语句中出现错误导致释放锁操作失败的情况出现
wait()方法(synchronized关键字下使用)
| 方法名 | 作用 |
|---|---|
| void wait() | 导致当前线程等待,直到另一个线程调用该对象的notify()方法或notifyAll()方法 |
| void notify() | 唤醒正在等待对象监视器的单个线程 |
| void notifyAll | 唤醒正在等待对象监视器的所有线程 |
IllegalMonitorStateException:抛出以表示线程已尝试再对象的监视器上等待或通知其他线程等待对象的监视器,而不拥有指定的监视器监视器:锁对象同步(snychronized)
注意:wait()方法必须要在同步方法中运行 也就是方法的关键字要添加snychronized关键字(如果程序没报错但卡住了就是有地方wait()了但是没有notify()唤醒)
生产者消费者案例
思路:
- 创建奶箱对象,这是共享数据区域
- 创建生产者对象,把奶香对象作为构造方法参数传递,因为在这个类中要调用存储牛奶的操作
- 创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
- 创建2个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
- 启动线程
代码示例:
public class Box{
//主要代码
private int milk;
private boolean state = false;
public synchronized void put(int milk){
if(state){
//state=true证明有牛奶 已生产出牛奶后设置wait()等消费者类调用get()方法取牛奶
try{
wait(); //设置wait()等待消费者线程运行
}catch(InterruptedException e){
e.printStackTrace();
}
}
this.milk=milk;
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("送奶工将第"+this.milk+"瓶奶放入奶箱");
state=true;
notifyAll(); //唤醒所有等待的线程
}
public synchronized void get(){
if(!state){
// !state 表示当Box内没有牛奶时设置wait()等待生产者类调用put()方法生产牛奶
try{
wait();
}catch(InterruptedException e){
e.printStackTrace();
}
}
try{
Thread.sleep(100);
}catch(InterruptedException e){
e.printStackTrace();
}
System.out.println("用户拿到第"+this.milk+"瓶奶");
state=false; //表示用户已经拿到牛奶设置state=false;
notifyAll(); //唤醒所有等待线程
}
}
public class Customer implements Runnable{
private Box b; //设置一个Box对象
public Customer(Box b){
this.b = b;
}
@Override
public void run(){
while(true){
b.get();
}
}
}
public class Producer implements Runnable{
private Box b; //设置一个Box对象
Producer(Box b){
this.b = b;
}
@Override
public void run(){
for(int i=1;i<=5;i++){
b.put(i);
}
}
}
public class Demo{
public static void main(String[] args){
//main
Box b = new Box();
Producer p = new Producer(b);
Customer c = new Customer(b);
Thread t1 = new Thread(p);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}

查看11道真题和解析