应用程序
应用程序就是可执行的软件,如,微信。
进程
进程:进程是执行中的程序,是多个线程的集合,一个操作系统可以有多个进程,每个进程可以有多条执行路径。在进程中一定有一个主线程可以控制代码执行顺序
线程
在进程中,线程就是一个执行流程,正在独立运行的一条执行路径。
多线程:
为了提高程序的效率
注:多线程下载、断点续传中多线程并不是提高了宽带速度,而是提高了程序的效率(打个比方就是本来是一个干,多线程就是多个人一起做一件事,并且彼此之间独立,即每个人负责一部分,因为是独立的,所以一个出问题不会影响到其他线程)
多线程应用场景
多线程下载,应用的多个功能同时进行,多线程爬虫,ajax(异步上传),分布式job(同一时刻进行多个任务调度)
归根结底:多线程就是为了提高程序效率
同步-synchronized
可以理解为在执行完一个函数或方法之后,一直等待系统返回值或消息,这时程序是出于阻塞的,只有接收到返回的值或消息后才往下执行其他的命令。 即按顺序执行,必须一个执行完另一个才能执行。
同步就是共享,如果不是共享的资源,就没有必要进行同步。
同步与线程安全
同步的目的就是为了线程安全,其实对于线程安全来说,需要满足两个特性:
原子性(同步) 可见性
讯享网
原子性
原子操作指的是不可再分的指令操作,即在执行原子操作时不可能被打断,要么原子操作没有执行,要么已经执行完毕。
比如:a+=b 可以分为三步:
取出a和b
计算a+b
写入内存
如果有两个线程t1,t2在进行这样的操作。t1在第二步做完之后还没来得及把数据写回内存就被线程调度器中断了,于是t2开始执行,t2执行完毕后t1又把没有完成的第三步做完。这个时候就出现了错误,相当于t2的计算结果被无视掉了。
这时就不具备原子性
可见性
可见性volatile修饰词,可以应对多线程同时访问修改同一变量,由于相互的不可见性所带来的不可预期的结果,存在二义性的现象,出现的。
多线程变量不可见:当一个线程对一变量a修改后,还没有来得及将修改后的a值回写到主存,而被线程调度器中断操作(或收回时间片),然后让另一线程进行对a变量的访问修改,这时候,后来的线程并不知道a值已经修改过,它使用的仍旧是修改之前的a值,这样修改后的a值就被另一线程覆盖掉了。
多线程变量可见:被volatile修饰的成员变量在每次被线程访问时,都强迫从内存中重读该成员变量的值;而且,当成员变量发生变化时,强迫线程将变化值回写到共享内存,这样在任何时刻两个不同线程总是看到某一成员变量的同一个值,这就是保证了可见性。
volatile使用场景:
在两个或者更多的线程访问的成员变量上使用volatile。当要访问的变量已在synchronized代码块中,或者为常量时,不必使用。
由于使用volatile屏蔽掉了JVM中必要的代码优化,所以在效率上比较低,因此一定在必要时才使用此关键字。
注:
如果给一个变量加上volatile修饰符,就相当于:每一个线程中一旦这个值发生了变化就马上刷新回主存,使得各个线程取出的值相同。编译器不要对这个变量的读、写操作做优化。但是值得注意的是,除了对long和double的简单操作之外,volatile并不能提供原子性。所以,就算你将一个变量修饰为volatile,但是对这个变量的操作并不是原子的,在并发环境下,还是不能避免错误的发生!
异步-asynchronized
异步就是独立,相互之间不受到任何制约
注
以上内容会在 同步与线程安全 中详细讲解
创建线程
创建线程方式:
讯享网1、继承Thread类重写run方法 2、实现Runnable接口 3、使用匿名内部类 4、使用callable 5、使用线程池创
使用继承的方式创建线程
public class MyExendsThread extends Thread {
/ * run方法里面就是线程需要执行的任务或代码 */ @Override public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run" + i + " = " + i); } //获取线程名 System.out.println(this.currentThread().getName()); } public static void main(String[] args) {
//声明线程对象 MyExendsThread exendsThread = new MyExendsThread(); //设置线程名 exendsThread.setName("Thread1"); //启动线程 exendsThread.start(); } }
使用Runnable接口方式
讯享网public class MyRunnable implements Runnable {
@Override public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run" + i + " = " + i); } //获取线程名 System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) {
//声明对象 MyRunnable myRunnable = new MyRunnable(); //创建线程,并命名,可以不进行命名(去除"Thead1" 就行:new Thread(myRunnable)) Thread thread = new Thread(myRunnable, "Thead1"); thread.start(); } }
使用Runnable接口方式比继承Thread的好处
1、使用继承无法再继承其他类 2、一般来说都是面向接口编程
使用匿名内部类创建线程
讯享网public class MyInnerThread {
public static void main(String[] args) {
Thread thread = new Thread(){
@Override public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("run" + i + " = " + i); } //获取线程名 System.out.println(this.currentThread().getName()); } }; thread.setName("Thead1"); thread.start(); } }
多线程常用API
常用线程方法:
| 方法 | 用处 |
|---|---|
| start | 启动线程 |
| currentThread | 获取当前线程对象 |
| getName | 获取线程名称 |
| getId | 获取线程Id |
| sleep(Long mill) | 休眠线程(毫秒数) |
| stop | 停止线程 |
注:stop方法不建议大家使用,他是直接停止,不安全,后面会叙述停止线程的方法
常用线程构造函数:
| 方法 | 用处 |
|---|---|
| Thread() | 分配一个新的 Thread 对象 |
| Thread(String name) | 分配一个新的 Thread 对象,指定名字是name |
| Thread(Runnable r) | 分配一个新的 Thread 对象 |
| Thread(Runnable r,String name) | 分配一个新的 Thread 对象,指定名字是name |
守护线程与非守护线程
守护线程
和主线程相关的线程。
特征:和主线程一起被销毁
注:gc线程也属于守护线程。垃圾回收机制主要回收主线程的。
非守护线程
用户线程也叫非守护线程。
用户线程,即用户自己创建的线程,如果主线程停止掉,不会影响到用户线程
特征:和主相互独立,互不影响。
下面代码可以进行演示:
/ * 用户线程是由主线程创建的,是非守护线程 */ public class MyInnerThread {
public static void main(String[] args) {
Thread t1 = new Thread(){
@Override public void run() {
for (int i = 0; i < 30; i++) {
try {
sleep(300); System.out.println("子线程" + i + " = " + i); } catch (InterruptedException e) {
e.printStackTrace(); } } } }; //设置t1为守护线程,随主线程一起销毁 //t1.setDaemon(true); t1.start(); for (int i = 0; i < 5; i++) {
System.out.println("主线程" + i + " = " + i); } System.out.println("主线程结束..."); } }
上面代码可以看出,主线程已经结束,子线程依然在进行,如果要将子线程设置为守护线程,当主线程结束后,子线程也会销毁,可以通过代码:thread.setDaemon(true); 进行设置
多线程运行状态
讯享网新建状态 准备状态 运行状态 休眠(阻塞)状态 停止状态
各个阶段如下图:

join方法
把cpu执行权让给其他线程,等待其他线程执行完毕,再继续执行。可以用来设置优先级。
例如:有A、B两个线程,在A线程中调用 B.join() 就是把cpu执行权让给B,此时A会等待B执行完再执行。
下面代码可以进行演示:
public class JoinThread2 {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
for (int i = 0; i < 20; i++) {
System.out.println("子线程:" + i); } System.out.println("子线程执行结束"); } }); //让cpu执行权给t1,等t1执行完再执行 t1.start(); t1.join(); for (int i = 0; i < 10; i++) {
System.out.println("主线程:" + i); } System.out.println("主线程执行结束"); } }
如果不加 t1.join(); 就会是子线程与主线程争夺cpu执行权,加上就会先执行子线程,再执行主线程。
线程优先级案例:
有三个线程T1、T2、T3,保证T2在T1后面执行,T3在T2后面执行:
使用join方法,设定优先级:
讯享网public class JoinThread {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override public void run() {
System.out.println("T1执行结束"); } }); Thread t2 = new Thread(new Runnable() {
@Override public void run() {
try {
//cpu执行权让给t1 t1.join(); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("T2执行结束"); } }); Thread t3 = new Thread(new Runnable() {
@Override public void run() {
try {
//cpu执行权让给t2 t2.join(); } catch (InterruptedException e) {
e.printStackTrace(); } System.out.println("T3执行结束"); } }); t1.start(); t2.start(); t3.start(); } }
多线程分批处理数据案例:
有10万个用户,要求给每个用户发送一条短信。
分析:如果使用单线程的话,假如一个用户从查询数据,到发信息需要1秒,那也需要10万秒,并且单线程如果有一个用户出错,会影响到后面的用户,所以使用多线程来处理数据,可以提高效率,并且会减小线程出错带来的影响,如下图:

为了方便演示,我们以10个数据代替10万:
pojo类:
public class User {
private int userId; private String userName; public User() {
} public User(int userId, String userName) {
this.userId = userId; this.userName = userName; } public int getUserId() {
return userId; } public void setUserId(int userId) {
this.userId = userId; } public String getUserName() {
return userName; } public void setUserName(String userName) {
this.userName = userName; } @Override public String toString() {
return "User{" + "userId=" + userId + ", userName='" + userName + '\'' + '}'; } }
分配数据类:
讯享网public class UserData {
public static List<List<User>> getData(List<User> list, int countNum){
List<List<User>> lists = new ArrayList<List<User>>(); int count = list.size(); int page = count/countNum + (count%countNum>0?1:0); for (int i = 0; i < page; i++) {
List<User> list1 = new ArrayList<User>(); for (int j = i*countNum; j < (i+1)*countNum; j++) {
if (j == list.size()){
break; } list1.add(list.get(j)); } lists.add(list1); } return lists; } }
发送线程类:
public class SendThead implements Runnable{
private List<User> list; public SendThead() {
} public SendThead(List<User> list) {
this.list = list; } @Override public void run() {
for (User user:list){
System.out.println(Thread.currentThread().getName()+": "+user); } } }
批量处理(主线程):
讯享网public class BatchSms {
private static List<User> initUser() {
//初始化数据 我们以10代10万进行演示 List<User> list = new ArrayList<>(); for (int i = 0; i < 10; i++) {
list.add(new User(i, String.valueOf(i))); } return list; } public static void main(String[] args) {
//1.初始化数据 List<User> list = initUser(); //2.定义每个线程分批发送大小 int countNum = 2; //3.计算每个线程需要分配的数据 List<List<User>> data = UserData.getData(list, countNum); System.out.println(data); //4.进行分批发送 for (int i = 0; i < data.size(); i++) {
List<User> list1 = data.get(i); Thread thread = new Thread(new SendThead(list1), "线程" + i); thread.start(); } } }
下一篇:同步与线程安全

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容,请联系我们,一经查实,本站将立刻删除。
如需转载请保留出处:https://51itzy.com/kjqy/10629.html