*在某个时刻,某个核中只能执行一个进程,但是进程可以拆分成多个线程,只能执行其中一个线程,CPU只能执行一个线程,CPU会切换执行线程
*
多线程的好处
线程要么和CPU进行交互要么和硬件进行交互,当线程在和硬件进行交互时CPU处于空闲状态,引入多线程提高CPU的利用率(理论上可以提高到100%)
创建多线程的方式
1.继承Thread类,创建描述线程执行信息的类的对象,调用父类的start方法来开启线程
2.实现Runnable接口,由接口实现类对象来构建Thread类对象调用start方法来开启线程(常用)
==由于多线程之间存在相互抢占(CPU的执行权),抢占发生在代码每一步,导致多线程数据并发安全问题(重复、负数、跳过等等错误数据)
多线程并发安全问题的解决方式(加锁)
1.同步代码块锁
synchronized(锁对象){} 根据指定的锁对象来共享线程对象对象,保证共享进来线程对象执行代码块内容时不会发生抢占
锁对象
当前参与的线程对象共享进来
方法区资源(所有的线程对象都能共享进来)
this(当参与进来的线程对象被同一个Runnable实现类对象共享时)
2.同步方法锁
保证共享进来线程对象执行方法内容时不会发生抢占
如果是在非静态方法上加上锁,默认锁对象就是this
如果是在静态方法上加上锁,默认锁对象就是当前类.class(方法区资源)
public class SellTicketDemo2 {
public static void main(String[] args) throws IOException {
//加载配置文件里的count的对应值
Properties p=new Properties();
p.load(new FileReader("ticket.properties"));
//获取键对应的值
String count=p.getProperty("count");
//创建代表票类的对象
Ticket t=new Ticket();
//设置票数
t.setCount(Integer.parseInt(count));
//创建一个Seller类(Runnable实现类)对象---一个售票系统
//四个对象都是由相同的t对象构建而来,共享t对象的票数
Seller1 s=new Seller1(t);
//创建四个线程对象
//指定线程对象的名称
//四个线程对象共享同一个Runnable实现类对象,锁对象就可以指定为this
Thread t1=new Thread(s,"A").start();
Thread t2=new Thread(s,"B").start();
Thread t3=new Thread(s,"C").start();
Thread t4=new Thread(s,"D").start();
}
}
//代表线程的任务信息(卖票过程)
class Seller1 implements Runnable{
//表示票数
//static int count=100;
//引入代表票的类的对象
Ticket t;
//有参构造---保证创建的对象共享同一个Ticket类的对象
public Seller1(Ticket t){
this.t=t;
}
//卖票具体过程--重写run方法
//如果加在非静态方法上默认锁对象就是this
//如果加在静态方法上默认锁对象就是当前类.class(方法区资源)
@Override
public synchronized void run() {
//循环实现重复卖票过程
while (true) {
//同步代码块锁
//()--需要填入锁对象
//锁对象---提供一个对象来共享线程对象,被共享进来的线程对象保证
//执行代码块区域内容时某个时刻只能一个线程对象来执行(不会发生抢占)
//synchronized (t) {//把当前参与的线程对象共享进来
//指定都是方法区的资源(方法区的资源是被所有的线程对象共享)
//synchronized (Math.class) {//Seller.class String.class
//synchronized (this) {//可以当作锁对象
//循环结束条件
if(t.getCount()==0)
break;
//卖一张票---设置新的票数
t.setCount(t.getCount() - 1);
//输出卖票信息
//Thread.currentThread()---当前正在执行线程对象
//getName()---线程对象的名称
System.out.println(Thread.currentThread().getName() +
"卖了一张票,还剩" + t.getCount() + "张票。。。");
//线程休眠
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
//}
}
}
}
解决死锁问题
死锁—由于锁的嵌套导致死锁问题,通过死锁检测让其中一个线程对象先执行解决死锁问题
下面为一个生产消费模型案例
每次生产的商品数量是随机值,但是保证总的剩余商品数量不能大于1000,每次消费也是随机值,生产和消费要交替出现。
**等待唤醒机制(结合锁使用)
通过wait、notif以及notifyAll和标志位来控制线程对象执行顺序
**
public class NotifyAllWaitDemo {
public static void main(String[] args) {
//创建代表商品类对象
Pruduct p=new Pruduct();
new Thread(new Pruductor1(p)).start();
new Thread(new Pruductor1(p)).start();
new Thread(new Pruductor1(p)).start();
new Thread(new Consumer1(p)).start();
new Thread(new Consumer1(p)).start();
new Thread(new Consumer1(p)).start();
}
}
//代表生产者---描述生产过程
class Pruductor1 implements Runnable{
//声明商品类的对象
Pruduct p;
//通过有参构造传入商品类对象
public Pruductor1(Pruduct p){
this.p=p;
}
//重写方法---详细生产的过程
@Override
public void run() {
//保证一直生产
while (true){
//加锁
synchronized (p){
//线程等待
//保证每次都能进行判断
while (p.getFalg()==false)
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//此次最大生产商品数量
int max=1000-p.getCount();
//随机生产商品数量
int count=(int) (Math.random()*(max+1));
//设置新的剩余商品数量
p.setCount(p.getCount()+count);
//输出
System.out.println("生产了"+count+"个商品,还剩余"
+p.getCount()+"个商品。。。");
//唤醒线程
//p.notify();
//唤醒所有线程对象
p.notifyAll();
//改变布尔值
p.setFalg(false);
}
} }
}
//=============================================================================================
//代表消费者---描述消费过程
class Consumer1 implements Runnable{
//声明商品类的对象
Pruduct p;
//通过有参构造传入商品类对象
public Consumer1(Pruduct p){
this.p=p;
}
//重写方法---详细消费的过程
@Override
public void run() {
//保证一直消费
while (true){
//加锁
synchronized (p){
//线程等待
while (p.getFalg()==true)
try {
p.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
//随机消费商品数量
int count=(int) (Math.random()*(p.getCount()+1));
//设置新的剩余商品数量
p.setCount(p.getCount()-count);
//输出
System.out.println("消费了"+count+"个商品,还剩余"
+p.getCount()+"个商品。。。");
//唤醒等待线程对象
// p.notify();
//唤醒所有线程对象
p.notifyAll();
//改变布尔值
p.setFalg(true);
}
}
}}
sleep方法与wait方法的区别
1.sleep方法一定要指定休眠时间(毫秒),到点自然醒。休眠期间,如果没有锁是释放CPU执行权,但是当有锁时依然会释放CPU执行权不会释放锁对象。定义在Thread类里静态方法。
2.wait方法可以指定等待时间也可以不指定,如果指定等待时间到点自然醒,如果没有指定等待时间需要手动唤醒。在等待期间会释放CPU执行权和锁对象。wait方法定义在Object类里。
线程状态
计算机生成了可选文字:阻塞创建一一一拯全