Java学习之线程

线程

目录

  1. 什么是线程
  2. 线程的使用
  3. 线程的同步
  4. 线程常用方法

随着时代的发展,软件的要求越来越高,软件或者说网站并发量越来越高(并发指一个时间段内存在大量访问软件的用户),例如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方法时,

  1. 首先进入到sleap睡眠,停了0.1秒,那这个时候cpu开始执行线程2,线程2进入开始休眠,线程3进入开始休眠;
  2. 这时线程1睡醒了,所以这个时候线程1开始执行剩下代码,为a减1,但是注意,a的运算方式是先计算a-1,然后重新为a赋值;
  3. 正常情况下,a-1变成99,然后线程2调用,但是现实情况可能是a减完1后还没有赋值;
  4. 这个时候线程2醒了,线程2执行代码,这个时候a减去了1,但是并没有重新赋值,所以这个时候a还是等于100;
  5. 这里注意这只是一个极端情况,但是我们不能保证这种情况必然不会出现,所以我们需要对这些问题进行处理。

解决办法:使用synchronized为统一执行的代码加锁。synchronized 同步的,语法如下

synchronized(对象名){
	同步内容
}

这里的对象名指的是需要传入一个对象,注意是一个,这个对象就是一个锁,这个对象可以是任意类型,注意是只有一个就可以了,这个对象就是起到一个锁的作用,

  1. 线程1进入run方法,拿到这个锁,然后锁住这个代码块,不让其他线程通过,
  2. 线程2进入到run方法,发现锁住,但这个时候线程1还没有执行完成,这个锁还在线程1中,所以线程2无法进入执行方法;
  3. 线程2等候,线程1结束运算,全局变量-1操作完成,把锁还了过来,这时候线程2拿到锁,开始执行代码,知道执行完成把锁释放,这个期间只有线程2可以操作这块代码
  4. 最后运行结束

实现代码如下:

	@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--));
				}
			}
		}
	}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值