线程
目录
- 什么是线程
- 线程的使用
- 线程的同步
- 线程常用方法
随着时代的发展,软件的要求越来越高,软件或者说网站并发量越来越高(并发指一个时间段内存在大量访问软件的用户),例如12306或者刚过去不久的双十一,这是要求服务器充分利用CPU资源,而线程主要就是用于此。
1. 什么是线程
在说线程之前,需要先了解进程,什么是进程,你可以打开自己的任务管理器,上面有进程选项。下图就是你电脑上打开的进程,这些都是你打开的软件,进程就是你打开软件进行活动,有系统分配的资源和调度的基本单位,我们可以先简单理解为自己打开的软件,我们计算机中可以打开多个软件,这样的或我们就可以同时玩游戏,听歌,聊QQ。
多进程简单点说计算机可以同时运行多个软件。
而线程和进程之间的关系与麻绳和小股绳子的关系一样,线程是进程的一部分,进程中包含多个线程运行,
线程是进程重要组成部分,线程是进程在运行时执行的多个小任务,多线程指的就是运行多个线程在进程内部,带来的好处在于提高应用程序使用率,程序中更多路径使用CPU资源,那么程序会得到更多的cpu执行权,比如说ABC三人抢绣球,A有三百个手下帮忙抢,那么A得到的几率就更大。
总结下进程就是一个大型任务一样,由CPU去调用执行,而线程就像其中的小任务一样,cpu去执行一个个小任务,以此得到CPU资源,充分利用CPU。
在线程没有出现之前,Java代码是这样运行的(红色为执行路线)
在使用线程后
2. 线程的使用
常用的有两种,第一种继承Thread类,重写run方法
public class Thread01 extends Thread {
@Override
public void run() {
for(int a=0;a<100;a++){
System.out.println("这里是线程,"+a+"次");
}
}
}
测试类
public class Main {
public static void main(String[] args) {
Thread01 t = new Thread01();
t.start();
for(int a=0;a<100;a++){
System.out.println("这里是主线程,"+a+"次");
}
}
}
结果
首先创建一个类继承Thread类,然后重写run方法,这个run方法就是线程中主要执行的代码,这里需要注意这个run方法中可以调用其他的类和其他方法,可以和其他功能结合使用。
使用线程的的话需要创建这个类的对象,然后调用start()方法,当使用new创建Thread01对象时,线程就处于一个新建状态,但这个时候还没有启动,当调用start方法,线程进入就绪状态,站在就绪队列,等待调度,然后等待CPU资源的宠幸,获得资源后执行run()方法,但是注意,CPU不是一致执行这一个线程,而是分配给线程一个时间段,在这个时间段中去执行,所以大家可以看到打印结果是一会线程,一会儿主线程,而且次序是随机的。流程图大至如下
这里注意线程的各种状态。
由于在Java中属于单继承,当类继承Thread类后不能再继承其他类,造成使用的不便,所以最好使用第二种方式,实现Runnable,然后实现run方法
public class ThreadTest implements Runnable{
@Override
public void run() {
for(int a=0;a<100;a++){
System.out.println("这里是线程,"+a+"次");
}
}
}
使用的话需要注意,首先需要实例化线程类,然后创建Thread类传入对象,最后调用start方法
public class Main {
public static void main(String[] args) {
ThreadTest t = new ThreadTest(); // 实例化ThreadTest类
Thread t1 = new Thread(t); // 创建Thread类,调用ThreadTest
t1.start();
}
}
3. 线程的同步
当当前环境属于多线程时,多个线程同一访问一个资源并进行修改,这个时候容易造成资源重复修改并出现数据错误,例如下面代码:
ThreadTest定义一个变量a,然后在run方法中每次休眠0.1秒,然后修改a自减1
public class ThreadTest implements Runnable{
public int a = 100;
Object o = new Object();
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
while(a>0){
System.out.println(Thread.currentThread().getName()+"当前剩余"+(a--));
}
}
}
然后在测试类中启动三个线程执行
public class Main {
public static void main(String[] args) {
ThreadTest t = new ThreadTest();
Thread t1 = new Thread(t, "一");
Thread t2 = new Thread(t, "二");
Thread t3 = new Thread(t, "三");
t1.start();
t2.start();
t3.start();
}
}
结果运行如下
结果中有线程三,二,一都输出100(线程是随机执行的,每次结果不一定一样,如果有同时出现的数字,意味错误),那这样的话这个代码就是错误的,举个例子比如说这个变量a指的是100张电影票,每个线程是卖票窗口,三个窗口不可能同时卖出编号为100的票。
为什么出现这种问题呢?
当线程1开始执行run方法时,
- 首先进入到sleap睡眠,停了0.1秒,那这个时候cpu开始执行线程2,线程2进入开始休眠,线程3进入开始休眠;
- 这时线程1睡醒了,所以这个时候线程1开始执行剩下代码,为a减1,但是注意,a的运算方式是先计算a-1,然后重新为a赋值;
- 正常情况下,a-1变成99,然后线程2调用,但是现实情况可能是a减完1后还没有赋值;
- 这个时候线程2醒了,线程2执行代码,这个时候a减去了1,但是并没有重新赋值,所以这个时候a还是等于100;
- 这里注意这只是一个极端情况,但是我们不能保证这种情况必然不会出现,所以我们需要对这些问题进行处理。
解决办法:使用synchronized为统一执行的代码加锁。synchronized 同步的,语法如下
synchronized(对象名){
同步内容
}
这里的对象名指的是需要传入一个对象,注意是一个,这个对象就是一个锁,这个对象可以是任意类型,注意是只有一个就可以了,这个对象就是起到一个锁的作用,
- 线程1进入run方法,拿到这个锁,然后锁住这个代码块,不让其他线程通过,
- 线程2进入到run方法,发现锁住,但这个时候线程1还没有执行完成,这个锁还在线程1中,所以线程2无法进入执行方法;
- 线程2等候,线程1结束运算,全局变量-1操作完成,把锁还了过来,这时候线程2拿到锁,开始执行代码,知道执行完成把锁释放,这个期间只有线程2可以操作这块代码
- 最后运行结束
实现代码如下:
@Override
public void run() {
while(true){
synchronized (o) {
if(a>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()
+"当前剩余"+(a--));
}
}
}
}