线程与进程的区别
线程:简单点来说就是线程是cpu调度的最小单位
进程:是cpu资源分配的最小单位
多线程
如果在一个进程中同时运行了多个线程,用来完成不同的工作,则称之为“多线程”
多个线程交替占用CPU资源,而非真正的并行执行
多线程的好处
- 充分利用CPU的资源
- 简化编程模型
- 带来良好的用户体验
多线程的四种实现方法
第一种 继承Thead
1:继承java.lang.Thread类
2:重写线程体 run函数(当线程启动时要完成的操作)
3:实例化Thread的派生类对象
4:调用start方法开启线程
这么说太无趣直接上代码吧
public class MyThread extends Thread{//继承Thread @Override public void run() {//实现run()方法 System.out.println("我是新的线程:"+Thread.currentThread().getName());//获取当前线程的名称 try { System.in.read(); } catch (IOException e) { e.printStackTrace(); } }
//Thread.getName:获取线程的名称 main Thread-x 自定义线程名 //currentThread:获取当前正在执行的线程 public class Test { public static void main(String[] args) { System.out.println("主线程名:"+Thread.currentThread().getName()); //启动MyThread线程 MyThread t=new MyThread("风哥线程01"); t.start();// MyThread t1=new MyThread("风哥线程02"); t1.start();// MyThread t2=new MyThread("风哥线程03"); t2.start();// System.out.println("程序执行结束"); } }
第二种 实现Runnable接口
1:创建Runnable接口的实现类
2:重写run函数
3:创建Runnable的实现类对象
4:创建Thread对象,并将Runnable的实现类对象作为参数传递
public class Theadde2 implements Runnable{ @Override public void run() { for (int i = 0; i < 100; i++) { System.out.println("当前运行的是"+Thread.currentThread().getName()+"-"+i); } } }
public class De2Text { public static void main(String[] args) { Thread th=new Thread(new Theadde2(),"哈哈");//新建Thread对象 把自己建的类放进去 th.start();//启动线程 } }
第三种 实现Callable接口(相较于run方法来讲是一个升级版)
1:创建Callable接口的实现类
2:重写call方法,并声明Callable的泛型,延伸到call函数的返回值
3:创建FutureTask<T>对象包装Callable,get()获取线程运行后的返回值
4:创建Thread类,将FutureTask作为构造参数,start开启线程
public class CallableTest implements Callable<String>{ @Override public String call() throws Exception {//可以抛出异常 System.out.println("call函数的线程名:"+Thread.currentThread().getName()); return "你好!";//可以返回数据 } }
public class Test { public static void main(String[] args) throws Exception { FutureTask<String> ft=new FutureTask<String>(new CallableTest()); Thread t1=new Thread(ft); t1.start();//启动线程 System.out.println("返回值:"+ft.get());//用get方法可以获得返回值 } }
第四种 自定义线程池ThreadPoolExecutor
JDK内置的线程池有四种
Executors(父类)
优点:避免反复开关线程池带来的损耗
1:固定长度的线程池newFixedThreadPool
2:newSingleThreadExecutor构建单例的线程池
3:构建自带缓存的线程池:newCachedThreadPool
4:构建调度线程池newScheduledThreadPool
这些内置的往往不能满足我们的业务要求所以这里我只写一个自定义的固定长度线程池
//创建了一个具有10条线程的线程池 固定 ExecutorService service = Executors.newFixedThreadPool(10);//括号里的是自己定的长度 for (int i = 0; i < 10; i++) { service.execute(new Runnable() {//传入对象 @Override public void run() { System.out.println("线程名:"+Thread.currentThread().getName()); } }); } service.shutdown();//关闭线程池
//这个是自定义线程池,推荐使用
//尽量保证:任务数不要超过最大线程数+阻塞队列的长度 ThreadPoolExecutor pool= new ThreadPoolExecutor(5, 10, 6, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>(10));//五个填写的参数为(核心线程数,最大线程数,存活时间,TimeUtil(枚举),BlockQueue()) for (int i = 0; i < 20; i++) { pool.execute(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()); try { Thread.sleep(2000);//线程睡眠 } catch (InterruptedException e) { e.printStackTrace(); } } }); } pool.shutdown();
线程调度常用方法
start:启动线程
sleep:sleep方法在谁的线程体中调用,就阻塞谁
setPriority:更改线程的优先级
join方法:强制获取CPU的执行时间
setDaemon():GC 将线程设置为后台线程
isDaemon():判断是否是后台线程
注意:后台线程的特点:随着前台线程的运行而运行,随着前台线程的消亡而消亡
interrupt():中断线程
isAlive():是否处于活动状态
yield():暂停当前正在执行的线程对象,并执行其他线程
//部分方法展示
t1.start();//启动线程 t1.join();//强制获取 t1.sleep(500);//线程休眠
t1.setDaemon(true);//变成后台线程
t1.setPriority(Thread.MAX_PRIORITY);//设置最大优先级(默认级别5)
t1.setPriority(Thread.MIN_PRIORITY);//设置最小优先级
线程的五种状态
1. 新建状态(New) : 线程对象被创建后,就进入了新建状态。例如,Thread thread = new Thread()。
2. 就绪状态(Runnable): 也被称为“可执行状态”。线程对象被创建后,其它线程调用了该对象的start()方法,从而来启动该线程。例如,thread.start()。处于就绪状态的线程,随时可能被CPU调度执行。
3. 运行状态(Running) : 线程获取CPU权限进行执行。需要注意的是,线程只能从就绪状态进入到运行状态。
4. 阻塞状态(Blocked) : 阻塞状态是线程因为某种原因放弃CPU使用权,暂时停止运行。直到线程进入就绪状态,才有机会转到运行状态。阻塞的情况分三种:
(1) 等待阻塞 -- 通过调用线程的wait()方法,让线程等待某工作的完成。
(2) 同步阻塞 -- 线程在获取synchronized同步锁失败(因为锁被其它线程所占用),它会进入同步阻塞状态。
(3) 其他阻塞 -- 通过调用线程的sleep()或join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新转入就绪状态。
5. 死亡状态(Dead) : 线程执行完了或者因异常退出了run()方法,该线程结束生命周期。
用线程锁(synchronized)做的小例子
synchronized:修饰方法(整个所修饰的方法体都是线程同步的范围)(this:)====>synchronized(this)
synchronized:修饰代码块(代码块中的内容是线程同步的范围)
子父两条线程交替执行,确保子父线程顺序不会出错
分析:
//子线程在调用son方法的时候一上来就能获取同步监视器this //父线程在调用father函数的时候一上来就是获取不到 //字父线程执行的四种情况: 子 父 父 子 子子 父父 //wait:Object (导致线程阻塞,并释放对同步监视器的占用 ) //notify:Object (唤醒线程的作用,提示后续等待该锁的线程,可以进行访问) public class FatherAndSon { boolean flag=true; public void son(){ synchronized (this) { //子一定是先执行的 if(!flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < 3; i++) { System.out.println("儿子线程执行第"+(i+1)+"次"); } flag=false;//1:把机会让给父线程 2、防止子线程再次执行 this.notify(); } } public void father(){ synchronized (this) { //父是后执行的 if(flag){ try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } for (int i = 0; i < 5; i++) { System.out.println("老子线程执行第"+(i+1)+"次"); } flag=true;//1:把机会让给子线程 2、防止父线程再次执行 this.notify(); } } }
public class Test { public static void main(String[] args) throws InterruptedException { final FatherAndSon fs=new FatherAndSon(); Thread t1=new Thread(new Runnable(){ @Override public void run() { //子线程的线程体中 for (int i = 0; i < 10; i++) { fs.son(); } } }); t1.start(); //父线程的线程体中 for (int i = 0; i < 10; i++) { fs.father(); } } }
在程序中创建三条线程,A线程打印我 B线程打印爱 C线程打印你 我爱你X10
public class RunnableTest implements Runnable{ private String word;//当前线程要输出的字符 private Object prev;//上一个线程的锁对象 private Object current;//当前线程的锁对象 public RunnableTest(String word, Object prev, Object current) { super(); this.word = word; this.prev = prev; this.current = current; } @Override public void run() { for (int i = 0; i < 10; i++) { synchronized (prev) { synchronized (current) { System.out.print(word); current.notify();//内层提醒 } try { prev.wait();//外层阻塞 } catch (InterruptedException e) { e.printStackTrace(); } } } } }
public class Test { public static void main(String[] args) throws InterruptedException { Object a=new Object();//新建object对象 Object b=new Object(); Object c=new Object(); Thread A=new Thread(new RunnableTest("我", c, a));//传入的是要打印的字,上一个线程锁,还有当前的线程锁 Thread B=new Thread(new RunnableTest("爱", a, b)); Thread C=new Thread(new RunnableTest("你", b, c)); A.start(); Thread.sleep(1000);//休眠确保顺序是我们想要的(不可缺少) B.start(); Thread.sleep(1000); C.start(); } }
虚拟实现网络售票功能
public class Ticket implements Runnable{ private int piao=100;//100张票 private int qiang=0;//抢到的第几张票 @Override public void run() { while (true) { synchronized (this) { if(piao<=0){ break; } piao--;//票做减操作 qiang++; System.out.println(Thread.currentThread().getName()+"抢到了第"+qiang+"张,还剩余"+piao+"张票"); if(Thread.currentThread().getName().equals("小明")){//只允许小明抢到一张 try { this.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
public class Test { public static void main(String[] args) { Ticket t=new Ticket();
//创建四个线程同时操作一个对象 Thread th=new Thread(t,"小明"); Thread th1=new Thread(t,"小红"); Thread th2=new Thread(t,"小紫"); Thread th3=new Thread(t,"小兰"); th.start(); th1.start(); th2.start(); th3.start(); } }