线程
线程
一、什么是线程?
在讲线程之前需要先知道什么是进程?。
进程: 是指内存中运行的应用程序(App:例如 QQ,微信,stream等),每一个进程都存在一个独立的内存空间,而每一应用程序都可以同时运行多个线程。
如下图:
线程: 线程是进程中的一个执行单元,负责当前进程中程序的执行,每个进程至少有一条线程,进程是可以有多个线程的,这种用用多个线程的,被称为多线程程序。
1、创建线程类
创建线程类有三种方式,如下:
1.1、extends Thread
运行结果:
1.2、implements Runnable
这里new Thread的原因是:Bunnable里面没有start方法,只能通过 new Thread这个方法来实现开启多线程。(这只是其中的一种方法)
运行结果:
1.3、Callable
package com.doem.doem4;
import java.util.concurrent.Callable;
public class MyCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int a=0;
for (int i=0;i<10;i++){
a=a+i;
}
return a;
}
}
package com.doem.doem4;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Text1 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
//把线程任务封装到该类中,该类可以获取线程任务执行后的结果
FutureTask<Integer> integerFutureTask = new FutureTask<>(myCallable);
// 将封装好的类 cun
Thread thread = new Thread(integerFutureTask);
thread.start();
System.out.println(integerFutureTask.get());
}
}
运行结果:
1.4、总结
(1)线程开启后运行的结果是随机的,与平时代码运行的结果不同
(2)通过运行结果我们可以看出,代码的运行是一段一段的运行,好像在my和‘ 欸嘿2‘之间来回跳转,而不是如同我们所认知的同时进行。由此可知,多线程是一条单线程根据相同的时间在不同的运行线上进行快速跳转以此来实现多线程。
(3)在java中一直都是单继承,多实现 ,因此Runnable作为线程,更适合资源共享,同时也容易实现解耦操作。
2、获取和设置线程名称
2.1、获取线程ID和线程名称
// 创建一个类继承Thread 线程类
public class MyThread extends Thread{
// 调用 Thread的 run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// Thread.currentThread().getName() 获取当前线程的名称 --- 可作用在任何地方
// this.getName() 获取当前线程的名称 ---该方法只能用在Thread子类下
System.out.println(this.getName()+"欸嘿==="+i);
}
}
}
运行结果:
2.2、修改线程名称
1、调用线程对象的setName()方法
2、使用线程子类的构造方法赋值
public class My {
public static void main(String[] args) throws InterruptedException {
// 创建 Mythread线程类
MyThread myThread = new MyThread();
// setName 对线程的命名
myThread.setName("测试是否命名成功 yes");
// start 开启线程的方法
myThread.start();
for (int i = 0; i < 20; i++) {
System.out.println("myThread==="+i);
}
}
}
// 创建一个类继承Thread 线程类
public class MyThread extends Thread{
// 调用 Thread的 run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// Thread.currentThread().getName() 获取当前线程的名称 --- 可作用在任何地方
// this.getName() 获取当前线程的名称 ---该方法只能用在Thread子类下
System.out.println(this.getName()+"欸嘿==="+i);
}
}
}
运行结果:
二、线程的常用方法
1、设置优先级 setPriority()
同优先级别的线程,采取的策略就是先到先服务,使用时间片策略。
如果优先级别高,被CPU调度的概率就高。级别从1到10,默认的级别是5。
实例: MyThread2:欸嘿2 MyThread:欸嘿
public class My {
public static void main(String[] args) {
// // 创建 Mythread线程类
MyThread myThread = new MyThread();
// 实现Runnable 的 线程类
MyThread2 myThread2 = new MyThread2();
// Thread() ,括号里面填写的是实Runnable的线程类所创建的线程类 如上
Thread thread = new Thread(myThread2);
// 开启线程
// // start 开启线程的方法
myThread.start();
thread.start();
// 设置优先级
myThread.setPriority(1);
thread.setPriority(10);
// for (int i = 0; i < 10; i++) {
// System.out.println("my2==="+i);
// }
// for (int i = 0; i < 10; i++) {
// System.out.println("my1==="+i);
// }
}
}
运行结果:
这个方法仅仅是提高被调用的概率 因此也会出现如下结果:
2、线程插队方法 join()
谁调用这个方法,就会进入堵塞的状态,必须等待这个方法执行完才会往下执行
public class My {
public static void main(String[] args) throws InterruptedException {
// // 创建 Mythread线程类
MyThread myThread = new MyThread();
// 实现Runnable 的 线程类
MyThread2 myThread2 = new MyThread2();
// Thread() ,括号里面填写的是实Runnable的线程类所创建的线程类 如上
Thread thread = new Thread(myThread2);
// 开启线程
// // start 开启线程的方法
myThread.start();
// join()方法 插队法
myThread.join();
thread.start();
}
}
运行结果:
由结果可知:添加前运行结果为随机的。添加这个方法后给“诶嘿”后,执行完“欸嘿”后才执行”欸嘿“2
3、线程休眠方法 sleep(参数millis)
使正在执行的线程暂停,将CPU转让给别的线程,从而进入休眠等待状态。
public class MyThread extends Thread{
// 调用 Thread的 run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
@Override
public void run() {
try {
// 这里设置 休眠时间为1000毫秒
Thread.sleep(1000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
for (int i = 0; i < 10; i++) {
System.out.println("欸嘿==="+i);
}
}
}
运行结果:
以上结果: 因为设置了休眠时间sleep 因此才会出这种情况。当休眠时间结束后就会出现一下结果。
4、设置伴随线程 setDaemon() (保镖)
将某一个子线程设置为主线程的伴随线程后,主线程停止后,子线程也会伴随着停止。
public class My {
public static void main(String[] args) throws InterruptedException {
// 创建 Mythread线程类
MyThread myThread = new MyThread();
// 实现Runnable 的 线程类
MyThread2 myThread2 = new MyThread2();
// Thread() ,括号里面填写的是实Runnable的线程类所创建的线程类 如上
// Thread thread = new Thread(myThread2);
myThread.setDaemon(true);
// start 开启线程的方法
myThread.start();
for (int i = 0; i < 10; i++) {
System.out.println("myThread==="+i);
break;
}
// thread.start();
}
}
// 创建一个类继承Thread 线程类
public class MyThread extends Thread{
// 调用 Thread的 run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("欸嘿==="+i);
}
}
}
运行结果:
结论: 虽然设置的myThread关闭了,但是欸嘿依旧处于运行状态,这是因为setDaemon() 虽然关闭了,但子线程依旧是运行了以一次相同的时间后才会随着主线程关闭而关闭。
5、 yield()
yield ()的作用是让步,它可以让当前线程由"运行状态"进入到"就绪状态",从而让其它具有相同优先等级的等待线程获取执行权.但是也有可能是当前线程又进入到"运行状态"继续运行!
// 创建一个类继承Thread 线程类
public class MyThread extends Thread{
// 调用 Thread的 run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
@Override
public void run() {
for (int i = 0; i < 20; i++) {
// 开启 yield()
Thread.yield();
System.out.println("欸嘿==="+i);
}
}
}
运行结果:
(1)让出CPU资源使mythread运行
(2)让出CPU资源后又再次占用
三、线程安全
public class My {
public static void main(String[] args) throws InterruptedException {
// 创建 Mythread线程类
MyThread2 myThread2 = new MyThread2();
new Thread( myThread2,"窗口1").start();
new Thread( myThread2,"窗口2").start();
}
}
// 创建一个类实现 Runnable 线程类
public class MyThread2 implements Runnable {
// 调用 Runnable run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
private int ticket = 100;
@Override
public void run(){
while(ticket>0){
System.out.println(Thread.currentThread().getName() + "售卖第" + ticket + "张票");
ticket--;
}
}
}
运行结果:
由结果可知,窗口1都销售到第93张了,但是窗口2却卖到了第94张.
售票出现了问题,而这就是线称安全问题
如何解决线程安全问题?
要想解决线程俺去那问题,就需要优先考虑Java并发的三大基本特性:原子性,可见性,有序性.
原子性
原子性是指在一个操作中就是cpu不可以在中途暂停然后再调度,即不被中断操作,要不全部执行完成
可见性
当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值
有序性
程序执行的顺序按照代码的先后顺序执行,在多线程编程时就得考虑这个问题。
第一种自动锁: synchronized
public class My {
public static void main(String[] args) throws InterruptedException {
// 创建 Mythread线程类
MyThread2 myThread2 = new MyThread2();
new Thread( myThread2,"窗口1").start();
new Thread( myThread2,"窗口2").start();
}
}
public class MyThread2 implements Runnable {
// 调用 Runnable run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
private int ticket = 100;
@Override
public void run(){
while(true){
// 自动锁 () ()括号里输入的共同的值
synchronized (this){
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName() + "还剩" + ticket + "张票");
}else {
break;
}
}
}
}
}
运行结果:
使窗口1直接卖完
第二种手动锁: Lock
// 创建一个类实现 Runnable 线程类
public class MyThread2 implements Runnable {
// 调用 Runnable run 方法,这里重写run方法
// 这里使用循环遍历的目的是为了能看清多线成的结果
private int ticket = 100;
// 创建手动锁
private Lock l = new ReentrantLock();//创建手动锁对象
@Override
public void run() {
while (true) {
// 自动锁 () ()括号里输入的共同的值
// synchronized (this) {
// if(ticket>0){
// ticket--;
// System.out.println(Thread.currentThread().getName() + "还剩" + ticket + "张票");
// }else {
// break;
// }
// }
l.lock();
if (ticket > 0) {
ticket--;
System.out.println(Thread.currentThread().getName() + "还剩" + ticket + "张票");
} else {
break;
}
}
}
}
运行结果:
)
使窗口2直接卖完
四、死锁
死锁:
两个或两个以上的进程(线程)在执行过程中,由于竞争资源或彼此通信而造成一种阻塞的现象,若没有外力作用,他们都将无法进行下去。这些永久在互相等待的进程(线程)称为死锁进程(线程)
public class Text1 {
public static void main(String[] args) {
// 创建ALock()
ALock aLock = new ALock();
// 创建BLock()
BLock bLock = new BLock();
// 开启线程
new Thread(aLock).start();
new Thread(bLock).start();
}
}
package com.doem.dome2;
import java.util.concurrent.locks.ReentrantLock;
// 创建一个类 , 声明两个静态方法 使他们等于ReentrantLock()
public class LockObject {
// 锁A
public static ReentrantLock lockA=new ReentrantLock();
// 锁B
public static ReentrantLock lockB=new ReentrantLock();
}
public class BLock implements Runnable{
@Override
public void run() {
// 设置 自动锁()里面是通过的钥匙
synchronized (LockObject.lockB) {
System.out.println("获得B锁");
// 设置 自动锁()里面是通过的钥匙
synchronized (LockObject.lockA){
System.out.println("获得A");
System.out.println("可以和牌");
}
}
}
}
package com.doem.dome2;
//创建一个线程类
public class ALock implements Runnable{
// 调用Runnable里面的Run方法
@Override
public void run() {
// 设置 自动锁()里面是通过的钥匙
synchronized (LockObject.lockA) {
System.out.println("获得A锁");
// 设置 自动锁 () 通过的钥匙
synchronized (LockObject.lockB){
System.out.println("获得B");
System.out.println("可以和牌");
}
}
}
}
运行结果:
以上便是死锁的结果
如何解决死锁:
(1)、减少同步代码块的嵌套
(2)、设置锁的时间。
(3)、可以使用安全类-jdk提高的安全类
public class Text1 {
public static void main(String[] args) throws InterruptedException {
// 创建ALock()
ALock aLock = new ALock();
// 创建BLock()
BLock bLock = new BLock();
// 开启线程
Thread thread = new Thread(aLock);
thread.start();
// 设置了休眠时间 设置了锁的时间
thread.sleep(10);
new Thread(bLock).start();
}
}
运行结果:
五、线程通信
线程通信:当多个线程共同操作共享数据的资源时,相互告知自己的状态一避免资源争夺。
实现线程通信目的:是为了更好的协作。
为了实现线程之间的消息传递,大多使用的wait/notify等待通方式。
以存钱为例:小明存了100,又向游戏里充100元。
package com.doem.dome3;
public class WeChat extends Object{
// 金额
private int monery;
// 用于判断是否存有钱
private boolean falg=false;
// 为整个方法上锁,防止其他线程进入
// 存钱
public synchronized void saveTask(int yuan){
if(falg==true){
try {
this.wait(); // 调用了父类的方法,当执行到当前的线程就会锁资源,并且进入等待队列中,
// 等待队列中的线程选需要其他线程调用notify来唤醒等待队列中的线程,且参与下次锁的竞争
} catch (InterruptedException e) {
e.printStackTrace();
}
}
monery=monery+yuan;
System.out.println(Thread.currentThread().getName()+"向微信中存了"+yuan+"微信中所剩余额 :"+monery+"元");
falg=true;
// 唤醒---等待队列中的线程对象,随机唤醒
this.notifyAll();
}
// 为整个方法上锁,防止其他线程进入
// 充值
public synchronized void tackTask(int yuan){
if(falg==false){
try {
this.wait(); // 调用了父类的方法,当执行到当前的线程就会锁资源,并且进入等待队列中,
// 等待队列中的线程选需要其他线程调用notify来唤醒等待队列中的线程,且参与下次锁的竞争
} catch (InterruptedException e) {
e.printStackTrace();
}
}
monery=monery-yuan;
System.out.println(Thread.currentThread().getName() + "向游戏中充值了" + yuan + "微信中所剩余额 :" + monery + "元");
falg=false;
// 唤醒---等待队列中的线程对象,随机唤醒
this.notifyAll();
}
}
package com.doem.dome3;
// 创建SaveTask 线程类
public class SaveTask implements Runnable{
// 设置一个WeChart对象;
private WeChat weChat;
// 使weChat等于dome3.WeChat
public SaveTask(WeChat w) {
weChat = w;
}
@Override
public void run() {
// 设置循环查看效果
for (int i=0;i<10;i++){
weChat.saveTask(100);
}
}
}
package com.doem.dome3;
public class TackTask implements Runnable {
private WeChat weChat;
public TackTask(WeChat w) {
weChat = w;
}
@Override
public void run() {
for (int i=0;i<10;i++){
weChat.tackTask(100);
}
}
}
package com.doem.dome3;
public class Text2 {
public static void main(String[] args) {
WeChat weChat = new WeChat();
SaveTask saveTask = new SaveTask(weChat);
TackTask tackTask = new TackTask(weChat);
Thread thread = new Thread(saveTask, "小明");
Thread thread1 = new Thread(tackTask, "小明");
thread.start();
thread1.start();
}
}
运行结果:
六、线程的状态
NEW:新建状态
RUNNABLE: start()就绪状态-时间片-运行状态. 统称为RUNNABLE
BLOCKED: 堵塞状态。加锁时就如该状态
WAITING: 无期等待: 调用wait方法时会进入该状态
TIMED_WAITING: 有期等待---当调用sleep方法时就会进入该状态
TERMINATED: 终止状态。线程的任务代码执行完毕或出现异常。
线程的状态之间可以通过调用相应的方法,进行转换。
七、线程池
7.1、什么是线程池
线程池:
(1)是一种利用池化技术思想来实现的线程管理技术。
(2)线程容器,可以设定线程分配的数量上限。
(3)将预先创建的线程对象存入到之中,并重用线程池中的线程对象。
(4)避免频繁的创建和销毁。
线程池的优点:
(1)降低资源消耗,复用已创建的线程来降低创建和销毁程的消耗。
(2)提高响应速度,任务到达时,可以不需要等待线程的创建立即执行。
(3)提高线程的可管理性,使用线程池能够统一的分配、调优和监控。
7.2 线程池
package com.doem.doem4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
// 1、创建一个固定长度的线程池。
ExecutorService executorService = Executors.newFixedThreadPool(5);
for(int i=0;i<50;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~");
}
});
}
executorService.shutdown();
}
}
运行结果:
package com.doem.doem4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
// 1、创建一个固定长度的线程池。
// ExecutorService executorService = Executors.newFixedThreadPool(5);
// 2、单一线程
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i=0;i<50;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~");
}
});
}
executorService.shutdown();
}
}
运行结果:
package com.doem.doem4;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class Test {
public static void main(String[] args) {
// 1、创建一个固定长度的线程池。
// ExecutorService executorService = Executors.newFixedThreadPool(5);
// 2、单一线程
// ExecutorService executorService = Executors.newSingleThreadExecutor();
// 3、可变线程池---缓存线程池
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<50;i++){
executorService.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~");
}
});
}
executorService.shutdown();
}
}
运行结果:
package com.doem.doem4;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
public class Test {
public static void main(String[] args) {
// 1、创建一个固定长度的线程池。
// ExecutorService executorService = Executors.newFixedThreadPool(5);
// 2、单一线程
// ExecutorService executorService = Executors.newSingleThreadExecutor();
// 3、可变线程池---缓存线程池
// ExecutorService executorService = Executors.newCachedThreadPool();
// 延迟线程池
ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(5);
for(int i=0;i<50;i++){
// executorService.submit(new Runnable() {
// @Override
// public void run() {
// System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~");
// }
// });
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"~~~~~~~~~~~~~~~~~~~~");
}
},10, TimeUnit.SECONDS);
}
executorService.shutdown();
}
}
运行结果:
延迟效果:
7.3线程池常用方法
Executor:线程池的根接口
void execute(Runnable command):执行Runnable类型的任务
ExecutorService: Executor的子接口。
void shtdown(): 关闭线程池。需要等待任务执行完毕。
shutdownNow(); 立即关闭线程池。不在接受新任务。
isShutdown(): 判断是否执行了关闭。
isTerminated(): 判断线程池是否终止。表示线程池中的任务都执行完毕,并且线程池关闭了
submint(Callable<T> task); 提交任务,可以提交Callable
submint(Runnable task): 提交任务,可以提交Runnable任务