目录
一.线程创建
线程基础知识介绍
创建方法一:继承Thread类
创建方法二:实现Runnable接口
创建方法三:实现Callable接口;线程池
线程池
Callable接口实现多线程
二.线程状态
线程状态
线程状态转换
代码演示
三.线程优先级
四.用户线程,守护线程
五.线程同步
同步方法
同步代码块
可重入锁ReentrantLock
详细讲解,强烈建议收藏!!!
一.线程创建 线程基础知识介绍什么是线程?为什么要使用线程?
在没有使用到线程的程序中,我们总是只能串行的执行程序,也就是我们的程序一次性只能进行一个 *** 作。而我们使用微信进行视频聊天的时候,我们可以一边视频,一边和其他人打字聊天。这就是多线程,可以一个线程负责视频聊天,一个线程负责打字聊天,使用的任然是一个程序。在日常使用场景中,多线程是必要的。
创建方法一:继承Thread类Thread单词意思就是线程的意思,是Java提供的一个线程类。可以自己创建一个类然后继承Thread,重写run方法。run方法写入你想要进行多线程运行的代码。
通过你建的类,实例化一个对象;通过这个对象调用start()方法,该对象就可以进行多线程 *** 作了。
创建步骤:
1.继承Thread类,重写run方法
2.实例化一个对象,调用start方法(就是执行run函数里面的内容)
代码演示:
//1 创建两个类,继承Thread,都重写run方法
class Thread1 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Thread1 running!");
}
}
}
class Thread2 extends Thread{
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Thread2 running!");
}
}
}
//2 对这两个类分别实例化一个对象,调用start方法
public class Demo1 {
public static void main(String[] args) {
Thread1 thread1 = new Thread1();
Thread2 thread2 = new Thread2();
thread1.start();
thread2.start();
}
}
运行结果发现,Thread1线程和Thread2线程轮流上处理机执行,由此可知该段程序确实实现了多线程 *** 作
创建方法二:实现Runnable接口Runnable是一个接口,该接口是一个函数式接口(不知道函数式接口的伙伴,可以看看我上一篇博客哦),只有一个抽象方法run
创建方法一的Thread类就是实现了Runnable接口,查看源码如下图:
那为什么已经有Java开发人员已经给我们封装好的Thread类,有更多的方法可以供我们调用,我们还要用Runnable接口来实现线程,不是很冗余嘛,容我卖个关子,会在代码中解释。
创建步骤:
1 创建一个类实现Runnable接口,重写run方法
2 通过该类实例化对象
3 将实例化的对象作为参数传到Thread实现线程,再调用start方法
//1 创建Demo1实现Runnable接口,重写run方法
public class Demo1 implements Runnable{
//run方法模拟买票场景
int ticket = 10; //设置票数为10
@Override
public void run() {
// 只要票数有剩余,就可以一直进行买票
while (ticket > 0){
// Thread.currentThread()该静态方法可以获得当前运行的线程 getName()得到线程名称
System.out.println(Thread.currentThread().getName() + " get " +ticket--);
}
}
public static void main(String[] args) {
//2 实例化一个demo1对象
Demo1 demo1 = new Demo1();
//3 将实例化的对象作为参数传到Thread实现线程
//Thread有传入一个对象的构造器,构造器还能加入线程的名称
//实例化了多个Thread类,再调用start方法,就能实现多个线程对同一个对象进行 *** 作
//这就是为什么还要用Runnable接口来实现线程的原因
new Thread(demo1,"pp").start();
new Thread(demo1,"mc").start();
new Thread(demo1,"pp1").start();
new Thread(demo1,"mc1").start();
}
}
上面代码用到的构造器:
细心的伙伴在运行代码的时候会发现,票数可能出现负数的情况,这是明显的异常,原因是多个线程买了同一张票,为了解决这个问题,需要用到线程同步的知识,将在后续章节介绍。
创建方法三:实现Callable接口;线程池Callable也是一个接口,可以使用到线程池。当多线程场景下,我们一直创建或者销毁线程,对性能影响都很大,如果我们提前创建好多个线程,放入线程池,使用时直接获取,使用完放回池中,就可以避免一直创建、销毁,达到重复利用。
线程池ExecutorService 线程池接口
常用方法:
void execute(Runnable command); 执行任务,没有返回值,一般用来执行Runnable
void shutdown();关闭连接池
Executors 该类用于创建线程池
Callable接口实现多线程实现callable接口需要返回值类型 ,而上述两种方法是不需要返回值的。不同与上述两种方法,实现Callable需要重写call方法,而不是run方法,并且call方法需要抛出异常。
创建步骤:
1 创建一个类实现Callable接口,重写call方法
2 创建目标对象,即通过该类实例化对象
3 创建执行的服务,可通过实现线程池来创建
4 提交执行 callable为submit方法
5 获取结果
6 关闭服务
/*实现callable接口需要返回值类型*/
public class TestCallable implements Callable {
//重写call方法需要抛出异常
@Override
public Boolean call() throws Exception {
return null;
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
//1 创建目标对象
TestCallable testCallable1 = new TestCallable();
TestCallable testCallable2 = new TestCallable();
TestCallable testCallable3 = new TestCallable();
//2 创建执行的服务
ExecutorService ser = Executors.newFixedThreadPool(3);
//3 提交执行
Future f1 = ser.submit(testCallable1);
Future f2 = ser.submit(testCallable2);
Future f3 = ser.submit(testCallable3);
//4 获取结果
boolean r1 = f1.get();
boolean r2 = f2.get();
boolean r3 = f3.get();
//5 关闭服务
ser.shutdown();
}
}
二.线程状态
线程状态
创建:当线程被创建时为创建状态
就绪:线程被分配了除处理机以外的所有资源
运行:线程正在处理机上运行
阻塞:线程还缺乏除处理机以外的其他资源导致无法继续执行
终止:线程执行结束
线程状态转换
创建到就绪:线程被分配足够资源就到了就绪状态,可以随时上处理机,理论上创建好的线程调用start方法即可进入就绪状态
就绪到运行:该转换可能又 *** 作系统自行安排,也可通过程序定义的抢占来完成,join方法就是抢占方法,只有join的线程执行完,其他的才能执行
运行到就绪大有可能就是有线程抢占了处理机资源,也可以使用yield方法线程自己礼让
运行到阻塞:除了线程缺乏资源只能阻塞,也可以调用sleep方法,线程自行休眠
代码演示下面对上述及其他线程状态有关方法演示:
// 线程状态演示
public class Station implements Runnable{
@SneakyThrows
@Override
public void run() {
/*
* 线程休眠
* 从运行状态变为阻塞状态
* ...毫秒后,继续变为就绪状态
* 每个线程都有一把锁,休眠锁保持
*/
Thread.sleep(1000);
/*
* 线程礼让
* 从运行状态变为就绪状态,不阻塞
*/
Thread.yield();
}
public static void main(String[] args) throws InterruptedException {
Station station = new Station();
// 线程创建
Thread thread = new Thread(station);
// start之后线程就绪
thread.start();
/*
* 线程抢占
* 只有join的线程执行完,其他的才能执行
*/
thread.join();
//观察线程状态
System.out.println(thread.getState());
}
}
三.线程优先级
上文介绍线程状态切换中有个抢占算法join,什么时候需要抢占呢?其中有个使用场景就是,线程优先级是不同的,高优先级可以抢占低优先级的处理机资源。
Java线程按照数值划分等级,从1~10数值越大等级越高。
可以通过getPriority()这个方法获取线程的优先级
通过setPriority()设置线程的优先级,数值可以从0~10
还提供了三种常量可供选择。
未设置优先级,默认为5.
下面进行代码演示
public class Priority {
public static void main(String[] args) {
// 未设置优先级,优先级都为5,包括主线程
System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
Thread thread1 = new Thread(new ThreadPriority(),"thread1");
thread1.setPriority(1); // 设置线程优先级 1~10
Thread thread2 = new Thread(new ThreadPriority(),"thread2");
thread2.setPriority(Thread.MAX_PRIORITY);
thread1.start();
thread2.start();
}
}
class ThreadPriority implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "--->" + Thread.currentThread().getPriority());
}
}
四.用户线程,守护线程
/*
用户线程 : 用户线程全部执行完毕,程序终止
守护线程 : 守护线程就算无限循环,也会随着用户线程全部终止而终止 日志,垃圾回收,监控内存
默认创建的都是用户线程
*/
thread.setDaemon(true); //将该线程设置为守护线程
五.线程同步
多个线程 *** 作同一个对象,就可能造成同步问题,如上文买票,可能多个线程对同一张票进行 *** 作。解决方案可以让多个线程排队 ,,锁唯对 *** 作对象的线程上锁一,就能避免同步问题
同步方法使用synchronized修饰一个方法,就实现了同步方法,整个方法体都会进行同步;同时只会有一个线程执行这个方法。实际就是对这个方法加上了锁,锁唯一,有一个线程执行了这个方法,就上锁了,其他线程不能进入,就解决了同步问题。
下面优化代码,解决上文遗留的买票问题
public class Lock1 {
public static void main(String[] args) {
//创建一个买票的对象
BuyTicket buyTicket = new BuyTicket();
// 三个线程小明,小红,小蓝都要买票
Thread thread1 = new Thread(buyTicket,"小明");
Thread thread2 = new Thread(buyTicket,"小红");
Thread thread3 = new Thread(buyTicket,"小蓝");
thread1.start();
thread2.start();
thread3.start();
}
}
class BuyTicket implements Runnable{
// 一个100张票
private int ticket = 100;
//票卖完了就设置false,初始为true
boolean flag = true;
//线程执行逻辑,有票就一直买
@Override
public void run() {
while (flag){
buy();
}
}
//实现了一个同步方法,整个方法体都进行了同步
//票大于0,就买;票没了就设置flag为false,没票了
private synchronized void buy() {
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + " get " + ticket--);
}else {
flag = false;
}
}
}
同步代码块
使用synchronized定义一段代码块,对一段代码块或者代码块里的共享变量进行上锁。
上文buy方法也可以用同步代码块实现。
// 实现了一个同步代码块,只对代码块里面的内容进行上锁
private void buy2() {
synchronized(this){
if (ticket > 0){
System.out.println(Thread.currentThread().getName() + " get " + ticket--);
}else {
flag = false;
}
}
}
在取钱场景中,一个账户,同时在手机端和ATM机上取钱 ,也存在同步问题。账户就作为了共享变量,多个线程都可以进行 *** 作。此时将取钱方法使用synchronized修饰,变为同步方法,由于对共享变量更新不及时,依然可能存在同步问题。
此时使用同步代码块对共享变量进行上锁可以完全解决同步问题。
import lombok.Data;
public class Lock2 {
public static void main(String[] args) {
Account account = new Account(100);
// 定义两个线程,取两次钱
Drawing drawing1 = new Drawing(50,account);
Drawing drawing2 = new Drawing(100,account);
drawing1.start();
drawing2.start();
}
}
// 账户类
@Data
class Account{
private int money;
public Account(int money) {
this.money = money;
}
}
// 存取款类,可以多个线程同时进行取钱
class Drawing extends Thread{
final Account account;
int drawingMoney; // 存取金额
public Drawing(int drawingMoney,Account account) {
this.drawingMoney = drawingMoney;
this.account = account;
}
@Override
public synchronized void run() {
if(account.getMoney() - drawingMoney < 0){
System.out.println("没钱了!!!");
}else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setMoney(account.getMoney() - drawingMoney);
System.out.println("取了:" + drawingMoney + ";剩余:" + account.getMoney());
}
}
}
执行结果
依然存在问题,使用同步代码块:
import lombok.Data;
public class Lock2 {
public static void main(String[] args) {
Account account = new Account(100);
// 定义两个线程,取两次钱
Drawing drawing1 = new Drawing(50,account);
Drawing drawing2 = new Drawing(100,account);
drawing1.start();
drawing2.start();
}
}
// 账户类
@Data
class Account{
private int money;
public Account(int money) {
this.money = money;
}
}
// 存取款类,可以多个线程同时进行取钱
class Drawing extends Thread{
final Account account;
int drawingMoney; // 存取金额
public Drawing(int drawingMoney,Account account) {
this.drawingMoney = drawingMoney;
this.account = account;
}
@Override
public void run() {
// 同步代码块,对共享变量上锁
synchronized(account){
if(account.getMoney() - drawingMoney < 0){
System.out.println("没钱了!!!");
}else {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
account.setMoney(account.getMoney() - drawingMoney);
System.out.println("取了:" + drawingMoney + ";剩余:" + account.getMoney());
}
}
}
}
得到结果
同步问题解决。
在实际开发中,可以尽量使用同步代码块,一个方法体并非所有部分都需要上锁,使用同步代码块更加灵活。并且同步代码块我们可以自由选择锁,对于共享变量的处理,使用同步代码块能更好解决问题。
可重入锁ReentrantLock同步代码块、同步方法都是系统帮助我们上锁,解锁。可重入锁可以显示的上锁,解锁。
import java.util.concurrent.locks.ReentrantLock;
public class Lock3 {
public static void main(String[] args) {
Buy buy = new Buy();
new Thread(buy).start();
new Thread(buy).start();
new Thread(buy).start();
}
}
class Buy implements Runnable {
int goods = 10;
// 定义一个可重入锁
private final ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
try {
// 显式上锁
lock.lock();
while (true) {
if (goods > 0) {
System.out.println(goods--);
} else {
break;
}
}
} finally {
// 显式解锁
lock.unlock();
}
}
}
如有帮助,烦请点赞关注!
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)