一.线程的相关概念
二.线程的创建和启动★
三.线程的停止
四.线程的常用方法
五.线程的生命周期★
六.线程的同步
七.线程的通信
八.线程的创建方式三
九.线程的创建方式四(线程池)
一.线程的相关概念
程序:是完成特定任务、用某种语言编写的一组指令的集合
进程:是程序的一次执行过程,或是正在运行的一个程序
线程:进程可进一步细化为线程,线程是进程中的一个细小的单元
单线程:一个程序,在“同一时刻”只能执行一个线程
多线程:一个程序,在“同一时刻”可以同时执行多个线程
双核
多核
高并发
高频率
二、线程的创建和启动
1、之前的所有代码都是单线程
2、线程的创建方式(两种)一共是四种
2.1 a 新建一个类
b 继承一个类Thread
c 重写run方法(实际运行的代码)
启动线程的方式:
a。 创建一个线程对象
Thread1 t1=new Thread1();
b。调用当前线程的start方法
t1.start();//启动线程的正确方式(实际运行的依然是run方法)
start() 方法有两个作用:①启动当前线程 ② 调用当前线程
问题1:我们不能通过直接调用run方法的方式启动线程
ti.run() //线程只能运行main线程,t1 线程不能执行
问题2:在启动一个线程,遍历100以内的偶数,不可以还让已经start的线程去执行,会报错IllegalThreadStateException
public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); }
当start 方法启动时,会判断当前线程状态是不是0 也就是New 状态
如果想调用新的线程,直接再新建一个T2线程
2.2
a 新建一个类
b实现一个接口Runable
c实现接口中的抽象方法 run方法
启动线程的方式
a 创建线程对象
b通过Thread类进行包装
c调用Start方法进行启动
t22.start();//实际运行的依然是t2的run 方法
3、 练习
1 创 建两个子线程,让其中一个输出1-100之间的偶数,另一个输出1-100之间的奇数。
package Demo; //创建两个子线程,让其中一个输出1-100的偶数,另一个输出1-100之间的奇数 public class Demo3 { public static void main(String[] args) { Thread1 t1=new Thread1(); t1.start(); Thread2 t2=new Thread2(); Thread t22=new Thread(t2); t22.start(); } } class Thread1 extends Thread{ @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 101; i++) { if (i%2==0) { System.out.println("偶数"+i); } } super.run(); } } class Thread2 implements Runnable{ @Override public void run() { // TODO Auto-generated method stub for (int i = 0; i < 101; i++) { if (i%2!=0) { System.out.println("奇数:"+i); } } } }
三、线程常见方法
1. 如何停止一个线程
a stop(); 停止一个线程 过时了 不建议使用
b 可以采用逻辑的方式
四.线程的常用方法
1、currentThread();获得当前线程的对象 静态方法
2、Thread[Thread-0,5,main] [当前线程的名字,当前线程的优先级,当前线程由哪个线程创建的]
3、getName();获得线程的名字
4、setName(String name);设置当前线程的名字
5、getPriority();获得当前线程的优先级 线程的优先级默认和创建他的线程优先级一致
/** * The minimum priority that a thread can have. */ public static final int MIN_PRIORITY = 1; /** * The default priority that is assigned to a thread. */ public static final int NORM_PRIORITY = 5; /** * The maximum priority that a thread can have. */ public static final int MAX_PRIORITY = 10;
6、setPriority(2);设置当前线程的优先级 范围:1-10 默认优先级是5
说明:高优先级的线程要抢占低优先级CPU执行权,但是只是从概率上讲高优先级高概率被执行,并不意味着一定先执行 。
7、sleep(long time);睡眠 设置时间 单位:毫秒 需要抛出一个异常 InterruptedException e
8、interrupt();中断(不能中断线程) (只是改变了当前线程中一个值的状态)
在中断睡眠或者等待的线程时会抛出一个异常
9、yield()释放当前CPU的执行权,有可能当前CPU又分配到当前线程
10、join():在线程a中,调用线程bde join方法,线程a进入阻塞状态,直到线程b完全执行完成,线程a 才继续执行
线程分类
* 守护线程 (负责守护) 当用户线程执行完毕,自己线程无论是否执行完毕都自动结束!
如何设置守护线程 t1.setDaemon(true);//设置为守护线程 需要在启动之前设置
记住守护线程的特点:
java一个最经典的守护线程是 :垃圾回收机制的线程
用户线程 (功能)
2. 练习
编写程序:在main方法中创建一个线程。线程每隔一定时间(200ms以内的随机时间)
2.定义一个接口用来实现两个对象的比较。
interface CompareObject{
public int compareTo(Object o);
//若返回值是 0 , 代表相等; 若为正数,代表当前对象大;负数代表当前对象小
}
定义一个Circle类。
定义一个ComparableCircle类,继承Circle类并且实现CompareObject接口。
在ComparableCircle类中给出接口中方法compareTo的实现体,用来比较两个圆的面积大小。
定义一个测试类InterfaceTest,创建两个ComparableCircle对象,
调用compareTo方法比较两个类的半径大小。 产生一个0-100之间的随机整数,打印后将该整数放到集合中;
共产生100个整数,全部产生后,睡眠5秒,然后将集合内容打印输出;
package Demo; import java.util.ArrayList; import java.util.Iterator; import java.util.List; /*在main方法中创建一个线程。线程每隔一定时间(200ms以内的随机时间) * 产生一个0-100之间的随机整数,打印后将该整数放到集合中; 共产生100个整数,全部产生后,睡眠5秒,然后将集合内容打印输出;*/ public class Demo4 { public static void main(String[] args) { Thread3 t1=new Thread3(); t1.start(); } } class Thread3 extends Thread{ List<Integer> list =new ArrayList<Integer>(); @Override public void run() { super.run(); // TODO Auto-generated method stub for (int i = 0; i <100; i++) { try { Thread.sleep((long) (Math.random()*200+1)); } catch (Exception e) { // TODO: handle exception } int a = (int)(Math.random()*101); System.out.println(a); list.add(a); } try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } for (Integer integer : list) { System.out.print(integer); }
五.线程的同步
1、线程的同步
多个窗口买票
1. 互斥锁
a. 为一段代码块上锁
语法:
synchronized(锁对象【同步监视器】){
//锁对象是任意对象(this或String对象) 多个线程必须公用同一把锁
需要锁住的代码;关键代码;
}
b. 为一段代码块上锁
语法:public synchronized void method(){
//如何让循环结束
}
关于同步方法的总结:
① 同步方法仍然设计到同步监视器 ,只是不需要我们显式的声明
② 非静态的同步方法,同步监视器是:this
静态的同步方法,同步监视器是:当前类
C.
1.创建一个对象
ReentrantLock lock=new ReentrantLock();
2.在需要被锁住的上一行执行lock方法
lock.lock();
3.在需要被锁住的最后一行下一行执行unlock方法(必须要执行:finally中)
Lock.unlock(); (必须被执行:finally里中)
4. 案例
try { lock.lock(); if(num<=0){ System.out.println("卖完了"); break; } try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"卖出了一张票:剩余:"+--num); } finally { lock.unlock();//一定要保证能运行 finally中 }
synchronized 与lock 的异同?
相同点:二者都可以解决线程安全问题
不同点:synchronized 机制在执行完相应的同步代码以后,自动的释放同步监视器
lock 需要手动的启动同步(lock()),同时结束同步也需要手动的实现(unlock())
2、死锁的问题
1、产生原因:
就是锁与锁之间的相互等待
一个线程一直占用这个资源,另一个线程一直在等待他结束
此类用于演示死锁 (避免)
1. 产生的原因:
就是锁与锁之间相互等待
2. 案例见下面代码
public class Demo2 { public static void main(String[] args) { Thread1 t1=new Thread1();//新生线程 t1.start();//就绪(可运行)线程 Thread2 t2=new Thread2(); t2.start(); } } class Thread1 extends Thread{ @Override public void run() { System.out.println("这是Thread1线程"); synchronized ("java") { try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("Thread1刚刚睡醒"); synchronized ("html5") { System.out.println("Thread1内部锁的代码"); } } } } class Thread2 extends Thread{ @Override public void run() { System.out.println("这是Thread2线程"); synchronized ("html5") { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Thread2刚刚睡醒"); synchronized ("java") { System.out.println("Thread2内部锁的代码"); } } } }
解决懒汉模式
package com.atguigu.demon; /** * 此类用于演示解决懒汉模式的线程安全问题 * 1. 代码见下面 */ public class Demo3 { } class Lazy{ private Lazy(){} private static Lazy lazy; public static Lazy getLazy(){ if(lazy==null){ synchronized ("aaa") { if(lazy==null){ lazy=new Lazy(); } } } return lazy; } }
六.线程的生命周期★
1、新生线程,(线程对象刚刚诞生)刚new出来
可运行(就绪)线程(调用Start方法)
运行线程 (等待cpu分配执行权)
阻塞线程
睡眠sleep
互斥锁
io阻塞
暂停join
等待wait
死亡线程
以上需要背下来
2、线程的让步和线程的插队
a.线程的让步
Thread.yield()
礼让只是比自己优先级高或者同级别的
如果不满足这个条件该方法失效
抢到之后只让一次,下次抢到直接执行
b.线程的插队
this.join();
调用方法时,调用线程将被阻塞,直到join()方法加入的join方法执行完成。
让给谁,谁执行完,我才执行
七.线程的通信
1、需求
启动两个线程:一个线程做普通打印操作(0.5秒打印一次) 另一个线程去监控键盘,
如果有输入则暂停另一个线程的打印,如果在次检测到键盘有输入,继续运行上一个线程
package com.atguigu.demon; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; /** * 此类用于演示线程的通信 */ public class Demo5 { public static void main(String[] args) { Thread51 t1=new Thread51(); t1.start(); Thread52 t2=new Thread52(); t2.start(); Thread53 t3=new Thread53(); t3.start(); } } class Thread51 extends Thread{ public static boolean flag=false; @Override public void run() { while(true){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if(flag){ //让当前线程暂停(等待) synchronized ("java") { try { "java".wait();//Object } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println(Math.random()); } } } class Thread53 extends Thread{ public static boolean flag=false; @Override public void run() { while(true){ try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } if(flag){ //让当前线程暂停(等待) synchronized ("java") { try { "java".wait();//Object } catch (InterruptedException e) { e.printStackTrace(); } } } System.out.println("Thread53-----------"+Math.random()); } } } class Thread52 extends Thread{ @Override public void run() { BufferedReader in=new BufferedReader(new InputStreamReader(System.in)); try { in.readLine(); //需要改变flag的值 Thread51.flag=true; Thread53.flag=true; in.readLine(); Thread51.flag=false; Thread53.flag=false; synchronized ("java") { // "java".notify();//唤醒在"java"这个对象下等待的线程 "java".notifyAll();//唤醒所有在"java"这个对象下等待的线程 } } catch (IOException e) { e.printStackTrace(); } } }
2. 方法
wait(): 一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器(锁)
"java".notify();//唤醒在"java"这个对象下等待的线程 每次只能唤醒一个,多个线程wait 唤醒优先级高的。
"java".notifyAll();//唤醒所有在"java"这个对象下等待的线程
特点:
必须用在synchronized方法或synchronized代码块中,
这三个方法全部来自于Object类
sleep()和wait()的异同?
相同点:一旦执行方法,都可以使得当前的线程进入阻塞状态
不同点:1)两个方法声明位置不同,Thread类中声明sleep(),Object类中声明wait
2)调用的要求不同 :sleep() 可以在任何需要的场景调用。wait()必须在同步代码块中使用
3)sleep()是不会释放锁的,wait()会释放锁
八.线程的创建方式三
1、第三种创建方式
a 新建一个接口
b 实现一个接口Callable<方法的返回值>
c 实现抽象方法Call()
启动方式 :
创建对象:FutureTask<String> ft=new FutureTask<>(new Thread61());
使用Thread类进行包装:Thread t=new Thread(ft);
调用start方法启动:t.start();
如何拿到返回值
String str=ft.get();//返回类型默认是泛型类型
特点:
主要是和第二种创建方式的对比
a. 支持泛型
b. 不需要手动处理异常 call方法可以抛出异常,被外面操作捕获,获取异常信息
c. call方法有返回值
FutureTask<String> ft=new FutureTask<>(new Thread61()); Thread t=new Thread(ft); t.start();//运行的是call方法 //返回值如何拿 try {
String str=ft.get(); System.out.println("-----------"+str); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } class Thread61 implements Callable<String>{ @Override public String call() throws Exception { String str="java"; for (int i = 0; i < 10; i++) { str+=i; System.out.println(str); } return str; } }
Future 接口
可以对具体Runable 、callable 任务的执行结果进行取消、查询是否完成、获取结果等。
FutrueTask 是Futrue 接口的唯一的实现类
FutrueTask 同时实现Runable,Funture 接口,它既可以作为Runable被线程执行,又可以作为Future得到Callable 的返回值。
九.线程的创建方式四(线程池)
2. 第四种创建方式 (线程池) 数据库连接池
a. 创建了一个线程池容量为5
ExecutorService es=Executors.newFixedThreadPool(5);
b. submit(Runnable/Callable<>); 添加子线程 直接就启动
c. 关闭资源
es.shutdown();
public class Demo6 { public static void main(String[] args) { //第四种创建方式 //1. 创建了一个线程池容量为5 ExecutorService es=Executors.newFixedThreadPool(5); //2. 添加子线程 es.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("----------"+i); } } }); es.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 10; i++) { try { Thread.sleep(500); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(">>>>>>>>>>"+i); } } }); //3. 关闭 es.shutdown(); } }
Executors:工具类、线程池的工厂类,用于创建并返回不同类型的线程池
① Exectors.newCachedThreadPool():用于创建一个可根据需要创建新线程的线程池
② Exectors.newFixedThreadPool(n):创建一个可重用固定线程数的线程池
③ Exectors.newSingleThreadPool():创建一个只有一个线程的线程池
④ Exectors.newScheduledThreadPool(n):创建一个线程池,它可安排在给定延迟后 运行命令或定期地执行