java多线程创建及线程安全详解
作者:ITdfq 发布时间:2022-12-02 18:58:51
什么是线程
线程被称为轻量级进程,是程序执行的最小单位,它是指在程序执行过程中,能够执行代码的一个执行单位。每个程序程序都至少有一个线程,也即是程序本身。
线程的状态
新建(New):创建后尚未启动的线程处于这种状态
运行(Runable):Runable包括了操作系统线程状态的Running和Ready,也就是处于此状态的线程有可能正在执行,也有可能正在等待着CPU为它分配执行时间。
等待(Wating):处于这种状态的线程不会被分配CPU执行时间。等待状态又分为无限期等待和有限期等待,处于无限期等待的线程需要被其他线程显示地唤醒,没有设置Timeout参数的Object.wait()、没有设置Timeout参数的Thread.join()方法都会使线程进入无限期等待状态;有限期等待状态无须等待被其他线程显示地唤醒,在一定时间之后它们会由系统自动唤醒,Thread.sleep()、设置了Timeout参数的Object.wait()、设置了Timeout参数的Thread.join()方法都会使线程进入有限期等待状态。
阻塞(Blocked):线程被阻塞了,“阻塞状态”与”等待状态“的区别是:”阻塞状态“在等待着获取到一个排他锁,这个时间将在另外一个线程放弃这个锁的时候发生;而”等待状态“则是在等待一段时间或者唤醒动作的发生。在程序等待进入同步区域的时候,线程将进入这种状态。
结束(Terminated):已终止线程的线程状态,线程已经结束执行。
多线程创建方法
继承Thread
/**
* @Author GocChin
* @Date 2021/5/11 11:56
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class MyThread extends Thread{
@Override
public void run() {
System.out.println(currentThread().getName()+"运行了");
}
}
class Test{
public static void main(String[] args) {
MyThread myThread = new MyThread();
System.out.println(Thread.currentThread().getName()+":运行了");
myThread.start();
}
}
实现Runable接口创建多线程
/**
* @Author GocChin
* @Date 2021/5/11 12:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 实现Runable接口的方式创建多线程
* 1.创建一个实现了Runable接口的类
* 2.实现类去实现Runable中的抽象方法,run();
* 3.创建实现类的对象
* 4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5.通过Thread类的对象调用start()
*/
class MThread implements Runnable{
@Override
public void run() {
for (int i = 0; i<100;i++){
if (i%2!=0){
System.out.println(i);
}
}
}
}
public class ThreadTest1 {
public static void main(String[] args) {
//3.创建实现类的对象
MThread mThread = new MThread();
//4.将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread thread = new Thread(mThread);
thread.start();
}
}
Thread和Runable创建多线程对比
开发中:优先使用Runable
1.实现的方式没有类的单继承的局限性。
2.实现的方式跟适合处理多个线程有共享数据的情况。
联系:Thread类中也实现了Runable,两种方式都需要重写run()。
实现Callable接口
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* @Author GocChin
* @Date 2021/5/11 13:03
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class MCallable implements Callable<Integer> {
@Override
public Integer call() throws Exception {
int sum=0;
for(int i=0;i<100;i++){
sum+=i;
}
return sum;
}
}
public class CallableTest {
public static void main(String[] args) {
//执行Callable 方式,需要FutureTask 实现实现,用于接收运算结果
FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new MCallable());
new Thread(integerFutureTask).start();
//接受线程运算后的结果
Integer integer = null;
try {
integer = integerFutureTask.get();
System.out.println(integer);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
与Runable相比,Callable功能更强大
相比run()方法可以有返回值
方法可以抛出异常
支持泛型的返回值
需要借助FutureTask类,比如获取返回结果
使用线程池进行创建
线程池创建的好处
提高响应速度(减少了创建新线程的时间)
降低资源消耗(重复利用线程池中线程,不需要每次都创建)
便于线程管理:
corePoolSize:核心线程池的大小
maximumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多长时间后悔中止
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Author GocChin
* @Date 2021/5/11 13:10
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class Thread1 implements Runnable{
@Override
public void run() {
for (int i=1;i<30;i++){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//创建线程池
ExecutorService executorService= Executors.newFixedThreadPool(10);
Thread1 threadPool = new Thread1();
for (int i=0;i<5;i++){
//为线程池分配任务
executorService.submit(threadPool);
}
//关闭线程池
executorService.shutdown();
}
}
Thread中的常用方法
start():启动当前线程;调用当前线程的run();
run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中。
currentThread():静态方法,返回当前代码的线程。
getName():获取当前线程的名字。
setName():设置当前线程的名字。
yield():释放当前cpu的执行权,切换线程执行。
join():在线程a中调用线程b的join(),此时线程a会进入阻塞状态,知道线程b完全执行完毕,线程a 才结束阻塞状态。
stop():强制线程生命期结束。(过时了,不建议使用)
isAlive():判断线程是否还活着。
sleep(long millitime):让当前线程睡眠指定的事milltime毫秒。在指定的millitime毫秒时间内,当前线程是阻塞状态。
线程的优先级
线程的优先级等级
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5
涉及的方法
getPriority():返回线程的优先值
setPriority(int newPriority):改变线程的优先级
说明
线程创建时继承父线程的优先级
低优先级知识获得调度的概率低,并非一定是在高优先级线程之后才被调用
线程的同步
多线程卖票
基于实现Runable的方式实现多线程买票
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 13:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 创建三个窗口买票,总票数为100张,使用Runable接口的方式
* 存在线程安全问题,待解决
*/
class Thread2 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if (ticket>0) {
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}else {
break;
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Thread2 thread2 = new Thread2();
Thread t1 = new Thread(thread2);
Thread t2 = new Thread(thread2);
Thread t3 = new Thread(thread2);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
实现结果,存在重复的票
如果在买票方法中加入sleep函数
public void run() {
while (true){
if (ticket>0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}else {
break;
}
}
}
则运行结果可能会出现-1,表示也是不正常的
理想情况
极端情况
在java中,我们通过同步机制,来解决线程的安全问题。
同步代码块
synchronized(同步监视器){
//需要被同步的代码
}
说明
操作共享数据的代码就是需要被同步的代码。
共享数据:多个线程共同操作的变量,比如本题中的ticket就是共享数据。
同步监视器:俗称:锁。任何一个类的对象都可以充当锁。要求:多个线程必须要共用统一把锁。
同步的方式,解决了线程的安全问题—好处。但是操作同步代码时,只能有一个线程参与,其他线程等待。相当于是一个单线程的过程,效率低。-----局限性
使用Runable接口创建多线程的方式中,可以使用this关键字;在继承Thread类中创建多线程中,慎用this充当同步监视器,可以考虑使用当前类充当同步监视器。Class clazz = Windows.class 因此 类也是一个对象
包裹操作共享数据的代码 不能多也不能少
修改之后的代码:
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 13:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 创建三个窗口买票,总票数为100张,使用Runable接口的方式
* 存在线程安全问题,待解决
*/
class Thread2 implements Runnable{
private int ticket=100;
Object object = new Object();
@Override
public void run() {
while (true){
synchronized(object) { //括号中的内容可以直接使用当前对象this去充当
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
public class Test1 {
public static void main(String[] args) {
Thread2 thread2 = new Thread2();
Thread t1 = new Thread(thread2);
Thread t2 = new Thread(thread2);
Thread t3 = new Thread(thread2);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
结果
继承Thread的方式,去使用同步代码块,需要将声明的锁对象设为statci,否则创建的对象的同步监视器不唯一,就无法实现。
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 14:45
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class WindowsTest2 extends Thread{
private static int ticket=100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){ //这里不能使用this去充当,可以直接写一个Test.class 类也是对象
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(getName()+":买票,票号为:"+ticket);
ticket--;
}else {
break;
}
}
}
}
}
public class Test2{
public static void main(String[] args) {
WindowsTest2 w1 = new WindowsTest2();
WindowsTest2 w2 = new WindowsTest2();
WindowsTest2 w3 = new WindowsTest2();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
同步方法
如果操作共享数据的代码完整的声明在一个方法中,可以将此方法声明为同步的。
通过实现Runable的方式实现同步方法。
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 13:37
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript: 创建三个窗口买票,总票数为100张,使用Runable接口的方式
* 存在线程安全问题,待解决
*/
class Thread3 implements Runnable {
private int ticket = 100;
@Override
public void run() {
while (true) {
show();
}
}
private synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":买票,票号为:" + ticket);
ticket--;
}
}
}
public class Test3 {
public static void main(String[] args) {
Thread3 thread3 = new Thread3();
Thread t1 = new Thread(thread3);
Thread t2 = new Thread(thread3);
Thread t3 = new Thread(thread3);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
通过实现继承Thread的方式实现同步方法。使用的同步监视器是this,则不唯一,就会报错。所以将该方法定义为static。当前的同步换时期就变成Test4.class了
package demo2;
/**
* @Author GocChin
* @Date 2021/5/11 14:45
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class WindowsTest4 extends Thread{
private static int ticket=100;
private static Object obj = new Object();
@Override
public void run() {
while (true){
show();
}
}
public static synchronized void show(){//同步监视器不是this了,而是当前的类
// public synchronized void show(){//同步监视器是this ,t1,t2,t3
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":买票,票号为:"+ticket);
ticket--;
}
}
}
public class Test4{
public static void main(String[] args) {
WindowsTest4 w1 = new WindowsTest4();
WindowsTest4 w2 = new WindowsTest4();
WindowsTest4 w3 = new WindowsTest4();
w1.setName("窗口一");
w2.setName("窗口二");
w3.setName("窗口三");
w1.start();
w2.start();
w3.start();
}
}
总结
同步方法仍然设计到同步监视器,只是不需要我们去显示的声明。
非静态的同步方法,同步监视器是:this静态的同步方法中,同步监视器是类本身。
Lock锁解决线程安全问题
synchronize与lock的异同
相同
都可以解决线程安全问题
不同
synchronize机制在执行相应的同步代码以后,自动的释放同步监视器;Lock需要手动的启动同步lock(),同时结束同步也需要手动的实现unlock()。
建议优先使用顺序
Lock------>同步代码块(已经进入了方法体,分配了相应资源)---->同步方法(在方法体之外)
package demo2;
import java.util.concurrent.locks.ReentrantLock;
/**
* @Author GocChin
* @Date 2021/5/11 15:58
* @Blog: itdfq.com
* @QQ: 909256107
* @Descript:
*/
class Lock1 implements Runnable{
private int ticket=50;
//1.实例化
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
//2.调用lock锁定方法
lock.lock();
if (ticket>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"售票,票号为:"+ticket);
ticket--;
}else{
break;
}
} finally {
//3.调用解锁方法
lock.unlock();
}
}
}
}
public class LockTest1 {
public static void main(String[] args) {
Lock1 lock1 = new Lock1();
Thread t1 = new Thread(lock1);
Thread t2 = new Thread(lock1);
Thread t3 = new Thread(lock1);
t1.setName("窗口一");
t2.setName("窗口二");
t3.setName("窗口三");
t1.start();
t2.start();
t3.start();
}
}
来源:https://blog.csdn.net/qq_43417276/article/details/116645306
猜你喜欢
- 在绝大多数android机器etc路径下存放一个的apns-conf.xml文件,表示当前机器使用的apn信息通过root机器可以push出
- 函数指针最近看android camera 的source ,发现大量的call back ,多线程,有必要对其中的基础 :函数指针复习一下
- Spring的HandlerMapping支持 * , * 必须实现HandlerInterceptor接口,此接口里面有下面3中方法:1.
- 前言上文讲的MyBatis部署运行且根据官网运行了一个demo:一步到位部署运行MyBatis3源码<保姆级>jdbc再贴一个J
- Spring Cloud feign GET请求无法用实体传参代码如下:@FeignClient(name = "eureka-c
- C#中打印其实就是自己绘图+调用系统打印函数,于是便有了以下操作1.调用打印机设置如果你想在打印前设置打印机属性(或者切换打印机),请务必添
- 本文实例为大家分享了Android开发实现抽屉菜单的具体代码,供大家参考,具体内容如下实现效果点击菜单图表即可进入抽屉代码实现1、打开app
- 为什么Android要申请权限简单说下在Android6.0及6.0以上一些google认为涉及“危险和用户隐私”的一些权限不仅要做清单文件
- 前言做APP应用开发的时候,用户头像肯定是必不可少的,但是90%以上的需求头像都是圆形的。那么,如何通过自定义View的方式实现圆形头像呢,
- 前言众所周知,随着Google I/O大会的召开,Google宣布将支持Kotlin作为Android的开发语言,最近几日,关于Kotlin
- 1、Dom4j概述dom4j is an easy to use, open source library for working with
- 一、前言学习概述:学习四种不同类型的方法应用、方法被调用时的内存图、重载学习目标:熟练掌握方法的应用以及重载二、定义与调用1.概述定义:方法
- 一、表创建一、表创建//创建一个空表DataTable dt = new DataTable();//创建一个名为"Table_N
- 本文为大家分享了java多线程的简单实现及线程池实例,供大家参考,具体内容如下一、多线程的两种实现方式1、继承Thread类的多线程/**
- C# windows语音识别与朗读示例,供大家参考,具体内容如下本示例通过windows语音识别功能进行语音识别和文本朗读。打开window
- 一、效果:我们看到很多软件的通讯录在右侧都有一个字母索引功能,像微信,小米通讯录,QQ,还有美团选择地区等等。这里我截了一张美团选择城市的图
- 本文实例讲述了C#常用目录文件操作类。分享给大家供大家参考。具体分析如下:这个c#类封装了常用的目录操作,包括列出目录下的文件、检测目录是否
- MyBatis是支持定制化SQL、存储过程以及高级映射的优秀的持久层框架,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。spri
- 题目要求思路一:反向点+并查集根据题意不喜欢就不在一个组可以想到使用并查集,本题是两个集合所以对每一个节点引入一个反向点,使两者分属于不同集
- File类File类事java.io包中唯一代表磁盘文件本身的对象。File类定义了一些与平台无关的方法来操作文件,可以通过调用File类中