0x00 前言
在一个工具开发中,如果该工具需要不断的去执行同一个动作或者是请求的话,使用单线程是非常慢。还是拿一个目录扫描器来举例子,比如我们需要开发一个目录扫描器,我们的字典里有10000个字典,,只有一个线程去发起http的请求,这样的速度是非常慢的,但是如果我们用到多线程,4个线程,每个线程请求2500个字典,那么我们花费的时间就可以缩短4倍。
0x01 多线程概念
线程与进程
在了解多线程前,首先要清楚多线程和多进程的区别。
进程:一个程序运行在一个指定内存块当中。一个程序可以同时运行多个进程。
线程:线程是进程里面的一个执行单元,负责当前程序的执行,一个进程里面至少有一个线程。
一个程序运行运行,至少有一个进程,而一个进程可以开多个线程。在我们开发中,一般是使用多线程来开发。
线程调度
分时调度:使用线程轮流使用cpu使用权,平均分配每个线程占用cpu时间。
抢占式调度:有限让优先级高的线程使用cpu,,如果线程的优先级同等,就会随机选择一个线程进行执行,java当中默认是抢占式线程调度。
0x02 多线程编程
在java里面可以使用Thread创建多线程程序,所有的线程对象必须是Thread类或者是其子类的实例。
首先先来看看他的构造方法:
public Thread() :分配一个新的线程对象。
public Thread(String name) :分配一个指定名字的新的线程对象。
public Thread(Runnable target) :分配一个带有指定目标新的线程对象。
public Thread(Runnable target,String name) :分配一个带有指定目标新的线程对象并指定名字
使用方式:
1.定义一个子类继承Thread,然后对run方法进行重写,run方法的方法体代表线程需要完成的任务。
2.实例化该子类对象,然后调用start方法
public class Demo11 extends Thread{
@Override
public void run() {
System.out.println("我是子线程");
}
}
创建thread的子类,并对run方法进行重写。
public static void main(String[] args) {
Demo11 thread = new Demo11();
thread.start();
System.out.println("我是主线程");
}
实例化实现类对象,然后使用start进行子线程的启动。
Runnable创建多线程
通过实现Runable接口,使该类有了多线程的特征。run()方法里面是多线程里面需要执行的代码。Thread实际上也是实现了runnable接口里面的类。
在所有多线程里面其实都是Thread这个类来操控线程。
使用步骤:
首先创建一个Runnable的实现类,然后重写run方法,使用Thread类实例化对象,将runnable实例化的对象传递进去,使用thread的实例化对象调用start方法进行创建线程。
代码:
runnable实现类:
public class Demo11 implements Runnable{
@Override
public void run() {
System.out.println("我是子线程");
}
}
main方法代码:
public static void main(String[] args) {
Demo11 run = new Demo11();
Thread thread = new Thread(run);
thread.start();
System.out.println("我是主线程");
}
这两种方式的都可以实现多线程,但是如果使用第一种方法,,不适合资源的共享,而runable这种方式的话,容易实现资源的共享。
Runnable接口的优势:
1. 适合多个相同的程序代码的线程去共享同一个资源。
2. 可以避免java中的单继承的局限性。
3. 增加程序的健壮性,实现解耦操作,代码可以被多个线程共享,代码和线程独立。
4. 线程池只能放入实现Runable或Callable类线程,不能直接放入继承Thread的类。
匿名内部类创建多线程
我们在每次创建线程,需要创建实现类,然后在实例化实现类,再对其进行调用。而一个thread的strart方法只能调用一次,那么我们就可以使用匿名内部类对他进行定义。
public static void main(String[] args) {
Runnable r = new Runnable() {
@Override
public void run() {
System.out.println("我是子线程");
}
};
new Thread(r).start();
System.out.println("我是主线程");
}
0x03 线程安全
如果有多个线程同时在运行,并且是对某一个变量进行操作的话,那么这时候就会产生一些新的问题。
咱们还是拿一个目录扫描器来举例子,如果我们的字典里面有100个字典,需要开多个线程去扫描,那么这时候我们,如果直接让他去发起请求,那么这几个线程读取的都是同样的一个字典并且读取了5次,达不到多线程起到分工合作的一个效果。
那么这时候我们就可以使用到同步机制或者说线程锁解决掉这个问题。
同步代码块
同步代码块:synchronized关键字可以用于方法中的某个区块中,表示对这个区块的资源实行互斥。
格式:
synchronized(同步锁){
需要同步操作的代码
}
同步锁其实也只是一个概念,在对象上标记一个锁。
锁的对象可以是任意类型,但个线程对象要使用同一把锁
实现接口:
package cn.itcast;
public class Demo11 implements Runnable{
private int num = 100;
private boolean flag = true;
Object obj = new Object();
@Override
public void run() {
while (flag){
synchronized (obj){
if (num==0){
flag = false;
}else {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是子线程"+Thread.currentThread()+"数值"+num--);
}
}
}
}
}
main方法:
public class DemoMain {
public static void main(String[] args) {
Demo11 r = new Demo11();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
Lock同步锁
Lock机制比synchronized代码块方法更广泛。
常用方法:
public void lock() : 获取锁
public void unlock(): 释放锁
代码实例:
package cn.itcast;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Demo11 implements Runnable {
private int num = 100;
private boolean flag = true;
Lock lock = new ReentrantLock();
@Override
public void run() {
while (flag) {
lock.lock();
if (num == 0) {
flag = false;
} else {
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("我是子线程" + Thread.currentThread() + "数值" + num--);
}
lock.unlock();
}
}
}
mian方法:
public class DemoMain {
public static void main(String[] args) {
Demo11 r = new Demo11();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
0x04 结尾
多线程会在我们开发工具常用到,一些工具需要高并发提高效率,不然效率很低。后面也可以开发一些可以自定义线程的程序,用户输入多少个线程,可以直接使用匿名内部类new 多少个thread对象,这样就直接实现了。