线程常识
什么是线程
任务管理器可以有多个进程,每个进程运行的都是可执行程序,一个可执行程序就是一个软件,可执行程序的本质就是在计算机当中运行的一块代码
进程:可以看成是在计算机当中运行的一块代码
线程:可以看成是在计算机当中运行的一小块代码
线程与进程的关系
-
一个进程中可以有多个线程,至少得有一个线程;
-
上面说一个进程可以狭隘的看成是一大段代码,那其实线程也是一段代码
-
线程是进程中的最小单位;
-
也可以把线程看成是一个轻量级的进程
多线程为什么快?
基本上每个线程通过抢占得到CPU的概率都是相同,故一个软件开的线程数越多,则该软件的得到CPU使用权的概率就更大。无论是哪一种调度算法,先来先服务、先进先出、时间片轮转等等相关的调度算法,当一个软件开启的线程数越多,则该软件得到CPU使用权的概率就更大,即其对应线程更容易被CPU调用执行,故多线程更快。
线程的作用
主要是有两种理解方式:
-
可以将代码中(软件)的某些独立的功能包装起来,单独作为任务交给CPU处理!
-
将需做的某个功能封装成一个线程体,该线程可以独立的获得CPU分配的资源
从而实现多功能同时运行。
创建线程的方式
继承Thread类
具体语法:通过继承Thread类,然后再重写run方法。
语法示例:
实现Runnable接口
具体语法:通过实现Runnable接口,重写其中的run方法实现线程创建
具体语法:
线程注意事项
直接调用run方法与start方法的区别
-
可以直接调用run方法,但是没有启动一个独立的线程;
-
只有调用start 才会启动一个独立的线程;由cpu调度run
自己主动创建的线程与主线程有什么关系
-
直接写一个最简单的hello word 程序,就有一个主线程
-
一个线程一旦启动就是独立的了,和创建启动它的环境没有直接的包含关系
继承Thread与Runnable的区别
1、继承有局限,Java中类只能够单继承
2、实现的方式,我们的类在业务上可以继承它本应该有的类,同时可以实现接口变成一个线程类
3、关于数据共享的问题:就看所谓被共享的数据所在的类的对象被创建了几个
线程安全问题
什么是线程安全问题
多个线程同时操作同样的数据,可能会导致数据出错。
如何解决线程安全问题
线程同步
1.同步代码
基本语法结构
synchronized (同步监听对象) {
可能引发线程安全问题的代码
}
代码示例:
2.同步方法
基本语法:就是在需要被同步的方法上面加关键字 synchronized
代码示例:
上锁
代码示例:
注意事项:上锁了一定要记得解锁,不然会导致系统进入死锁状态。
简单案例示例
需求:三个售票窗口,共同售卖五十张门票,请给出相关JAVA代码。
额外要求(通过三种方式分别解决线程安全问题,且通过两种不同的方法创建线程)
1.通过继承方式创建线程
1.1通过同步代码解决线程安全问题
//同步代码块实现
public class TicketThread1 extends Thread {
static int num=50;//定义一个资源共享
public TicketThread1() {
}
public TicketThread1(String name) {
super(name);
}
@Override
public void run(){
while (num>0){//每一轮三个线程都会在此等待
synchronized (TicketThread1.class){//只同步核心代码
if (num > 0) {//多一个判断,避免出现负值票号
System.out.println(getName()+"当前票号"+num);
num--;
}
}
}
}
}
class TicketThreadTest{
public static void main(String[] args) {
TicketThread1 thread1=new TicketThread1("窗口一");
TicketThread1 thread2=new TicketThread1("窗口二");
TicketThread1 thread3=new TicketThread1("窗口三");
thread1.start();
thread2.start();
thread3.start();
//启动线程
}
}
1.2通过同步方法解决线程安全问题
//同步方法
public class TicketThread2 extends Thread {
static int num=50;
public TicketThread2(){
}
public TicketThread2(String name){
super(name);
}
@Override
public void run(){
while (num>0){
sale();
}
}
public static synchronized void sale(){
Thread thread=Thread.currentThread();
if(num>0){
System.out.println(thread.getName()+"当前票号"+num);
num--;
}try {
Thread.sleep(50);
}catch (Exception e){
System.out.println("发生了异常");
}
}
}
class TicketThread2Test{
public static void main(String[] args) {
//创建三个线程
TicketThread2 thread1=new TicketThread2("窗口一");
TicketThread2 thread2=new TicketThread2("窗口二");
TicketThread2 thread3=new TicketThread2("窗口三");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
1.3通过加锁解决线程安全问题
//加锁
public class TicketThread3 extends Thread {
static int num = 50;
public TicketThread3() {
}
public TicketThread3(String name) {
super(name);
}
static Lock lock = new ReentrantLock();//定义一个共享锁
@Override
public void run() {
Thread thread = Thread.currentThread();//当前线程
while (num > 0) {
lock.lock();
if (num > 0) {
System.out.println(thread.getName() + "当前票号为" + num);
num--;
}
try{
Thread.sleep(50);
}catch (Exception e){
System.out.println("发生了异常");
}finally {
lock.unlock();
}
}
}
}
class TicketThread3Test{
public static void main(String[] args) {
TicketThread3 thread1=new TicketThread3("窗口一");
TicketThread3 thread2=new TicketThread3("窗口二");
TicketThread3 thread3=new TicketThread3("窗口三");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
2.通过实现Runnable创建线程
2.1通过同步代码解决线程安全问题
//同步代码块实现
public class TicketThread1 implements Runnable {
int num=50;
@Override
public void run(){
Thread thread =Thread.currentThread();
while (num>0){
synchronized (this){
if(num>0){
System.out.println(thread.getName()+"当前票号"+num);
num--;
}
}
}
}
}
class TicketThread1Test{
public static void main(String[] args) {
TicketThread1 ticketThread1=new TicketThread1();//一个计票线程
Thread thread1=new Thread(ticketThread1,"窗口一");//3个卖票线程
Thread thread2=new Thread(ticketThread1,"窗口二");
Thread thread3=new Thread(ticketThread1,"窗口三");
//启动线程
thread1.start();
thread2.start();
thread3.start();
}
}
2.2通过同步方法解决线程安全问题
//同步方法
public class TicketThread2 implements Runnable {
int num=50;
@Override
public void run(){
Thread thread=Thread.currentThread();
while (num>0){
sale();
}
}
public synchronized void sale(){
Thread thread=Thread.currentThread();
while (num>0){
if(num>0){
System.out.println(thread.getName()+"当前票号"+num);
num--;
}
}
}
}
class TicketThread2Test{
public static void main(String[] args) {
TicketThread2 ticketThread2=new TicketThread2();//一个计票
Thread thread1=new Thread(ticketThread2,"窗口一");//3个卖票
Thread thread2=new Thread(ticketThread2,"窗口二");
Thread thread3=new Thread(ticketThread2,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
2.3通过加锁解决线程安全问题
//加锁
public class TicketThread3 implements Runnable {
int num=50;
Lock lock=new ReentrantLock();//新建锁
@Override
public void run(){
Thread thread=Thread.currentThread();//获取当前线程
while (num>0){
try {
lock.lock();
if(num>0){
System.out.println(thread.getName()+"当前票号"+num);
num--;
}try {
Thread.sleep(50);
}catch (Exception e){
System.out.println("产生了异常");
}
}finally {
lock.unlock();//解锁
}
}
}
}
class TicketThread3Test{
public static void main(String[] args) {
TicketThread3 ticketThread3=new TicketThread3();//1个计票
Thread thread1=new Thread(ticketThread3,"窗口一");
Thread thread2=new Thread(ticketThread3,"窗口二");
Thread thread3=new Thread(ticketThread3,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
运行结果:
根据笔者的面试经验来说,线程是在考察JAVA基础方面,非常容易被问到的一个点。后续的相关线程池,线程的调用这些知识点,也都十分容易被问到。如果面试过程中,自身提到了对线程了解比较多,那么基本上如何创建线程,两种创建线程的方式有什么区别,线程池的相关参数这些知识点肯定会被询问。笔者大中小厂均有过面试经验,每天分享全栈知识,与大家共同进步。