线程和进程:
1.定义
进程:具有一定独立功能的程序关于某个数据集合上的一次运行活动,进程是系统进行资源分配和调度的一个独立单位.
线程:进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位.线程自己基本上不拥有系统资源,只拥有一点在运行中必不可少的资源(如程序计数器,一组寄存器和栈),但是它可与同属一个进程的其他的线程共享进程所拥有的全部资源.
2.关系
一个线程可以创建和撤销另一个线程;同一个进程中的多个线程之间可以并发执行.
相对进程而言,线程是一个更加接近于执行体的概念,它可以与同进程中的其他线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
3.区别
进程和线程的主要差别在于它们是不同的操作系统资源管理方式。进程有独立的地址空间,一个进程崩溃后,在保护模式下不会对其它进程产生影响,而线程只是一个进程中的不同执行路径。线程有自己的堆栈和局部变量,但线程之间没有单独的地址空间,一个线程死掉就等于整个进程死掉,所以多进程的程序要比多线程的程序健壮,但在进程切换时,耗费资源较大,效率要差一些。但对于一些要求同时进行并且又要共享某些变量的并发操作,只能用线程,不能用进程。
-
简而言之,一个程序至少有一个进程,一个进程至少有一个线程.
-
线程的划分尺度小于进程,使得多线程程序的并发性高。
-
另外,进程在执行过程中拥有独立的内存单元,而多个线程共享内存,从而极大地提高了程序的运行效率。
-
线程在执行过程中与进程还是有区别的。每个独立的线程有一个程序运行的入口、顺序执行序列和程序的出口。但是线程不能够独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制。
-
从逻辑角度来看,多线程的意义在于一个应用程序中,有多个执行部分可以同时执行。但操作系统并没有将多个线程看做多个独立的应用,来实现进程的调度和管理以及资源分配。这就是进程和线程的重要区别。
4.优缺点
线程和进程在使用上各有优缺点:
线程执行开销小,但不利于资源的管理和保护;
而进程正相反。
同时,线程适合于在SMP机器(SMP的全称是"对称多处理"技术,是指在一个计算机上汇集了一组处理器,各CPU之间共享内存子系统以及总线结构。)上运行,而进程则可以跨机器迁移。
多线程的作用:
1.单核的电脑都是伪多线程,只有多核cpu才是真正的多线程
2.多线程可以防止阻塞,某个线程挂了,不会影响其他线程的运行
3.多线程可以拆分单线程的业务逻辑,可以简化开发。
多线程缺点:
1.确保数据准确性困难
2.资源消耗多
创建及使用线程的三种方法:
1.继承Thread
调用最简单,直接新建线程,调用start()方法即可package com.monkey1024.thread; /** * 多线程实现的第一种方法,继承Thread * */ //1.自定义一个类,继承java.lang包下的Thread类 class MyThread extends Thread { //2.重写run方法 public void run() { //3.将要在线程中执行的代码编写在run方法中 for(int i = 0; i < 1000; i++) { System.out.println("monkey"); } } } public class ThreadTest01 extends Thread { public static void main(String[] args) { //4.创建上面自定义类的对象 //创建一个线程 MyThread mt = new MyThread(); //5.调用start方法启动线程 mt.start(); //将循环的次数设置多一些,否则还没等到CPU切换就已经打印完成了 for(int i = 0; i < 1000; i++) { System.out.println("1024"); } } }
2.实现runnalbe接口
需要新建对象,将对象交给线程,由线程按照对象run()方法运行,由线程调用start()方法执行
package com.monkey1024.thread;
/**
* 多线程实现的第二种方法,实现Runnable接口
*
*/
// 1.自定义一个类实现java.lang包下的Runnable接口
class MyRunnable implements Runnable {
// 2.重写run方法
@Override
public void run() {
// 3.将要在线程中执行的代码编写在run方法中
for (int i = 0; i < 1000; i++) {
System.out.println("monkey");
}
}
}
public class ThreadTest02 {
public static void main(String[] args) {
// 4.创建上面自定义类的对象
MyRunnable mr = new MyRunnable();
// 5.创建Thread对象并将上面自定义类的对象作为参数传递给Thread的构造方法
Thread t = new Thread(mr);
//6.调用start方法启动线程
t.start();
for (int i = 0; i < 1000; i++) {
System.out.println("1024");
}
}
}
3.实现callable接口
需要新建线程池,使用线程池提交对象运行,运行结果直接封装成Future<>对象,可以使用Future<>对象的get方法获取运行结果
package com.monkey1024.thread;
import java.util.ArrayList;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/**
* 多线程实现的第三种方法,实现Callable接口 优点: 可以获取返回值 可以抛出异常
*/
//1.定义一个类实现Callable<V>接口
class MyCallable implements Callable<Integer> {
//睡眠时间
private long second;
//计算阶乘
private int count;
public MyCallable(int count, long second) {
this.count = count;
this.second = second;
}
// 2.重写call方法
@Override
public Integer call() throws Exception {
// 3.将要执行的代码写在call方法中
// 计算阶乘
//让当前线程睡眠,单位是毫秒
Thread.sleep(second);
int sum = 1;
if (count == 0) {
sum = 0;
} else {
for (int i = 1; i <= count; i++) {
sum *= i;
}
}
// 打印线程名称
System.out.println(Thread.currentThread().getName() + "-----sum=" + sum);
return sum;
}
public int getCount() {
return count;
}
public void setCount(int count) {
this.count = count;
}
}
public class ThreadTest03 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
//4.创建ExecutorService线程池
ExecutorService exec = Executors.newCachedThreadPool();
//5.创建存储Future对象的集合,用来存放ExecutorService的执行结果
ArrayList<Future<Integer>> results = new ArrayList<Future<Integer>>();
//6.开启2个线程,将返回的Future对象放入集合中
for (int i = 0; i < 2; i++) {
if (i == 0) {
//计算5的阶乘,睡眠10秒
//callable线程的使用方法是直接提交线程池,然后可以直接获取运行结果并封装在Future<T>中
results.add(exec.submit(new MyCallable(5, 10000)));
} else {
//计算3的阶乘,睡眠i秒
results.add(exec.submit(new MyCallable(3, i)));
}
}
for (Future<Integer> fs : results) {
//7.判断线程是否执行结束,如果执行结束就将结果打印
if (fs.isDone()) {
System.out.println("计算结果:" + fs.get());
} else {
System.out.println(fs.toString() + "该线程还没有计算完毕,请耐心等待");
}
}
//8.关闭线程池,不再接收新的线程,未执行完的线程不会被关闭
exec.shutdown();
System.out.println("main方法执行结束");
}
}
4.三种创建方式的比较
继承Thread
优点:可以直接使用Thread类中的方法,代码简单
缺点:继承Thread类之后就不能继承其他的类
实现Runnable接口
优点:即时自定义类已经有父类了也不受影响,因为可以实现多个接口
缺点: 在run方法内部需要获取到当前线程的Thread对象后才能使用Thread中的方法
实现Callable接口
优点:可以获取返回值,可以抛出异常
缺点:代码编写较为复杂
线程相关方法:
通过Thread对象的setName()方法给线程设置名字
通过Thread对象的getName()方法获取线程的名字
Thread.currentThread()方法获取当前正在执行的线程
sleep(long millis),线程睡眠多少毫秒,不会释放锁,可以使用notify唤醒
setPriority(int) 通过参数指定线程的优先级,取值范围是整数1~10,优先级随着数字的增大而增强。
优先级最低:public final static int MIN_PRIORITY = 1;
优先级居中:public final static int NORM_PRIORITY = 5;
优先级最高:public final static int MAX_PRIORITY = 10;
线程安全产生的原因:
多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改,值不同步的情况,进而影响程序的执行流程。它包括原子性和可见性两个方面
线程安全的解决方法:
-
synchronized同步锁,原子性
synchronized能保证数据的原子性,也可以间接保证可见性,它会将私有内存和公共内存中的数据同步,但是执行效率低。
方法加锁,性能低public synchronized void changeNum(boolean flag)
同步代码块,给关键对象加锁,细粒度的加锁,性能提高
synchronized锁住当前对象的写法:
public synchronized void a(){
}
public void ab(){
synchronized (this){
}
}
synchronized锁住当前类的写法:
public synchronized static void a(){
}
public static void a(){
synchronized (类名.class){
}
}
public void ab(){
synchronized (类名.class){
}
}
2.volatile关键字,非原子性
volatile的作用是可以保持共享变量的可见性,即一个线程修改一个共享变量后,另一个线程能够读取到这个修改后的值。线程运行时有一个公共内存、还有一个私有内存,加上volatile关键字就会让线程强制从公共内存读取数据,防止jvm运行方式为-server时只读私有内存。