进程:计算机执行的任务
线程:执行任务中的小任务 多线程
计算机再执行过程中,再同一时间只能让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{//重写方法----实现线程的代码逻辑 @Overridepublic 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{//重写方法 --线程代码逻辑 @Overridepublic 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>{//重写方法----线程代码逻辑所在的地方 @Overridepublic Integer call() throws Exception {// TODO Auto-generated method stubreturn 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 @Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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;}@Overridepublic 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;}@Overridepublic void run() {// TODO Auto-generated method stubwhile(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{@Overridepublic 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{@Overridepublic void run() {// TODO Auto-generated method stubfor(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;}//重写 @Overridepublic 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;}@Overridepublic void run() {// TODO Auto-generated method stubwhile (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;}}