多线程的基础知识
线程和进程
- 进程:在内存中执行的应用程序
- 线程:是进程中最小的执行单元
- 作用:负责当前进程中程序的运行,一个进程至少有一个线程;一个进程有多个线程我们就称之为多线程程序
我们写的代码存放到了内存当中,需要在CPU上运行,为每一个功能的代码实现时,二者之间开辟了一个通道,方便CPU去内存中提取代码做计算,这个通道称之为“线程”
并发和并行
- 并行:在同一个时刻,有多个执行在多个CPU上同时运行
- 并发:在同一个时刻,有多个指令在单个CPU上交替执行
- 细节:
- 之前CPU是单核,但是在执行多个程序的时候好像是在同时执行,原因是CPU在多个线程之间做高速切换
- 现在CPU是多核多线程的,例如2核4线程,可以同时运行4个线程,当超出时,就会并发并行同时进行
CPU调度
- 分时调度:指的是让所有的线程轮流获取CPU使用权,并且平均分配每个线程占用CPU的时间片
- 抢占式调度:多个线程轮流抢占CPU使用权,那个线程抢到了,哪个线程先执行,一般都是优先级高的先抢到CPU使用权的概率大(Java)
主线程
CPU和内存之间为main方法开辟的通道
创建线程的方式
第一种:extends Thread
- 定义一个类,继承Thread
- 重写run方法,在run方法中设置线程任务(线程任务:此线程要干的具体任务)
- 创建自定义线程类对象
- 调用Thread中的start方法,开启线程,jvm自动调用run方法
public class dou1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("dou1: " + i);
}
}
}
public class dou1Text {
public static void main(String[] args) {
dou1 dou1 = new dou1();
//dou1.run();如果这样调用就会先执行run中的再执行下面的循环
dou1.start(); //当使用这个去调用则输出时两个for循环交替执行,每次执行结果都不一样
for (int i = 0; i < 10; i++) {
System.out.println("main: " + i);
}
}
}
注意:同一个线程不能连续调用多次start,如果想要再次调用start,那么咱们就new一个新的线程对象
Thread中的常用方法
public class dou1 extends Thread {
@Override
public void run() {//Thread重写的接口Runnable中的run方法
//run为重写的方法,父类并未抛异常,因此子类也不可抛异常
for (int i = 0; i < 10; i++) {
try {
Thread.sleep(1000L); //使线程每隔一段时间再执行
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println(getName() + " " + i);//输出时获取线程名字
}
}
}
public class dou1Text {
public static void main(String[] args) {
dou1 dou1 = new dou1();
//dou1.run();如果这样调用就会先执行run中的,结束完毕,再执行下面的循环
dou1.setName("困了");//给线程设置名字,输出:困了 5
dou1.start(); //当使用这个去调用则输出时两个for循环交替执行,每次执行结果都不一样
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread() + " " + i);
//Thread.currentThread()获取正在执行的线程对象(在那个线程当中用,就获取那个线程对象)
} //main方法不可调用getName()
}
}
Thread中的其他方法
final void setPriority(int newPriority) -> 更改此线程的优先级
final int getPriority() -> 返回此线程的优先级
final void join() -> 等待这个线程结束,插入线程
final void setDaemon(boolean on) -> 将此线程标记为 守护进程 线程或用户线程,当非守护线程运行完毕,守护线程也要结束运行,但并不是立马结束,而是非守护线程运行完要告诉守护线程,守护线程接收到信号立马结束运行
static void yield() -> 向调度程序提示当前线程愿意放弃其当前对处理器的使用(即当前线程让出CPU使用权)
线程优先级
分为0、5、10,数字越大级别越高,抢到CPU越容易
public class a1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class a1Text {
public static void main(String[] args) {
a1 a1 = new a1();
a1.setName("第一个");
a1 a2 = new a1();
a2.setName("第二个");
//获取优先级
System.out.println(a1.getPriority());
System.out.println(a2.getPriority());
//设置优先级:0/5/10
a1.setPriority(5);
a2.setPriority(10);
a1.start();
a2.start();
}
}
守护线程
public class a1Text {
public static void main(String[] args) {
a1 a1 = new a1();
a1.setName("第一个");
a2 a2 = new a2();
a2.setName("第二个");
a2.setDaemon(true);//设置守护线程,当非守护线程结束,守护线程也要结束,但不是立马结束
a1.start();
a2.start();
}
}
public class a1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class a2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "hhh " + i);
}
}
}
礼让线程
public class a1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
Thread.yield();//当前程序让出CPU,只是尽可能的平衡
}
}
}
public class a2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "hhh " + i);
}
}
}
public class a1Text {
public static void main(String[] args) {
a1 a1 = new a1();
a1.setName("第一个");
a2 a2 = new a2();
a2.setName("第二个");
a1.start();
a2.start();
}
}
插入线程
public class a1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
}
}
}
public class a1Text {
public static void main(String[] args) throws InterruptedException {
a1 a1 = new a1();
a1.setName("第一个");
a1.start();
a1.join();//将a1插入到当前(main)线程之前,a1结束完毕,再进行main方法的执行
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + "hhh " + i);
}
}
}
第二种:实现Runnable接口
- 创建类,实现Runnable接口
- 重写run方法,在run方法中设置线程任务(线程任务:此线程要干的具体任务)
- 利用Thread里的构造方法:Thread(Runnable target),创建Thread对象(线程对象),将自定义的类当参数传递到Thread构造中
- 调用Thread中的start方法,开启线程,jvm自动调用run方法
public class b1 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
public class b1Text {
public static void main(String[] args) {
b1 b1 = new b1();
Thread thread1 = new Thread(b1);
thread1.start();
}
}
第一种和第二种的区别
第一种为继承,有继承的局限性
第二种没有
匿名内部类创建多线程
public class b1Text2 {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 20; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
},"hhh").start();//给线程设置名字
}
}
public class AnonymousInnerClassExample {
public static void main(String[] args) {
// 创建一个线程,匿名内部类继承了Thread类
Thread thread = new Thread() {
@Override
public void run() {
// 线程执行的代码
System.out.println("线程:" + Thread.currentThread().getName() + " 正在执行");
}
};
// 启动线程
thread.start();
}
}
第三种:Callable接口
-
概述:Callable是一个接口,类似于runnable
-
方法:
V call() -> 设置线程任务,类似于run方法
-
call方法和run方法的区别:
-
相同点:都是设置线程任务的
-
不同点:
call方法有返回值,而且有异常可以throws
run方法没有返回值,而且有异常不可以throws
-
-
实现Callable接口时,指定泛型是什么类型,返回值就是什么类型
-
获取call方法的返回值:FutureTask
-
FutureTask实现了一个接口:Future
-
FutureTask中的一个方法:
V get() -> 获取call方法的返回值
-
import java.util.concurrent.Callable;
public class Callabletext implements Callable<String> {
@Override
public String call() throws Exception {
return "Hello World";
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Call {
public static void main(String[] args) {
Callabletext callabletext = new Callabletext();
FutureTask<String> stringFutureTask = new FutureTask<>(callabletext);
new Thread(stringFutureTask).start();
try {
System.out.println(stringFutureTask.get());
} catch (InterruptedException e) {
throw new RuntimeException(e);
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
}
第四种:线程池
-
如何创建线程池对象:工具类:Executors
-
获取线程池对象:Executors中的一个静态方法
static ExecutorService newFixedThreadPool(int nThreads) -> 创建一个线程池,该线程池重用固定数量的线程,这些线程在共享的无界队列中运行 1.参数:指定线程池中最多创建的线程对象条数 2.返回值ExecutorService是线程池,用来管理线程对象
-
执行线程任务:ExecutorService中的方法
Future<?> submit(Runnable task) -> 提交一个 Runnable 任务以供执行并返回一个代表该任务的 Future <T> Future<T> submit(Callable<T> task) -> 提交一个Callable任务以供执行,并返回一个代表任务未决结果的 Future
-
submit方法的返回值:Future接口
用于接收run方法或者call方法返回值的,但是run方法没有返回值,所以可以不用Future接收,执行call方法需要Future接收
Future中有一个方法:
V get() -> 用于获取call方法返回值
-
ExecutorService中的方法:
void shutdown() -> 启动有序关闭,其中执行先前提交的任务,但不会接受新任务
public class duo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "执行了");
}
}
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class doutext {
public static void main(String[] args) {
ExecutorService es = Executors.newFixedThreadPool(2);
es.submit(new duo());
es.submit(new duo());
es.submit(new duo());
//es.shutdown();//没有这个一直不结束
}
}
import java.util.concurrent.Callable;
public class dou2 implements Callable<Integer> {
@Override
public Integer call() throws Exception {
return 1;
}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class duo2text {
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Integer> future = es.submit(new dou2());
System.out.println(future.get());
}
}
线程安全
解决问题的第一种方式(使用同步代码块)
1.格式:
synchronized(任意对象){
线程可能出现不安全的代码
}
2.任意对象:就是我们的锁对象
3.执行:一个线程拿到锁之后,会进到同步代码块中执行,在此期间,其他线程拿不到锁,就进不去同步代码块,需要在同步代码块外面等待排队,等到执行完毕,出了同步代码块,等待的线程才能抢到锁,进入同步代码块去执行
public class c1 implements Runnable{
int t = 100;
Object o = new Object();
public void run(){
while(true){
synchronized(o){
if(t > 0){
System.out.println(Thread.currentThread().getName()+" "+t);
t--;
}
}
}
}
}
public class c1Text {
public static void main(String[] args) {
c1 c1 = new c1();
Thread t1 = new Thread(c1,"hh");
Thread t2 = new Thread(c1,"kk");
Thread t3 = new Thread(c1,"yy");
t1.start();
t2.start();
t3.start();
}
}
解决问题的第二种方式(同步方法)
普通同步方法
public class c1 implements Runnable{
int t = 100;
public void run(){
while(true){
m1();
}
}
public synchronized void m1(){
if(t > 0){
System.out.println(Thread.currentThread().getName()+" "+t);
t--;
}
}
}
public class c1Text {
public static void main(String[] args) {
c1 c1 = new c1();
Thread t1 = new Thread(c1,"hh");
Thread t2 = new Thread(c1,"kk");
Thread t3 = new Thread(c1,"yy");
t1.start();
t2.start();
t3.start();
}
}
静态同步方法
1.格式:
修饰符 static synchronized 返回值类型 方法名(参数){
方法体
return 结果
}
2.默认锁:this(class对象)
public class c1 implements Runnable{
static int t = 100;
public void run(){
while(true){
// m1();
m2();
}
}
// public static synchronized void m1(){
// if(t > 0){
// System.out.println(Thread.currentThread().getName()+" "+t);
// t--;
// }
// }
public static void m2(){
synchronized (c1.class){
if(t > 0){
System.out.println(Thread.currentThread().getName()+" "+t);
t--;
}
}
}
}
public class c1Text {
public static void main(String[] args) {
c1 c1 = new c1();
Thread t1 = new Thread(c1,"hh");
Thread t2 = new Thread(c1,"kk");
Thread t3 = new Thread(c1,"yy");
t1.start();
t2.start();
t3.start();
}
}
解决问题的第三种方式(Lock锁)
-
概述:Lock是一个接口
-
实现类:ReentrantLock
-
例:
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class c1 implements Runnable{ static int t = 100; static Lock lock = new ReentrantLock(); public void run(){ while(true){ try { Thread.sleep(100L); lock.lock(); if(t>0){ System.out.println(Thread.currentThread().getName() + t + " o"); t--; } } catch (InterruptedException e) { throw new RuntimeException(e); } lock.unlock(); } } } public class c1Text { public static void main(String[] args) { c1 c1 = new c1(); Thread t1 = new Thread(c1,"hh"); Thread t2 = new Thread(c1,"kk"); Thread t3 = new Thread(c1,"yy"); t1.start(); t2.start(); t3.start(); } }
synchronized:不管是同步代码块还是同步方法,都需要在结束一对{}之后,释放锁对象
Lock:是通过两个方法控制需要被同步的代码,更灵活
死锁
-
概述:指的是两个及以上的线程在执行过程中由于竞争同一个锁而产生堵塞的情况;如果没有外力作用,他们将无法继续执行下去,这种情况称之为死锁。
-
代码实现:
public class e1 { public static e1 ee1 = new e1(); } public class e2 { public static e2 ee2 = new e2(); } public class e12 implements Runnable{ private boolean flag; public e12(boolean flag){ this.flag = flag; } @Override public void run() { if(flag){ synchronized (e1.ee1){ System.out.println("ifee1"); synchronized (e2.ee2){ System.out.println("ifee2"); } } }else{ synchronized (e2.ee2){ System.out.println("elseee2"); synchronized (e1.ee1){ System.out.println("elseee1"); } } } } } public class e1Text { public static void main(String[] args) { e12 e121 = new e12(true); e12 e122 = new e12(false); new Thread(e121).start(); new Thread(e122).start(); } }
我们只需知道死锁出现的原因即可(所嵌套),以后尽量避免锁嵌套
线程状态
介绍:当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态。在线程的生命周期分为6种
NEW
:尚未启动的线程处于此状态。(新建)RUNNABLE
:在 Java 虚拟机中执行的线程处于这种状态。(可运行)BLOCKED
:阻塞等待监视器锁的线程处于此状态。(锁堵塞)WAITING
:无限期等待另一个线程执行特定操作的线程处于此状态。(无限等待)TIMED_WAITING
:等待另一个线程执行某个操作达指定等待时间的线程处于此状态。(计时等待)TERMINATED
:已退出的线程处于此状态。(被终止)
等待唤醒
方法 | 说明 |
---|---|
void wait() | 等待线程,等待的过程中线程会释放锁,需要被其他线程调用notify方法将其唤醒,重新抢锁执行 |
void notify() | 线程唤醒,一次唤醒一个等待线程,如果有多条等待线程,则随机唤醒一条等待线程 |
void notifyAll() | 唤醒所有等待线程 |
wait和notify方法需要锁对象调用,所以需要用到同步代码块中,而且必须是同一个锁对象
public class BaoZi {
private int count;
private boolean flag;
public BaoZi() {
}
public BaoZi(int count, boolean flag) {
this.count = count;
this.flag = flag;
}
public void getCount() {
System.out.println("消费了" + count + "包子");
}
public void setCount() {
count++;
System.out.println("生产了hhh" + count + "包子");
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
}
public class BShengChan implements Runnable{
private BaoZi bao;
public BShengChan(BaoZi Baozipu){
this.bao = Baozipu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (bao) {
while (bao.isFlag() == true) {
try {
bao.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
bao.setCount();
bao.setFlag(true);
bao.notifyAll();
}
}
}
}
package DuoXianCheng;
public class BXiaoFei implements Runnable{
private BaoZi bao;
public BXiaoFei(BaoZi Baozipu){
this.bao = Baozipu;
}
@Override
public void run() {
while(true){
try {
Thread.sleep(100L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
synchronized (bao){
while (bao.isFlag() == false){
try {
bao.wait();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
bao.getCount();
bao.setFlag(false);
bao.notifyAll();
}
}
}
}
public class Btext {
public static void main(String[] args) {
BaoZi bao = new BaoZi();
BXiaoFei BXiaoFei = new BXiaoFei(bao);
BShengChan BShengChan = new BShengChan(bao);
// Thread t1 = new Thread(BShengChan);
// Thread t2 = new Thread(BXiaoFei);
// t1.start();
// t2.start(); //此时while改为if;notifyAll改为notify
new Thread(BXiaoFei).start();
new Thread(BShengChan).start();
new Thread(BXiaoFei).start();
new Thread(BShengChan).start();
}
}
定时器Timer
-
构造:
Timer()
-
方法:
void schedule(TimerTask task, Date firstTime, long period) -> 安排指定的任务重复固定延迟执行, 从指定的时间开始 task:抽象类,是Runnable的实现类 firstTime:从什么时间开始 period:每隔多长时间执行一次,设置的是毫秒值
import java.util.Date;
import java.util.Timer;
import java.util.TimerTask;
public class Timerq {
public static void main(String[] args) {
Timer timer = new Timer();
timer.schedule(new TimerTask() {
public void run() {
System.out.println("hhhhh");
}
},new Date(),2000L);
}
}