多线程
进程:
程序执行系统分配给我们的程序的一个内存空间,启动时,会创建一个主线程
简单来说一个程序的执行就是一个进程.
分时操作:
将处理机的运行时间分成很短的时间片,按时间片轮流把处理机分配给各联机作业使用.因为各联机使用时间很短,所以看起来就像是一直在运行一样.
线程:
线程是操作系统能进行运算调度的最小单位,也就是执行程序的最小单位,一个进程里默认有一个线程:即主线程
进程与线程的区别:
1.作用
进程是资源分配的最小单位
线程是程序执行的最小单位
2.速度
进程有自己的独立地址空间
而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小得多,同时创建一个线程的开销也比进程要小得多
3.关系
进程中第一个线程是主线程,主线程创建其他线程,其他线程也可以创建线程,线程之间是平等的
进程有父进程,子进程,独立的内存空间,唯一的进程标识符,pid
4.创建
创建新的线程很容易
创建新的进程需要对父进程做一次复制
5.操作
一个线程可以操作同一进程的其他线程
进程只能操作其子进程
多线程作用:
提高程序的效率
创建使用:
一.继承Thread
1.步骤
1.写一个类直接继承Thread类
2.重写run方法.该run()方法的方法体就代表了线程需要完成的任务
3.创建Thread子类的实例
4.调用线程对象的start()方法来启动该线程
2.语法格式
public class MyThread extends Thread {
@Override
public void run() {
}
}
public class ThreadExample {
public static void main(String[] args) {
MyThread t = new MyThread();
t.start();
}
}
二.实现Runnable接口(推荐)
1.步骤
1.定义Runnable的实现类,重写run()方法
2.创建Runnable实现类的实例,并以此作为Thread的target来创建对象,该对象才是真正的线程对象
2.语法格式
public class MyRunnable implements Runnable{
@Override
public void run() {
}
}
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println("这个是实现Runnable");
}
}
public class ThreadExample {
public static void main(String[] args) {
// 第一步
Runnable runable = new MyRunnable();
//第二步
Thread thread = new Thread(runable);
// 第三步
thread.start();
}
}
三.匿名内部类实现
1.方式一
public class AnonThread {
public static void main(String[] args) {
anonThread();
}
public static void anonThread() {
Thread t = new Thread() {
@Override
public void run() {
super.run();
System.out.println("白天么么哒! 晚上xxx");
}
};
t.start();
}
}
2.方式二
public class AnonThread {
public static void main(String[] args) {
anonRunnable();
}
public static void anonRunnable() {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("自古多情空余恨, 总是套路得人心!");
}
});
t.start();
/**
* 简写方式 1.8以上的版本还可以使用lambda表达式
*/
new Thread(() -> System.out.println("我爱撸代码")).start();
}
}
注意:
核心方法为run()
但不要手动调用run()方法,通过start()方法开启线程
使用Runnable的优点:
1.适合多个相同的程序代码的线程去处理同一个资源
2.可以避免java中的单继承的限制
3.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
4.线程池只能放入实现Runnable或callable类线程,不能直接放入继承Thread的类
多线程状态说明:
1. New(初始化状态)
2. Runnable(可运行/运行状态)
3. Blocked(阻塞状态)
4. Waiting(无时间限制的等待状态)
5. Timed_Waiting(有时间限制的等待状态)
6. Terminated(终止状态)
多线程的生命周期:
1.准备阶段:初始化new
2.就绪阶段:Runnable
3.运行阶段(run):开始执行run方法的代码
4.阻塞阶段:Blocked(1.等待阻塞(wait);2.同步阻塞;3.其他阻塞(sleep,join))
5.死亡状态(dead):自然死亡(线程结束);interrupt;run()方法里面的程序出异常
线程调度:
线程调度是指按照特定机制为多个线程分配CPU的十使用
调度方式:
1.分时调度(CPU层面):
所有线程轮流使用CPU(时间片)
2.抢占式调度(jvm虚拟机层面)
通过内置的api实现线程的优先级设置:setPriority(int) (创建线程的默认级别是5,可以设置的范围1-10)
(不可靠)
设置优先级抢占调度(setPriority(int)):
public class ThreadPriorityExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "优先级: " + Thread.currentThread().getPriority());
});
Thread t2 = new Thread(() -> {
System.out.println(Thread.currentThread().getName() + "优先级: " + Thread.currentThread().getPriority());
});
t1.start();
t1.setPriority(1);
t2.start();
t2.setPriority(Thread.MAX_PRIORITY);
}
}
放弃CPU使用原因:
1. java虚拟机让当前线程暂时放弃CPU,转到就绪状态,使其它线程获得运行机会。
2. 当前线程因为某些原因而进入阻塞状态
3. 线程结束运行
放弃方式:
1. 调整各个线程的优先级
2. 让处于运行状态的线程调用Thread.sleep()方法
3. 让处于运行状态的线程调用Thread.yield()方法
4. 让处于运行状态的线程调用另一个线程的join()方法
1.线程睡眠(sleep)
1.说明
Thread.sleep(long millis)方法,一般用于开发阶段模拟网络延迟。使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。当睡眠结束后,就转为就绪(Runnable)状态。sleep()平台移植性好。
个人建议使用TimeUnit的sleep()方法
2.sleep方法
void sleep(long millis)
3.例子
// 注意该方法会抛出异常, 所以一定要对异常进行处理
new Thread(() -> {
try {
Thread.sleep(1000);
TimeUnit.SECONDS.sleep(1);
System.out.println("线程睡眠中!!!");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
2.线程让步(yield):
1.说明
Thread.yield() 方法,暂停当前正在执行的线程对象,把执行机会让给相同或者更高优先级的线程。
2.注意:
yield()是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但有可能没有效果。
3.例子
public class ThreadYield implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 1; i <= 3; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i);
// 当i为2时,该线程放弃CPU使用权,让其他或者自己的线程执行(也就是谁先抢到谁执行)
if (i == 2) {
Thread.yield();
}
}
}
}
// 测试类
public class ThreadYieldExample {
public static void main(String[] args) {
System.out.println("主线程开始:" + Thread.currentThread().getName());
Thread thread1 = new Thread(new ThreadYield());
Thread thread2 = new Thread(new ThreadYield());
thread1.start();
thread2.start();
System.out.println("主线程结束:" + Thread.currentThread().getName());
}
}
3.yield与sleep的区别:
1. sleep给其它线程运行的机会,但不考虑其它线程的优先级;但yield只会让位给相同或更高优先级的线程;
2. sleep有异常, yield没有
3. 当线程执行了sleep方法后,将转到阻塞状态,而执行了yield方法之后,则转到就绪状态;
4.中断线程(interrupted):
1.说明
对某一线程调用 interrupt()时,如果该线程正在执行普通的代码,那么该线程根本就不会抛出InterruptedException。
一旦该线程进入到 wait()/sleep()/join()后,就会立刻抛出InterruptedException 。
2.注意
1. 当抛出InterruptedException时候,会清除中断标志位,也就是说在调用isInterrupted会返回false。
2. 不要以为它是中断某个线程!它只是线线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,如果你处理了异常,那么这个线程还是不会中断的!
3.作用:
通常用于解决死锁的问题
4.常用方法
static boolean interrupted()
boolean isInterrupted
5.例子
public class ThreadInterruptedExample {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
TimeUnit.SECONDS.sleep(1);
System.out.println(Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
});
Thread t2 = new Thread(() -> {
t1.interrupt();
System.out.println(Thread.currentThread().getName());
});
t1.start();
t2.start();
}
}
// java.lang.InterruptedException: sleep interrupted
注意:
如果代码在运行状态下是不会中断的,如果成功会抛出异常
5.应用场景:
1.耗时操作:IO操作,网络操作
2.web开发
3.后台任务(1.发送邮件;2.日志采集;3.定时备份;4.定时任务)
4.分布式计算
线程同步:
1.原因:
当线程操作一个数据时,会引起脏数据的问题(解决方式使用Synchronized关键字,其本质是锁Lock)
2.使用时机:
1. 多个线程执行的时候需要同步,如果是单线程则不需要同步。
2. 多个线程在执行的过程中是不是使用同一把锁。
3.常用同步方案
1. synchronized(同步)
2. ReentrantLock (可重入锁)
3. Atomic( 原子类,效率较高,可用于优化 )
4. ThreadLocal (线程本地变量)
同步锁(synchronized):
1.说明
synchronized 是 Java 中的关键字,是利用锁的机制来实现同步的。synchronized可以保证方法或者代码块在运行时,同一时刻只有一个方法可以进入到临界区,这是synchronized实现同步的基础。
2.锁机制的两种特性:
1.互斥性
即在同一时间只允许一个线程持有某个对象锁,通过这种特性来实现多线程中的协调机制,这样在同一时间只有一个线程对需同步的代码块(复合操作)进行访问。互斥性我们也往往称为操作的原子性
2.可见性
必须确保在锁被释放之前,对共享变量所做的修改,对于随后获得该锁的另一个线程是可见的(即在获得锁时应获得最新共享变量的值),否则另一个线程可能是在本地缓存的某个副本上继续操作从而引起不一致。
3.同步锁的用法(类锁和对象锁)
1.类锁
在代码中的方法上加了static和synchronized
的锁,或者synchronized(xxx.class)
2.对象锁
在代码中的方法上加了synchronized
的锁,或者synchronized(this)
的代码段
3.注意
- 类锁和对象锁不会产生竞争,二者的加锁方法不会相互影响。
- 一个实例对象一把锁,多个实例对象多把锁.多线程解决高并发只能通过一个加锁实例实现
4.根据修饰对象用法分类
1.修饰代码块
- synchronized(this|object) {}
- synchronized(类.class) {}
2.修饰方法
- 修饰非静态方法
- 修饰静态方法
5.修饰代码块锁-对象
1.说明
在代码中的方法上加了`synchronized`的锁,或者`synchronized(this)`的代码段
如果一个线程访问一个对象中的synchronized(this)同步代码块时,其他试图访问该对象的线程将被阻塞。
例子(在调用方法中使用):
public class SyncThreadExample implements Runnable {
private int count;
public SyncThreadExamp1() {
count = 0;
}
@Override
public void run() {
// 锁定对象
synchronized (this) {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class TestSyncThreadExample {
public static void main(String[] args) {
SyncThreadExample syncThread = new SyncThreadExample();
Thread t1 = new Thread(syncThread, "同步线程1");
Thread t2 = new Thread(syncThread, "同步线程2");
t1.start();
t2.start();
}
/**
* 同步线程1:6
* 同步线程1:7
* 同步线程1:8
* 同步线程1:9
* 同步线程2:10
* 同步线程2:11
* 同步线程2:12
* 同步线程2:13
* 同步线程2:14
* 同步线程2:15
*/
}
2.说明
- 当两个并发线程(thread1和thread2)访问同一个对象(syncThread)中的synchronized代码块时,在同一时刻只能有一个线程得到执行,另一个线程受阻塞,必须等待当前线程执行完这个代码块以后才能执行该代码块。
- Thread1和thread2是互斥的,因为在执行synchronized代码块时会锁定当前的对象,只有执行完该代码块才能释放该对象锁,下一个线程才能执行并锁定该对象。
3.修改代码
Thread t1 = new Thread(new SyncThreadExample(), "同步线程1");
Thread t2 = new Thread(new SyncThreadExample(), "同步线程2");
t1.start();
t2.start();
/**
* 同步线程1:0
* 同步线程2:0
* 同步线程1:1
* 同步线程2:1
*/
4.分析
- 我们知道synchronized锁定的是对象,这时会有两把锁分别锁定Thread1对象和Thread2对象,
- 而这两把锁是互不干扰的,不形成互斥,所以两个线程可以同时执行。
2、多个线程访问synchronized和非synchronized代码块
例子:
public class Counter implements Runnable {
private int count = 0;
public static final int MAX_NUM = 10;
public static final String CONS_ADD = "add";
/**
* 同步方法 不允许多个线程同时访问
*/
private void add() {
synchronized (this) {
for (int i = 0; i < MAX_NUM; i++) {
try {
count++;
System.out.println(Thread.currentThread().getName() + "====>" + count);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 非同步方法 允许多个线程同时访问
*/
private void find() {
for (int i = 0; i < MAX_NUM; i++) {
try {
System.out.println(Thread.currentThread().getName() + " count:" + count);
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
@Override
public void run() {
String threadName = Thread.currentThread().getName();
if (CONS_ADD.equals(threadName)) {
add();
} else {
find();
}
}
}
public class TestSyncThreadCounter {
public static void main(String[] args) {
Counter counter = new Counter();
Thread thread1 = new Thread(counter, "add");
Thread thread2 = new Thread(counter, "find");
thread1.start();
thread2.start();
}
/**
* add====>3
* find count:3
* add====>4
* find count:4
* add====>5
* find count:5
* find count:6
* add====>6
* add====>7
* find count:7
*/
}
2.说明
- 上面代码中add是一个synchronized的,find是非synchronized的。
- 从上面的结果中可以看出一个线程访问一个对象的synchronized代码块时,别的线程可以访问该对象的非synchronized代码块而不受阻塞。
3.指定给某个对象加锁
例子:
public class Account {
private String cardNo;
private float balance;
public Account() {
}
public Account(String cardNo, float balance) {
this.cardNo = cardNo;
this.balance = balance;
}
public void put(float balance) {
this.balance += balance;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 取钱
*
* @param balance
*/
public void del(float balance) {
this.balance -= balance;
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
/**
* 查询余额
*/
public float find() {
return balance;
}
}
public class TestAccountOperator {
public static void main(String[] args) {
Account account = new Account();
AccountOperator operator = new AccountOperator(account);
for (int i = 0; i < 10; i++) {
new Thread(operator).start();
}
}
}
2.说明
在AccountOperator 类中的run方法里,我们用synchronized 给account对象加了锁。这时,当一个线程访问account对象时,其他试图访问account对象的线程将会阻塞,直到该线程访问account对象结束
6、修饰代码块锁-类
1.说明
对一个类加锁时,对该类的所有对象都起作用。跟静态方法是相同,因为静态方法本身就是属于类的
2.例子
public class SyncThreadExample1 implements Runnable {
private int count;
public SyncThreadExample1() {
count = 0;
}
@Override
public void run() {
synchronized (SyncThreadExample1.class) {
for (int i = 0; i < 10; i++) {
try {
System.out.println(Thread.currentThread().getName() + ":" + (count++));
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
public class TestSyncThreadExample1 {
public static void main(String[] args) {
Thread t1 = new Thread(new SyncThreadExample1(), "同步线程1");
Thread t2 = new Thread(new SyncThreadExample1(), "同步线程2");
t1.start();
t2.start();
/**
* 同步线程1:6
* 同步线程1:7
* 同步线程1:8
* 同步线程1:9
* 同步线程2:0
* 同步线程2:1
* 同步线程2:2
* 同步线程2:3
* 同步线程2:4
* 同步线程2:5
*/
}
}
7.修饰方法
1.说明
在执行该对象的这个同步方法(注意:是该对象)就会产生互斥
2.格式
public synchronized void method(){
}
8.修饰一个静态方法
1.说明
Synchronized也可修饰一个静态方法
2.例子
public synchronized static void method() {
}