Java-多线程
CPU
CPU中文名中央处理器,是电脑中进行逻辑运算时用的,主要由运算器,控制器,寄存器三部分组成,运算器是起着运算的作用,控制器是负责发出CPU每条指令所需要的信息,寄存器是为了存储运算或者指令的一些临时文件。线程运行在CPU上。
- CPU的两种工作状态
-
内核态:运行的程序是操作系统,可以操作计算机硬件
-
用户态:运行的程序是应用程序,不能操作计算机硬件
应用程序的运行必然涉及到计算机硬件的操作,那就必须有用户态切换到内核态下才能实现,所以计算机工作时在频繁发生内核态与用户态的转换
线程和进程的区别
- 说起进程就必须要说一下程序,程序是指令和数据的有序集合
- 而进程就是程序执行的过程,它是一个动态概念,是系统资源分配的单位
- 线程是存在于进程中,一个进程中有一个或者多个线程同时执行,线程是CPU调度和执行的单位
java中创建线程的几种方法
1.继承Thread类
//第一种创建线程的方法
//继承Thread类
public class TestThread01 extends Thread {
/**
* 继承Thread方法,因为Thread方法底层也是实现Runnable接口所以第一步都需要重写run方法
*/
@Override
public void run() {
//run方法线程体
for (int i = 0; i < 50; i++) {
System.out.println("执行run方法"+i);
}
}
//主线程main方法
public static void main(String[] args) {
//在主线程main方法中创建一个子线程TestThread01
TestThread01 testThread01 = new TestThread01();
//这句代码相当于只有主线程一条线程执行run方法
// testThread01.run();
//运行子线程,这里相当于用子线程执行run方法
//所以就是主线程和子线程同时交替执行
testThread01.start();
for (int i = 0; i < 1000; i++) {
System.out.println("执行main方法"+i);
}
}
}
2.实现Runnable接口
//创建线程的第二种方法:实现Runnable接口
public class TestThread03 implements Runnable {
//重写run方法
@Override
public void run() {
for (int i = 0; i < 200; i++) {
System.out.println("执行了run方法"+i);
}
}
public static void main(String[] args) {
TestThread03 testThread03 = new TestThread03();
Thread thread = new Thread(testThread03);
thread.start();
for (int i = 0; i < 1000; i++) {
System.out.println("执行main方法"+i);
}
}
}
3.实现Callable接口
通过实现Callable接口使用多线程同时下载三张图片:
public class TestCallable implements Callable<String> {
private String url;
private String name;
TestCallable(String url,String name){
this.url=url;
this.name=name;
}
//和上面两种方法不同的是Callable接口重写的是call方法
@Override
public String call() throws Exception {
Downloads download = new Downloads();
download.downloadImage(url,name);
System.out.println("下载了文件名为"+name);
return "true";
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable testThread01 = new TestCallable("https://lbm-edu.oss-cn-shenzhen.aliyuncs.com/cover/4e4d3d86baa840999bb546e4d5c30a4cC语言实用教程.jpg","C语言.jpg");
TestCallable testThread02 = new TestCallable("https://lbm-edu.oss-cn-shenzhen.aliyuncs.com/cover/5e7b616be66d4328bbde2e685714ccc4单片机.jpg","单片机.jpg");
TestCallable testThread03 = new TestCallable("https://lbm-edu.oss-cn-shenzhen.aliyuncs.com/cover/c8d5a81fe4664f2583ca347d258884e8三国.jpg","三国.jpg");
//创建线程池,设置线程池大小为3
ExecutorService service = Executors.newFixedThreadPool(3);
//分别将3个线程加入到线程池当中
Future<String> submit1 = service.submit(testThread01);
Future<String> submit2 = service.submit(testThread02);
Future<String> submit3 = service.submit(testThread03);
//拿到线程的运行结果
submit1.get();
submit2.get();
submit3.get();
//关闭线程池
service.shutdown();
}
}
class Downloads {
//下载图片的方法
public void downloadImage(String url,String name) {
try {
//这是工具类Apache下的Commons.io包,通过copyURLToFile方法可以下载图片
FileUtils.copyURLToFile(new URL(url),new File(name));
} catch (IOException e) {
System.out.println("downloadImage出现了问题");
e.printStackTrace();
}
}
}
运行结果:
可以看出运行的结果和线程执行的顺序不一样,说明是同步执行的,哪个运行速度快哪个先执行完。
什么是线程并发问题
//多个线程同时调用一个对象的时候出现并发问题
//买火车票
public class TestThread04 implements Runnable {
private int ticketNums=10;
@Override
public void run() {
while (true){
if (ticketNums<=0){
break;
}
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"拿到了第"+ticketNums--+"张票。。。。");
}
}
public static void main(String[] args) {
TestThread04 thread04 = new TestThread04();
new Thread(thread04,"线程1").start();
new Thread(thread04,"线程2").start();
new Thread(thread04,"线程3").start();
new Thread(thread04,"线程4").start();
}
}
运行结果:
程序可以看出一共有四个程同时操作一个买票的对象,并且有同时拿到一张票的情况,这是因为还剩n张票的时候两个线程同时看见了,并且同时拿了。也有拿到-1的情况,那是因为还剩1张票的时候几个线程同时去拿,当某个线程拿完之后只剩下0张票了,但是另一个线程已经开始拿了,所以只能拿到-1了(可能说的不太清楚,表达能力也就到这了。。。。。)。
线程的五种状态
线程方法
1.线程停止–stop(不推荐使用)
- 不推荐使用JDK提供的stop()、destroy()【以经废弃】方法。
- 推荐线程自己运行完停止
- 或者使用一个标志位进行终止,比如boolean flag=false的时候终止线程
2.线程休眠–sleep
- sleep存在异常InterredException
- sleep(时间)指定当前线程阻塞的毫秒数
- sleep时间达到后线程进入就绪状态
- 每个对象都有一个锁,sleep不会释放锁
3.线程礼让–yield
- 线程礼让,让当前正在执行的线程暂停,但不是进入阻塞状态
- 将线程从运行状态进入就绪状态
- 意思就是让CPU重新调度,当时有可能再次调用礼让的那个线程,具体看CPU如何调度。
4.线程强制执行–join
- Join合并线程,带此线程执行完成后再执行其他线程,其他线程阻塞
- 可以想象成插队
public class TestJoin implements Runnable {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println("VIP来了"+i);
}
}
public static void main(String[] args) throws InterruptedException {
TestJoin testJoin = new TestJoin();
Thread thread = new Thread(testJoin);
thread.start();
for (int i = 0; i < 1000; i++) {
if (i == 200) {//当i=200的时候让线程插进来
thread.join();
}
System.out.println("主线程执行中。。。。"+i);
}
}
}
运行结果:
可以看出当线程等于两百的时候“VIP”线程插队进来,其他线程进入阻塞状态,直到VIP线程运行完其他线程再开始运行
线程状态观测
线程可以处于以下六种状态之一:
public class TestState implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(10);//睡眠的时候状态为TIMED_WAITING(等待另一个线程执行动作,一直到达到指定等待时长-这里是5s)
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(".........");
}
public static void main(String[] args) {
TestState testState = new TestState();
Thread thread = new Thread(testState);
Thread.State state = thread.getState();
System.out.println(state);//刚new出来的时候状态为NEW
thread.start();
state=thread.getState();
System.out.println(state);//线程开始的时候状态为RUNNABLE
while (thread.getState()!= Thread.State.TERMINATED){//只要线程的状态不等于终止状态就一直观察并打印线程的状态
state=thread.getState();
System.out.println(state);
}
}
}
线程优先级
- java提供一个线程调度器来监控程序中启动后进入就绪状态的所有线程,线程调度器按照优先级决定应该调度那个线程来执行
- 线程的优先级用数字表示,范围1~10
- Thread.MIN_PRIORITY=1
- Thread.MAX_PRIORITY=10
- Thread.NORM_PRIORITY=5
- 使用一下方法可以设置或者获得优先级
- getPriority()
- setPriority(int xxx)
守护(daemon)线程–setDaemon(true)
- 线程分为用户线程和守护线程
- 比如说常见的main方法就是用户线程而GCC垃圾回收就是守护线程
- 虚拟机不需要等待守护线程执行完毕
- 虚拟机要等待用户线程执行完毕
- 常见的守护线程:后台记录操作日志,监控内存,垃圾回收