进程:计算机执行的任务
线程:执行任务中的小任务 多线程
计算机再执行过程中,再同一时间只能让cpu的一个核执行一个进程。进程有多个线程构成,再同一时刻Cpu只能处理一个线程。
引入多线程
当线程被cpu执行时cpu开始工作,线程需要和软硬件进行交互,这个时候cpu就是处于空闲状态
引用多线程可以提高cpu的使用效率
创建多线程的方式-----(Thead)
1.继承Thread类,重写run方法(线程代码逻辑所在的地方,调用start方法,开启线程. 有一个不好的地方就是java是单继承所以我们继承了Thread后就不能继承其他类
,所以我们通常采用第二种方法 实现接口
public class ThreadDemo { public static void main(String[] args) { //线程执行---执行线程逻辑所在的类 Demo d=new Demo(); //标记线程可以被cpu执行 d.start(); for(int i=0;i<10;i++) { System.out.println("main:"+i); } } } //线程任务的执行的代码逻辑 class Demo extends Thread{ //重写方法----实现线程的代码逻辑 @Override public void run() { for(int i=0;i<10;i++) { System.out.println("i"+i); } } }
2.实现Runnable接口,重写run方法(线程代码逻辑),通过Runnable接口的实现类对象构建Thread类对象,调用start方法开启线程
public class ThreadDemo2 { public static void main(String[] args) { //通过Runnable实现类对象构建Thread类对象 Thread t=new Thread(new TDemo() ); //开启线程 t.start(); for(int i=0;i<10;i++) { System.out.println("main:"+i); } } } //线程代码逻辑所在类,实现Runnable接口 class TDemo implements Runnable{ //重写方法 --线程代码逻辑 @Override public void run() { for(int i=0;i<10;i++) { System.out.println("i"+i); } } }
3.实现Callable接口,重写call方法(现阶段了解就好)
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.Executor; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.Future; public class ThreadDemo3 { public static void main(String[] args) throws InterruptedException, ExecutionException { //创建线程类对象 DTDemo1 dt=new DTDemo1(); //获取执行服务器, ExecutorService e=Executors.newCachedThreadPool(); //把你想要操作的东西放到执行服务器上 Future<Integer> f =e.submit(dt); System.out.println(f.get()); } } //Integer 重写方法返回值类型 class DTDemo1 implements Callable<Integer>{ //重写方法----线程代码逻辑所在的地方 @Override public Integer call() throws Exception { // TODO Auto-generated method stub return 20; } }
因为底层多线程之间存在抢占问题,抢占发生再代码的每一步,导致了数据安全问题
为了排除我们多线程的抢占问题我们采用加锁的策略,有两种 1.同步代码块锁,同步方法锁
同步代码块锁--synchtonized(锁对象){} ----锁对象指的是 可以被线程共享--方法区里的内容可以被所有线程共享(对多少个线程对象进行加锁,这些对象都是同步的)
同步方法锁--在方法上加上synchronized,如果这个方法是静态方法锁对象就是类名.class,如果这个方法是非静态方法锁对象就是this,构造器和属性上不能夹synchronized
同步:多个线程每次只能执行一个(一个一个)
异步:多个线程每次可以执行多个(抢占)
同步一定是安全的
安全的不一定是同步
不安全一定是异步
异步不一定不安全
从微观上同步一定是安全的,异步一定是不安全的
public class SellTicketDemo { public static void main(String[] args) { //创建票类对象 Ticket t=new Ticket(); //设置票数 t.setCount(100); //四个售票员 Seller s1=new Seller(t); Seller s2=new Seller(t); Seller s3=new Seller(t); Seller s4=new Seller(t); //开启线程 并给每个线程 new Thread(s1,"A").start(); new Thread(s2,"B").start(); new Thread(s3,"C").start(); new Thread(s4,"D").start(); } } //模拟卖票的过程---线程的代码逻辑 class Seller implements Runnable{ //引入票类 Ticket t; //有参构造 public Seller(Ticket t) { this.t=t; } //线程的代码逻辑---买票的过程 //同步方法锁是直接加在方法上 同步方法锁如果是非静态方法那么他的锁对象是this //如果是静态方法的话,那么锁对象就是类名.class @Override public synchronized void run() { while(true) { //同步代码块锁---()中的是锁对象 ----被线程共享,只要是能被所有对象共享的就可以,锁对象必须被所有被执行的线程共享 方法区中的就可以因为方法区(是被所有的线程共享的)但是范围太大了,能小的锁就小得锁 synchronized (Seller.class) { if(t.getCount()<=0) //票买完的时候就是票数为0 { break; } //设置新的票数 t.setCount(t.getCount()-1); //打印出具体是那个售票员卖的----具体是那个线程执行的 //Thread.currentThread()当前正在执行的线程 System.out.println(Thread.currentThread().getName()+"买了一张票,还剩"+t.getCount()+"票"); } } } } //表示票类 class Ticket{ //属性 //票数 private int count; public int getCount() { return count; } public void setCount(int count) { this.count = count; } }
锁之间的相互嵌套----死锁
public class DeadLoackDemo { // static Print p=new Print(); static Scan s=new Scan(); public static void main(String[] args) { //开启线程 new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub //打印信息 synchronized (p) { p.print(); //让线程进行休眠---线程释放执行权 try { Thread.sleep(20); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //扫描 synchronized (s) { s.sacnn(); } } } }).start(); new Thread(new Runnable() { @Override public void run() { // TODO Auto-generated method stub //先扫描 synchronized (s) { s.sacnn(); synchronized (p) { p.print(); } } } }).start(); } } //打印机 class Print{ public void print(){ System.out.println("在打印东西..."); } } //扫描仪 class Scan{ public void sacnn(){ System.out.println("在扫描信息..."); } }
如果破解死锁:上面的死锁出现的问题是两个线程需要同一个锁,如果一个走一个等待那么就不会产生死锁现象,所以我们需要控制锁一个锁走完再让另一个线程获取这个锁
package cn.tedu.thread; public class WaitNotifyDemo { public static void main(String[] args) { //创建学生类对象 Student s=new Student(); s.setName("lili"); s.setGender('女'); //开启线程 new Thread(new Ask(s)).start(); new Thread(new Change(s)).start(); } } //线程所在的类---问问题 class Ask implements Runnable{ // 引入学生类对象 private Student s; public Ask(Student s){ this.s=s; } @Override public void run() { // TODO Auto-generated method stub //表示问问题的结果 while(true){ synchronized (s) { //防止多线程抢占,保证性别 //释放线程执行权---等待 if(s.flag==false) try { //让线程等待----相当于堵塞主要是为了挨个回答问题 s.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //输出 System.out.println ("老师你好,我是"+s.getName() +",是一个"+s.getGender()+ "生,想问问题..."); //唤醒线程 s.notify(); //改变布尔值 s.flag=false; } } } } //线程所在的类---换学生 class Change implements Runnable{ //引入学生类对象 private Student s; public Change(Student s){ this.s=s; } @Override public void run() { // TODO Auto-generated method stub while(true){ synchronized (s) { //防止多线程的抢占---保证性别 //线程等待 if(s.flag==true) try { s.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // if (s.getName().equals("tom")) { s.setName("lili"); s.setGender('女'); } else { s.setName("tom"); s.setGender('男'); } //线程唤醒 s.notify(); //唤醒阻塞的线程,CPU可以将阻塞的线程抢执行权了。 //改变布尔值 s.flag=true; } } }} //学生类 class Student{ //属性 private String name; private char gender; // boolean flag=true; public char getGender() { return gender; } public void setGender(char gender) { this.gender = gender; } public String getName() { return name; } public void setName(String name) { this.name = name; } }
wait、notify、notifyAll是线程中通信可以使用的方法。线程中调用了wait方法,则进入阻塞状态,只有等另一个线程调用与wait同一个对象的notify方法。这里有个特殊的地方,调用wait或者notify,前提是需要获取锁,也就是说,需要在同步块中做以上操作。
等待唤醒的前提唤醒一个 是得有锁
notify()随机唤醒 肯定给你唤醒一个 作用 唤醒阻塞的线程,CPU可以将阻塞的线程抢执行权了。两个对象的话就是一个线程执行一次,因为只有一种情况,一个线程再执行,另一个再阻塞
wait和sleep的区别
sleep--用于是线程进入休眠状态(需要制定休眠的时间,到了这个时间才会唤醒),在其s0leep时间段内,该线程不会获得执行的机会,即使系统种没有其他可以运行的线程如果线程没有加锁,就会释放线程的执行权,如果加锁就不会释放执行权,但是会有CPU的切换 ,可以指定休眠时间 这是Thread的静态方法
wait---如果指定等待时间,就必须等到时间结束才能唤醒,如果不指定时间就只能手动唤醒,如果线程加锁就会释放锁也能释放执行权,如果没有加锁就释放执行权,是Object里的普通方法
线程状态:
当线程被创建并启动以后,它既不是以启动就进入了执行状态,也不是一致处于执行状态,在线程的生命周期中,他要径路新建,就绪,运行,阻塞和死亡5种状态。 尤其是线程启动以后,它不能一直“霸占”
着CPU独自运行,所以CPU需要在多条线程之间切换,于是线程状态也会多次在运行、阻塞之间切换
1.新建:当程序使用new关键字创建了一个线程之后,该线程就处于新建状态,此时它和其他java对象一样,仅仅有java虚拟机为其分配了内存,并初始化了其成员变量的值。此时的线程对象没有表现出任何线程的动态特征,程序也不会执行线程的线程执行体。
2.就绪状态:当线程对象调用了start()方法之后,该线程处于就绪状态,java虚拟机会为其创建方法调用栈和程序计数器,处于这个状态下的线程并没有开始运行,它只是表示该线程可以运行了,至于该线程何时开始运行,取决于jvm里线程调度器的调度
不要对已经处于启动状态的线程再次调用start方法,否则将引发IllegalThreadStateException异常
如果程序希望调用子线程的start()方法后子线程立即开始执行,程序可以使用Thread.sleep(1)来让当前运行的线程(主线程)睡眠一毫秒---1毫秒就够了,因为在这1毫秒内CPU不会空闲,它就会去执行另一条就绪状态的线程,这样就可以让我们的子线程立即获得执行
守护线程
需要手动开启,如果被守护线程执行结束,守护线程也随着结束,反之不是 如果有多个线程,除了守护
线程,其他的都是被守护线程 ,java中最大的守护线程是GC
package cn.tedu.thread; public class DemonDemo { public static void main(String[] args) { //创建出小兵对象 Thread t1=new Thread(new Soilder(),"小兵1"); Thread t2=new Thread(new Soilder(),"小兵2"); Thread t3=new Thread(new Soilder(),"小兵3"); Thread t4=new Thread(new Soilder(),"小兵4"); //设置守护线程 t1.setDaemon(true); //true代表手动开启守护线程 t2.setDaemon(true); t3.setDaemon(true); t4.setDaemon(true); //开启线程 t1.start(); t2.start(); t3.start(); t4.start(); //被守护线程 for(int i=10;i>=0;i--){ System.out.println("boss剩余"+i); } } } //线程类---小兵 class Soilder implements Runnable{ @Override public void run() { // TODO Auto-generated method stub //输出每个小兵的剩余血量 for(int i=10;i>=0;i--){ System.out.println (Thread.currentThread(). getName()+"还剩"+i+"滴血..."); } //线程走的太快就让慢点,方便自己查看结果 try { Thread.sleep(10); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
前台线程死亡后,jvm会通知后台线程死亡,但从它接受命令,到它做出响应,需要一定时间,而且要将某个线程设置为后台线程,必须在该线程启动之前设置,也就是说setDaemon(true)必须在start()方法调用之前否则会引发IllegalThreadStateException
线程优先级:
优先级(1-10),理论上优先级越大越有机会抢到执行权,理论上如果线程1与线程2之间的优先级之差大于5,那么线程1强到执行权的机会比线程2大一点。就算你设置成10和1差距也不大,功能比较鸡肋
package cn.tedu.thread; public class PririotyDemo { public static void main(String[] args) { Thread t1=new Thread(new PDemo(),"A"); Thread t2=new Thread(new PDemo(),"B"); //设置优先级 t1.setPriority(1); t2.setPriority(9); //开启线程 t1.start(); t2.start(); } } class PDemo implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for(int i=0;i<10;i++){ System.out.println(Thread.currentThread().getName()); } } // }
生产者和消费者问题
//生产消费模型 public class WaitNotifyText { public static void main(String[] args) { //商品对象 Product p=new Product(); //开启线程 new Thread(new Productor(p)).start(); new Thread(new Productor(p)).start(); new Thread(new Consumer(p)).start(); new Thread(new Consumer(p)).start(); } } //模拟生产过程---线程逻辑代码 class Productor implements Runnable{ //引入商品类 Product p; public Productor(Product p){ this.p=p; } //重写 @Override public void run() { while (true) { synchronized (p) { while(p.flag==true)//加上while保证线程一定会进行判断 try { p.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // TODO Auto-generated method stub // 此时生产的最大值 int max = 1000 - p.getNum();// 减去上次剩余 // 随机生产的商品数量 int count = (int) (Math.random() * (max + 1)); // 设置新的商品数量 p.setNum(p.getNum() + count); // 输出 System.out.println("此次生产了" + count + "个商品,还剩余" + p.getNum() + "个商品..."); //唤醒 //p.notify();//随机唤醒一个 p.notifyAll(); p.flag=true; } } } } //模拟消费过程 class Consumer implements Runnable{ // 引入商品类 Product p; public Consumer(Product p){ this.p=p; } @Override public void run() { // TODO Auto-generated method stub while (true) { synchronized (p) { while(p.flag==false) try { p.wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } // 此次消费最大值 int max = p.getNum(); // 此次消费的随机商品数量 int count = (int) (Math.random() * (max + 1)); // 设置新的商品数量 p.setNum(p.getNum() - count); // 输出 System.out.println("此次消费了" + count + "个商品,还剩余" + p.getNum() + "个商品..."); //唤醒 //p.notify(); p.notifyAll(); p.flag=false; } } } } //商品类 class Product{ //商品数量 private int num; // boolean flag=true; public int getNum() { return num; } public void setNum(int num) { this.num = num; } }