进程:进程就是应用程序在内存中分配的空间,也就是正在运行的程序,各个进程之间不干扰。同时进程保存着程序每一个时刻运行的状态。
程序:用某种编程语言(java、python等)编写,能够完成一定任务或者功能的代码集合,是指令和数据的有序集合,是一段静态代码。
线程:通常一个进程中可以包含若干个线程。进程单独占有一定的内存地址空间,而线程共享所属进程占有的内存地址空间和资源。
在Java中,我们是如何使用多线程的呢?
1.使用Thread 类和 Runnalble 接⼝来实现自己的“线程”类。
// 继承Thread类
public class MyThread1 extends Thread {
@Override
public void run() {
System.out.println("MyThread1*********");
}
}
// 实现Runnable接口
public class MyThread2 implements Runnable {
@Override
public void run() {
System.out.println("MyThread2@@@@@@@@@");
}
}
public class ThreadTest {
public static void main(String[] args) {
Thread myThread1 = new MyThread1();
myThread1.start();
Thread myThread2 = new Thread(new MyThread2());
myThread2.start();
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("MyThread3########");
}
}).start();
new Thread(() -> {
System.out.println("MyThread4%%%%%%%%");
}).start();
}
}
2.我们使用Runnable和Thread来创建一个新的线程。但是他们有一个弊端,就是run方法是没有返回值的。而有时候我们希望开启一个线程去执行一个任务,并且这个任务执行完成之后有一个返回值。JDK提供了Callable接口和Future类为我们解决了这个问题。
public class CallableTask implements Callable {
@Override
public String call() throws Exception {
Thread.sleep(1000);
return "中奖啦~";
}
public static void main(String args[]) throws ExecutionException, InterruptedException {
// 1.使用Future接口
// ExecutorService可以使用submit方法来让⼀个Callable接口执行。
ExecutorService executor1 = Executors.newCachedThreadPool();
CallableTask task = new CallableTask();
Future result = executor1.submit(task);
// 注意调用get方法会阻塞当前线程,直到得到结果。
// 所以实际编码中建议使用可以设置超时时间的重载get方法。
System.out.println(result.get() + "@@");
// 2.使用FutureTask类:FutureTask 是实现的 RunnableFuture 接口的,
// 而 RunnableFuture 接口同时继承了 Runnable 接口和 Future 接口。
ExecutorService executor2 = Executors.newCachedThreadPool();
FutureTask futureTask = new FutureTask<>(new CallableTask());
executor2.submit(futureTask);
System.out.println(futureTask.get() + "**");
}
}
什么是线程组和线程优先级?
1.线程组:每个Thread必然存在于⼀个ThreadGroup中,Thread不能独⽴于ThreadGroup存在。执⾏main()⽅法线程的名字是main,如果在new Thread时没有显式指定,那么默认将⽗线程(当前执⾏new Thread的线程)线程组设置为⾃⼰的线程组。
public class ThreadGroupTest {
public static void main(String[] args) {
Thread testThread1 = new Thread(() -> {
System.out.println("testThread1当前线程组名字:" +
Thread.currentThread().getThreadGroup().getName());
System.out.println("testThread1线程名字:" +
Thread.currentThread().getName());
});
Thread testThread2 = new Thread(() -> {
System.out.println("testThread2当前线程组名字:" +
Thread.currentThread().getThreadGroup().getName());
System.out.println("testThread2线程名字:" +
Thread.currentThread().getName());
});
testThread1.start();
testThread2.start();
System.out.println("执⾏main⽅法线程名字:" + Thread.currentThread().getName());
}
}
输出结果:
执⾏main⽅法线程名字:main
testThread2当前线程组名字:main
testThread2线程名字:Thread-1
testThread1当前线程组名字:main
testThread1线程名字:Thread-0
2.线程优先级:Java中线程优先级可以指定,范围是1~10 。但并不是所有的操作系统都支持10级优先级的划分,Java只是给操作系统一个优先级的参考值,线程最终在操作系统的优先级是多少还是由操作系统决定。通常情况下,⾼优先级的线程将会⽐低优先级的线程有更⾼的⼏率得到执⾏。
public static void main(String[] args) {
Thread a = new Thread();
System.out.println("我是默认线程优先级:"+a.getPriority());
Thread b = new Thread();
b.setPriority(10);
System.out.println("我是设置过的线程优先级:"+b.getPriority());
}
我是默认线程优先级:5
我是设置过的线程优先级:10
线程状态
线程状态转换图
锁与同步
无锁的时候,如下代码中两个线程A、B各自执行:
public class ThreadNoneLock {
static class ThreadA implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Thread A " + i);
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println("Thread B " + i);
}
}
}
public static void main(String[] args) {
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
}
}
Thread A 0
Thread A 1
Thread A 2
Thread B 0
Thread B 1
Thread B 2
Thread B 3
...
Thread B 97
Thread B 98
Thread B 99
Thread A 3
Thread A 4
Thread A 5
加锁之后(用synchronized关键字加上了同一个对象锁lock),A线程先执行完,B随后执行:
public class ThreadWithLock {
private static Object lock = new Object();
static class ThreadA implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
System.out.println("Thread A " + i);
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 100; i++) {
System.out.println("Thread B " + i);
}
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new ThreadA()).start();
Thread.sleep(10);
new Thread(new ThreadB()).start();
}
}
Thread A 97
Thread A 98
Thread A 99
Thread B 0
Thread B 1
Thread B 2
等待/通知机制
如下例子中,线程A和线程B首先打印出自己需要的东西,然后使用notify()方法叫醒另一个正在等待的线程,然后自己使用wait()方法陷入等待并释放lock锁。
public class WaitAndNotify {
private static Object lock = new Object();
static class ThreadA implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("Thread A: " + i);
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
synchronized (lock) {
for (int i = 0; i < 5; i++) {
try {
System.out.println("Thread B: " + i);
lock.notify();
lock.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
lock.notify();
}
}
}
public static void main(String[] args) throws InterruptedException {
new Thread(new ThreadA()).start();
Thread.sleep(1000);
new Thread(new ThreadB()).start();
}
}
Thread A: 0
Thread B: 0
Thread A: 1
Thread B: 1
Thread A: 2
Thread B: 2
Thread A: 3
Thread B: 3
Thread A: 4
Thread B: 4
信号量
public class Signal {
/**
* volatile关键字可以保证内存的可见性,如果用volatile关键字声明了一个变量,在一个线程里改变了这个变量值,那其他线程是立马可见更改后的值的。
*/
private static volatile int i = 0;
static class ThreadA implements Runnable {
@Override
public void run() {
while (i < 10) {
if (i % 2 == 0) {
System.out.println("Thread A: "+ i);
synchronized (this) {
i++;
}
}
}
}
}
static class ThreadB implements Runnable {
@Override
public void run() {
while (i < 10) {
if (i % 2 == 1) {
System.out.println("Thread B: " + i);
synchronized (this) {
i++;
}
}
}
}
}
public static void main(String[] args) {
new Thread(new ThreadA()).start();
new Thread(new ThreadB()).start();
}
}
Thread A: 0
Thread B: 1
Thread A: 2
Thread B: 3
Thread A: 4
Thread B: 5
Thread A: 6
Thread B: 7
Thread A: 8
Thread B: 9
ThreadLocal
多线程访问同一个共享变量的时候容易出现并发问题,特别是多个线程对同一个变量进行写入的时候,为了保证线程安全,一般使用者在访问共享变量的时候需要进行额外的同步措施才能保证线程安全性。TreadLocal是除了加锁这种同步方式之外的一种保证规避多线程访问出现线程不安全的方法,当我们在创建一个变量后,如果每个线程对其进行访问的时候访问的都是线程自己的变量这样就不会存在线程不安全的问题了。
线程池
为什么要使用线程池:1.创建/销毁线程需要消耗系统资源,线程池可以复用已创建的线程。2.控制并发的数量。并发数量过多,可能会导致资源消耗过多,从而造成服务器崩溃。3.可以对线程做统一管理。
ThreadPoolExecutor
public class ThreadPoolExecutorTest {
public static void main(String[] args) {
// ThreadPoolExecutor提供了四个构造函数: 1至5; 1至5 + 6; 1至5 + 7; 1至5 + 6 + 7
// 参数一:int corePoolSize 该线程池中核心线程数最大值,线程池新建的时候,如果当前线程总数小于corePoolSize,则新建核心线程,如果超过corePoolSize,则新建的是非核心线程
// 参数二:int maximumPoolSize 该线程池中线程总数最大值
// 参数三:long keepAliveTime 该线程池中非核心线程闲置超时时长
// 参数四:TimeUnit unit keepAliveTime的单位
// 参数五:BlockingQueue workQueue 该线程池中的任务队列,维护着等待执行的Runnable对象。常见类型:
// SynchronousQueue:这个队列接收到任务的时候,会直接提交给线程处理,而不保留它,如果所有线程都在工作怎么办?那就新建一个线程来处理这个任务!所以为了保证不出现的错误,使用这个类型队列的时候,maximumPoolSize一般指定成Integer.MAX_VALUE,即无限大
// LinkedBlockingQueue:这个队列接收到任务的时候,如果当前线程数小于核心线程数,则新建线程(核心线程)处理任务;如果当前线程数等于核心线程数,则进入队列等待。由于这个队列没有最大值限制,即所有超过核心线程数的任务都将被添加到队列中,这也就导致了maximumPoolSize的设定失效,因为总线程数永远不会超过corePoolSize
// ArrayBlockingQueue:可以限定队列的长度,接收到任务的时候,如果没有达到corePoolSize的值,则新建线程(核心线程)执行任务,如果达到了,则入队等候,如果队列已满,则新建线程(非核心线程)执行任务,又如果总线程数到了maximumPoolSize,并且队列也满了,则发生错误
// DelayQueue:队列内元素必须实现Delayed接口,这就意味着你传进去的任务必须先实现Delayed接口。这个队列接收到任务时,首先先入队,只有达到了指定的延时时间,才会执行任务
// 参数六:ThreadFactory threadFactory 给线程起名字
// 参数七:RejectedExecutionHandler handler 异常处理
ThreadFactory threadFactory = new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
return new Thread(r,"thread name...");
}
};
// lambda表达式写法
// ThreadFactory threadFactory = (Runnable r) -> new Thread(r,"thread name");
ThreadPoolExecutor executor = new ThreadPoolExecutor(5,5,0L, TimeUnit.SECONDS, new LinkedBlockingDeque<>(), threadFactory, new ThreadPoolExecutor.AbortPolicy());
executor.execute(new Runnable() {
@Override
public void run() {
System.out.println("@@@@");
}
});
}
}