多线程——个人的理解和认识
目录
1、基本概念
2、实现方法
继承Thread类
实现Runnable接口
实现Callable接口
Thread类的方法
3、Thread类及其中常用的方法
4、线程的安全问题及解决办法
同步代码块、同步方法:synchronized
锁:显示锁、公平锁及非公平锁
5、线程的六种状态
6、线程池
基本的概念
谈及线程,首先想到的应是进程。而进程很好理解,就是我们平时所用到的APP,我们使用到的一个个软件,具有自己独立的内存空间。而线程,可以说是进程进一步细化后的单位,它们是进程中的多条执行路径。一个进程中通常含有多条正在执行的线程。
多线程的实现方法
在谈到方法之前,必须说清楚线程的使用离不开两个:一个是任务对象,一个是线程(对象)。前者由我们编写清楚,将待执行的方法写在任务类中;后者则是建立一个个线程(对象)出来,然后将任务对象传给一个个线程,让线程访问任务类去执行方法。
实现方法主要是三种:一是编写一个任务类后,继承Thread类;二是令任务类实现Runnable接口;三是任务类实现Callable接口。
继承Thread类
public class TEXTextendsThread {
public static void main(String[] args) {
/* 创建任务对象mt1、mt2-继承Thread*/
MyThread mt1 = new MyThread();
new Thread(mt1).start();
new Thread(mt1).start();
}
public static class MyThread extends Thread{
//重写run方法
public void run() {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"尝试:extends Thread类");
}
}
}
结果为:
Thread-1尝试:extends Thread类
Thread-2尝试:extends Thread类
实现Runnable接口
public class TEXTimplementsRunnable {
public static void main(String[] args) {
MYthread myt = new MYthread();
/*格式1*/
Thread t = new Thread(myt);
t.start();
/*格式2*/
new Thread(new MYthread()).start();
for (int i=0;i<4;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 我的名字 "+i);
}/**/
}
public static class MYthread implements Runnable{
@Override
public void run() {
for (int i=0;i<4;i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+" 叫大炮 "+i);
}
}
}
}
结果为:
Thread-0 叫大炮 0
main 我的名字 0
Thread-1 叫大炮 0
Thread-1 叫大炮 1
Thread-0 叫大炮 1
main 我的名字 1
Thread-0 叫大炮 2
Thread-1 叫大炮 2
main 我的名字 2
Thread-1 叫大炮 3
Thread-0 叫大炮 3
main 我的名字 3
实现Callable接口
这个接口比较特殊,情况如下:
1.可以分清主次线程,主线程接收到子线程的返回值后才会执行;
2.无法实现多个子线程,因为主线程一旦收到返回值就会执行,所以情况是如果创建了多个子线程,只有一个抢到时间片的可以执行,然后就轮到主线程执行,然后结束。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class TEXTCallable {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> myCallable = new MyCallable();
FutureTask futureTask = new FutureTask(myCallable);
new Thread(futureTask).start();
new Thread(futureTask).start();
System.out.println("返回值为: "+futureTask.get());
int i = 5;
while (i > 0){
i--;
System.out.println(Thread.currentThread().getName()+" 输出 "+i);
}
}
public static class MyCallable implements Callable{
@Override
public Object call() throws Exception {
int i = 5;
while (i > 0){
i--;
System.out.println(Thread.currentThread().getName()+" 输出 "+i);
}
return 10;
}
}
}
结果为:
Thread-1 输出 4
Thread-1 输出 3
Thread-1 输出 2
Thread-1 输出 1
Thread-1 输出 0
返回值为: 10
main 输出 4
main 输出 3
main 输出 2
main 输出 1
main 输出 0
从 Thread 类的定义可以清楚的发现,Thread 类也是 Runnable 接口的子类,但在Thread类中并没有完全实现 Runnable 接口中的 run() 方法,下面是 Thread 类的部分定义。
继承Thread类和实现Runnable接口完成多线程任务的区别
Private Runnable target;
public Thread(Runnable target,String name){
init(null,target,name,0);
}
private void init(ThreadGroup g,Runnable target,String name,long stackSize){
...
this.target=target;
}
public void run(){
if(target!=null){
target.run();
}
}
Thread类
Thread类提供了较多的方法供我们使用,其中使用较多的有:
getName():获取当前运行的线程的名称
线程的中断:
是用线程中断自己,用待中断的线程调用interrupt方法即可。
public class TextInterruptDemo {
public static class ThreadInterruptDemo{
/**
* 两者都设置了sleep,从而让两者执行快慢不同,
* */
public static void main(String args[]){
MyThread mt = new MyThread(); // 实例化Runnable子类对象
Thread t1 = new Thread(mt,"t1-线程");
Thread t2 = new Thread(mt,"t2-线程");
// 实例化Thread对象
t1.start();
t2.start();
try{
Thread.sleep(100); // 线程休眠2秒
}catch(InterruptedException e){
System.out.println(Thread.currentThread().getName()+" 3、被唤醒");
}
t2.interrupt();// 中断线程执行
}
};
static class MyThread implements Runnable{ // 实现Runnable接口
public void run(){ // 重写run()方法
System.out.println("1. "+Thread.currentThread().getName()+" 已经进入run方法");
try{
Thread.sleep(500); // 线程休眠1秒
System.out.println("2. "+Thread.currentThread().getName()+" 已经睡醒");
}catch(InterruptedException e){
System.out.println("3. "+Thread.currentThread().getName()+" 休眠被终止");
return;
}
System.out.println("4. "+Thread.currentThread().getName()+" run方法正常结束,未被打断");
}
};
}
结果为:
t2-线程 已经进入run方法
t1-线程 已经进入run方法
t2-线程 休眠被终止
t1-线程 已经睡醒
t1-线程 run方法正常结束,未被打断
5.程序安全问题
当多个线程接收到了同一个任务对象时,他们都可“解开”访问其中数据的“锁”,从而会调用到同一个数据,最终造成数据使用的不安全,例如:
public class TEXTThreadSafety {
public static void main(String[] args) {
TRY t = new TRY();
/*多个线程调用同一任务对象*/
new Thread(t,"线程-1").start();
new Thread(t,"线程-2").start();
}
public static class TRY implements Runnable{
int count = 6;
@Override
public void run() {
while (true){
if (count > 0) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}count--;
System.out.println(Thread.currentThread().getName() + " 输出 " + count);
}else {
break;
}
}
}
}
}
结果为:
线程-1 输出 5
线程-2 输出 5
线程-1 输出 3
线程-2 输出 4
线程-1 输出 1
线程-2 输出 1
线程-1 输出 -1
线程-2 输出 -1
出现了-1的情况,而这完全不符合前面的if语句中的判断,正常是不可能出现的。而原因正在于两个线程接受到了同一任务对象t,因而两者都对int类型的count数值有改动的能力,而我又在if语句内的开头写了休眠1s从而让出错的几率变得更大:先抢到时间片的线程先进去,进去后就休眠,从而没有改动count所以此时的两个线程拿到的count值是一样的,所以一个能进去的话,另一个也可以进去,所以在前者完成休眠后,假设其此时的count = 0,并输出了结果显示count = 0,然后执行了count–的操作,此时count = -1了,则后者输出结果便是-1,从而出现了与i不符合f语句但又输出了的情况。
原因总结:没有排队,抢到时间片的前者进去后休眠了,此时抢到时间片的后者便紧随其后进入了if语句中,从而出错。
解决方法:另其排队!同步代码块、同步方法、上锁—显示锁
同步代码块和同步方法更像是隐式锁,而后者则为显示锁,有专门的锁对象。
同步代码块、同步方法:synchronized
前者现创建一个Object类型的锁对象,传入synchronized中,再给欲要排队的代码块上“锁”;后者是直接给欲要排队的方法修饰上关键词synchronized。
同步代码块
public class TEXTThreadSafety {
public static void main(String[] args) {
TRY t = new TRY();
/*多个线程调用同一任务对象*/
new Thread(t,"线程-1").start();
new Thread(t,"线程-2").start();
}
public static class TRY implements Runnable{
private int count = 6;
@Override
public void run() {
while (true){
synchronized (this){
if (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}count--;
System.out.println(Thread.currentThread().getName() + " 输出 " + count);
}else {
break;
}
}
}
}
}
}
结果为:
线程-1 输出 5
线程-1 输出 4
线程-1 输出 3
线程-1 输出 2
线程-1 输出 1
线程-1 输出 0
让线程-1抢到了时间片,然后执行完一次后就“回首掏”,从而后面每次抢到的几率都更大。
同步方法
这个更加简单,直接给待排队的方法加上一个synchronized修饰就好
public class TEXTThreadSafety {
public static void main(String[] args) {
TRY t = new TRY();
/*多个线程调用同一任务对象*/
new Thread(t,"线程-1").start();
new Thread(t,"线程-2").start();
new Thread(t).start();
}
public static class TRY implements Runnable{
private int count = 6;
@Override
public synchronized void run() {
while (true){
if (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}count--;
System.out.println(Thread.currentThread().getName() + " 输出 " + count);
}else {
break;
}
}
}
}
}
结果为:
线程-1 输出 5
线程-1 输出 4
线程-1 输出 3
线程-1 输出 2
线程-1 输出 1
线程-1 输出 0
显式锁
在任务类的属性创建时创建ReetrantLock类的锁对象(不是在方法体里创建!),然后在待锁代码块前面通过锁对象调用lock()方法以上锁,在代码块结束位置再用锁对象调用unlock()方法以解锁。
public class TEXTThreadSafety {
public static void main(String[] args) {
TRY t = new TRY();
new Thread(t,"线程-1").start();
new Thread(t,"线程-2").start();
}
public static class TRY implements Runnable{
private int count = 6;
Lock reentrantLock = new ReentrantLock();
@Override
public void run() {
reentrantLock.lock();
while (true){
if (count > 0) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}count--;
System.out.println(Thread.currentThread().getName() + " 输出 " + count);
}else {
break;
}
}
reentrantLock.unlock();
}
}
}
结果为:
线程-1 输出 5
线程-1 输出 4
线程-1 输出 3
线程-1 输出 2
线程-1 输出 1
线程-1 输出 0
公平锁和非公平锁
公平锁:先来先用,排队机制。
非公平锁:一块上来抢,不用排队,谁抢到谁先执行。
也是显式锁,只不过在创建锁对象时通过传入Boolean类的值true或false来区分上的是公平锁还是非公平锁。
5、线程的六种状态
在这里插入图片描述
6、线程池
顾名思义,线程池是一个容纳多个线程的容器。而之所以要有这个线程池,是因为如果每创建一个线程就只执行一个任务然后废除,会极大地消耗资源,而线程池正是容量定量或是不定量的一些线程,反复用于执行各个任务,节省资源的同时又可以较高效率完成各项任务。
种类
根据是否定量,线程池可分为两大类:一是定长类,包括定长线程池、周期性定长线程池和单线程线程池;类一定是非定长类,可以根据实情增加或释放内存。