java运行时最起码有两条线程,主线程(main方法)和垃圾回收线程。
并行:指物理上同时执行。
并发:指能够让多个任务在逻辑上交织执行的程序设计。
进程:每个进程都有独立的代码和数据空间(进程上下文),进程间的切换会有较大的开销,一个进程包含1--n个线程。(进程是资源分配的最小单位)
线程:同一类线程共享代码和数据空间,每个线程有独立的运行栈和程序计数器(PC),线程切换开销小。(线程是cpu调度的最小单位)
随机性原理:因为cpu的快速切换造成的,线程会争夺cpu的执行权。
线程的运行代码统一存放在了run方法中,用start方法开启线程。
直接调用run()方法的话,系统只会把线程对象当成一个普通对象,把run()方法也当成一个普通方法,而不是线程执行体。
创建线程的方式有四种:
1.继承Thread类:
public class Test01 { public static void main(String[] args) { MyThread mt = new MyThread(); // 4,创建Thread类的子类对象 mt.start(); // 5,开启线程 for (int i = 0; i < 1000; i++) { System.out.println("bb"); } } } class MyThread extends Thread { // 1,继承Thread public void run() { // 2,重写run方法 for (int i = 0; i < 1000; i++) { // 3,将要执行的代码写在run方法中 System.out.println("第一种方式"); } } }
好处:可以直接使用Thread类中的方法。
弊端:有父类就不能用这种方法(即单继承的弊端)。
2.实现Runnable接口:
public class Test02 { public static void main(String[] args) { MyRunnable mr = new MyRunnable(); // 4,创建Runnable的子类对象 Thread t = new Thread(mr); // 5,将其当作参数传递给Thread的构造函数 t.start(); // 6,开启线程 for (int i = 0; i < 1000; i++) { System.out.println("bb"); } } }
好处和弊端刚好和继承Thread类相反,两者是互补的。
匿名内部类方式:
public class Test03 { public static void main(String[] args) { new Thread() { public void run() { for (int i = 0; i < 1000; i++) { System.out.println("AA"); } }; }.start(); new Thread(new Runnable() { @Override public void run() { for (int i = 0; i < 1000; i++) { System.out.println("BB"); } } }).start(); } }
3.实现Callable:
例子:
结果:
横线一直没有输出,在等待线程执行完毕。这说明FutureTask可用于闭锁。
补充:
使用场景:
结果:
4.实现线程池:
提供了一个线程队列,队列中保存着所有等待状态的线程。避免了创建与销毁额外开销,提高了响应的速度。
例如:常量池中会存储-128~127之间的Integer对象原理一样。
线程池体系结构:
java.util.concurrent.Executor : 负责线程的使用与调度的根接口 |--ExecutorService 子接口: 线程池的主要接口 |--ThreadPoolExecutor 线程池的实现类 |--ScheduledExecutorService 子接口:负责线程的调度 |--ScheduledThreadPoolExecutor :继承 ThreadPoolExecutor, 实现 ScheduledExecutorService
分类:
ExecutorService newFixedThreadPool() : 创建固定大小的线程池
ExecutorService newCachedThreadPool() : 缓存线程池,线程池的数量不固定,可以根据需求自动的更改数量。
ExecutorService newSingleThreadExecutor() : 创建单个线程池。线程池中只有一个线程
ScheduledExecutorService newScheduledThreadPool() : 创建固定大小的线程,可以延迟或定时的执行任务。
参考:java线程池的核心线程数与最大的线程数的区别,饱和策略
例子:
public class TestThreadPool { public static void main(String[] args) throws Exception { //1. 创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); List<Future<Integer>> list = new ArrayList<>(); for (int i = 0; i < 10; i++) { Future<Integer> future = pool.submit(new Callable<Integer>(){ @Override public Integer call() throws Exception { int sum = 0; for (int i = 0; i <= 100; i++) { sum += i; } return sum; } }); list.add(future); } pool.shutdown(); for (Future<Integer> future : list) { System.out.println(future.get()); } /* ThreadPoolDemo tpd = new ThreadPoolDemo(); //2. 为线程池中的线程分配任务 for (int i = 0; i < 10; i++) { pool.submit(tpd); } //3. 关闭线程池 pool.shutdown();*/ } // new Thread(tpd).start(); // new Thread(tpd).start(); } class ThreadPoolDemo implements Runnable{ private int i = 0; @Override public void run() { while(i <= 100){ System.out.println(Thread.currentThread().getName() + " : " + i++); } } }
线程调度:
public class TestScheduledThreadPool { public static void main(String[] args) throws Exception { ScheduledExecutorService pool = Executors.newScheduledThreadPool(5); for (int i = 0; i < 5; i++) { Future<Integer> result = pool.schedule(new Callable<Integer>(){ @Override public Integer call() throws Exception { int num = new Random().nextInt(100);//生成随机数 System.out.println(Thread.currentThread().getName() + " : " + num); return num; } }, 1, TimeUnit.SECONDS); System.out.println(result.get()); } pool.shutdown(); } }
控制线程:
设置线程的名字:setName()通过构造方法 获取名字:getName() 获取当前线程的对象:Thread.currentThread(); 让线程休眠指定的时间:Thread.sleep(毫秒值) 守护线程setDaemon(true):把一个线程设置为守护线程(当其他线程都结束了,它如果没有执行完毕也会结束.) 让线程插队join():让指定的线程优先执行,然后其他的线程再执行 礼让线程yield():让出CPU执行权. 设置线程的优先级:线程优先级从 1-10 默认的线程优先级:5 setPriority()
线程状态转换:
线程的停止:让线程运行的代码结束,也就是结束run方法。
怎么结束run方法?一般run方法里肯定定义循环。所以只要结束循环即可。
定义循环的结束标记。
如果线程处于了冻结状态,是不可能读到标记的。
这时就需要通过Thread类中的interrupt方法,将其冻结状态强制清除。让线程恢复具备执行资格的状态,让线程可以读到标记,并结束。
问:用户线程和守护线程有什么区别?
答:所谓守护线程,是指在程序运行的时候在后台提供一种通用服务的线程,比如垃圾回收线程就是一个很称职的守护者,并且这种线程并不属于程序中不可或缺的部分。
因此,当所有的非守护线程结束时,程序也就终止了,同时会杀死进程中的所有守护线程。
Java创建线程之后,直接调用start()方法和run()的区别:
run()方法,在本线程内调用该Runnable对象的run()方法,可以重复多次调用。
start()方法,启动一个线程,调用该Runnable对象的run()方法,不能多次启动一个线程。
当你调用start()方法时你将创建新的线程,并且执行在run()方法里的代码。
但是如果你直接调用run()方法,它不会创建新的线程也不会执行调用线程的代码。
例子1:
/** *创建一个Bank银行类,属性有money(初始值为100),生成set/get方法,创建一个Bank银行对象, *创建三个线程(分别给线程命名为"用户A","用户B","用户C"), *当用户A线程执行时,通过set方法将money的值增加100,在控制台输出 "用户A线程正在执行第X次,增加了100元,目前money的值为X元" *当用户B线程执行时,通过set方法将money值随机增加1-100(不含100),在控制台输出"用户B线程正在执行第X次,增加了X元,目前money的值为X元" *当用户C线程执行时,线程休眠10毫秒,不作任何操作.在控制台输出"用户C线程正在执行第X次,睡眠了10毫秒" *共执行20次,最后打印输出money的值,如 "增加后的money值为:X元" *注意:(机子速度太快有可能会出现一条线程全执行完,关键要实现需求) */ public class Test01 { public static void main(String[] args) { BankThread b1 = new BankThread("用户A"); BankThread b2 = new BankThread("用户B"); BankThread b3 = new BankThread("用户C"); b2.start(); b3.start(); b1.start(); } } class BankThread extends Thread { BankThread(String str) { super(str); } static Bank b = new Bank(); static int count = 20; public void run() { while (true) { synchronized (BankThread.class) { if (count > 20) { break; } if ("用户A".equals(this.getName())) { b.setMoney(b.getMoney() + 100); System.out.println("用户A线程正在执行第" + count + "次,增加了100元,目前money的值为" + b.getMoney() + "元"); } else if ("用户B".equals(this.getName())) { Random r = new Random(); int num = r.nextInt(100); b.setMoney(b.getMoney() + num); System.out.println("用户B线程正在执行第" + count + "次,增加了" + num + "元,目前money的值为" + b.getMoney() + "元"); } else if ("用户C".equals(this.getName())) { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("用户C线程正在执行第X次,睡眠了10毫秒"); } count++; } } }; } class Bank { private int money = 100; public int getMoney() { return money; } public void setMoney(int money) { this.money = money; } }
例子2:
/** * 模拟3个老师同时发80份笔记, 每个老师相当于一个线程, 分别给三个线程命名为”张老师线程,林老师线程,李老师线程”, * 要求在控制台输出"xxx老师"在发第"xxx"份笔记 2) 如果要求最后一张试卷必须由”李老师线程”发出,请问应该怎么做? */ public class Test02 { public static void main(String[] args) { ShiJuan shi = new ShiJuan(); Thread t1 = new Thread(shi); Thread t2 = new Thread(shi); Thread t3 = new Thread(shi); t1.setName("张老师"); t2.setName("林老师"); t3.setName("李老师"); t1.start(); t2.start(); t3.start(); } } class ShiJuan implements Runnable { int shijuan = 80; @Override public void run() { while (true) { synchronized (ShiJuan.class) { if (shijuan <= 0) { break; } try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } if (shijuan == 1 && !Thread.currentThread().getName().equals("李老师")) { continue; } System.out.println(Thread.currentThread().getName() + "正在发" + shijuan); System.out.println(shijuan--); } } } }
例子3:
/* 模拟抢红包过程,生成50个红包(金额是随机的,范围在1-10元之间) 创建5个线程代表5个人,然后让这5个人去抢这50个红包,每次抢红包需要300ms的时间, 在控制台打印出(xxx抢了xxx元)(不限定每人抢的次数并且抢到红包后还可以接着抢,每次生成一个红包). */ public class Test03 { public static void main(String[] args) { HongBao_1 nb = new HongBao_1(); Thread t1 = new Thread(nb); Thread t2 = new Thread(nb); Thread t3 = new Thread(nb); Thread t4 = new Thread(nb); Thread t5 = new Thread(nb); t1.setName("线程1"); t2.setName("线程2"); t3.setName("线程3"); t4.setName("线程4"); t5.setName("线程5"); t1.start(); t2.start(); t3.start(); t4.start(); t5.start(); }} class HongBao_1 implements Runnable{ //这里的变量不需要静态 int hongbao=50; Random r = new Random(); @Override public void run() { while(true){ synchronized(HongBao_1.class){ if(hongbao<=0){ break; } try { Thread.sleep(30); } catch (InterruptedException e) { e.printStackTrace(); } int num = r.nextInt(10)+1; System.out.println(Thread.currentThread().getName()+"抢到了"+num+"元"); System.out.println(hongbao--); }} } }