多线程
程序、进程、线程
程序(program)是为完成特定任务、用某种语言编写的一组指令的集合。即指一段静态的代码,静态对象。
进程(process)是程序的一次执行过程,或是正在运行的一个程序。是一个动态的过程:有它自身的产生、存在和消亡的过程。——生命周期
比如:运行中的QQ,运行中的MP3播放器
程序是静态的,进程是动态的
进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
线程(thread),进程可进一步细化为线程,是一个程序内部的一条执行路径。
若一个进程同一时间并行执行多个线程,就是支持多线程的
线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器(pc),线程切换的开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间,它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全的隐患。
示意图:
cpu的单核多核
a)单核CPU,其实是一种假的多线程,因为在一个时间单元内,也只能执行一个线程 的任务。例如:虽然有多车道,但是收费站只有一个工作人员在收费,只有收了费 才能通过,那么CPU就好比收费人员。如果有某个人不想交钱,那么收费人员可以 把他“挂起”(晾着他,等他想通了,准备好了钱,再去收费)。但是因为CPU时间单元特别短,因此感觉不出来。
b)如果是多核的话,才能更好的发挥多线程的效率。(现在的服务器都是多核的)
c)一个Java应用程序java.exe,其实至少有三个线程:main()主线程,gc() 垃圾回收线程,异常处理线程。当然如果发生异常,会影响主线程。
并行并发
并行:多个CPU同时执行多个任务。比如:多个人同时做不同的事。
并发:一个CPU(采用时间片)同时执行多个任务。比如:秒杀、多个人做同一件事1(这些人要排队干)
优点
背景:以单核CPU为例,只使用单个线程先后完成多个任务(调用多个方 法),肯定比用多个线程来完成用的时间更短,为何仍需多线程呢?
多线程程序的优点:
1.提高应用程序的响应。对图形化界面更有意义,可增强用户体验。
2.提高计算机系统CPU的利用率
3.改善程序结构。将既长又复杂的进程分为多个线程,独立运行,利于理解和修改
应用场景
程序需要同时执行两个或多个任务。
程序需要实现一些需要等待的任务时,如用户输入、文件读写操作、网络操作、搜索等。
需要一些后台运行的程序时。
创建线程
创建线程有两种方式
1 继承Thread类并覆写run(相当于新线程中的main)方法
2 实现Runable接口并实现run方法
启动线程:手动调用线程对象的start()方法
注意:不是调用run方法调用start方法
如果调用run方法并不是启动线程,只是一个单纯的方法调用
当我们调用start方法的时候,会自动开启新的线程并调用run方法
Thread
public static void test_01(){//继承方式启动
Thread thread = new Processor_01();
//启动该线程,自动调用run方法,切记不能手动调用run方法
thread.start();
//这里,会有两个线程同时进行
for (int i = 0; i < 50; i++) {
System.out.println("main --->"+i);
}
}
===============
class Processor_01 extends Thread{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("run --->"+i);
}
}
}
Runnable
public static void test_02(){//实现方式启动
Thread thread = new Thread(new Processor_02());
// 创建线程有两种,但是启动线程只有一种方式,就是调用该对象的start方法
thread.start();
//这里,会有两个线程同时进行
for (int i = 0; i < 50; i++) {
System.out.println("main --->"+i);
}
}
======
class Processor_02 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("run --->"+i);
}
}
常用方法
start() : 启动线程的唯一方式
setName() : 设置线程的名字 默认是Thread-0,Thread-1…
getName() : 获取线程的名字
setPriority() : 设置线程优先级
getPriority() : 获取线程优先级
static currentThread() : 获取当前线程的内存地址
static sleep() : 睡眠当前线程,参数是睡眠的毫秒数
静态的,和用哪个对象没关系,写在哪个线程中,就获取哪个线程对象,就睡眠哪个线程
public class Thread_02 {
public static void main(String[] args) {
Thread t1 = new Processer();
Thread t2 = new Processer();
t1.setName("t1");
t2.setName("t2");
// 设置优先级为1
t1.setPriority(1);
// 设置优先级为10
t2.setPriority(10);
t1.start();
t2.start();
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Processer extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " --> " + i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生命周期
JDK中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类 及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五种状态:
新建: 当一个Thread类或其子类的对象被声明并创建时,新生的线程对象处于新建 状态
就绪:处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
运行:当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线 程的操作和功能
阻塞:在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
死亡:线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结束
终止线程
public class Thread_03_stop {
public static void main(String[] args) {
Processer_02 p = new Processer_02();
Thread t1 = new Thread(p);
t1.setName("t1");
t1.start();
// t1线程是死循环,5秒后,终止它
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// stop已经过时,不推荐使用,因为可能会导致死锁
// t1.stop();
// 使用占位符的方式终止线程
p.flag = false;
}
}
class Processer_02 implements Runnable {
boolean flag = true;
@Override
public void run() {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss SSS");
while (flag) {
System.out.println(Thread.currentThread().getName() + " : "
+ sdf.format(new Date()));
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
线程控制
public class Thread_04_Join {
public static void main(String[] args) {
Thread t1 = new Processer_03();
t1.setName("t1");
t1.start();
// 多线程同时执行
// 合并 让当前线程等待指定线程执行完后再执行
try {
t1.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
class Processer_03 extends Thread {
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + " : " + i);
}
}
}
yield() : 暂停当前线程,让位给其他等待中的线程执行
是个静态方法,使用Thread.yield() 进行调用
同优先级让位,不同优先级不让位
public class Thread_05_Yield {
public static void main(String[] args) {
Thread t1 = new Thread(new Processor_04());
t1.start();
for (int i = 0; i < 10; i++) {
// 让出当前执行的时间片,让其他等待的线程执行
Thread.yield();
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
class Processor_04 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName()+" : "+i);
}
}
}
线程同步
多个线程操作同一组共享数据时,一个线程没执行完另一个线程又来执行,导致数据出错,所以这种需求下只能让一个线程执行完,在执行过程中,其他线程不能参与
当多个线程有可能操作同一个数据的时候,为了考虑数据的一致性和安全性,需要进行同步机制,尤其是更改操作,查询无所谓,因为查询不会对数据发生更改.
1 同步代码块
Synchronized(对象){ // 需要同步的代码 }
2 还可以锁方法,使用synchronized 修饰方法即可
多线程可能出现的问题与解决
原始问题状态
因为多线程并行问题,导致多个线程操作了同一个数据,导致结果不一致
解决:
public void withDraw(double money) {
System.out.println("不需要同步的其他功能");
// 下面代码 需要同步执行
synchronized (this) {
// try {
// Thread.sleep(5000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
double after = balance - money;
balance = after;
System.out.println(Thread.currentThread().getName()
+ " --> 取款成功 : 1000.0 , 余额为 : " + balance);
}
}
lock
1 Lock 是显式锁,需要手动开启和手动关闭,而synchronized是隐式锁,出了作用域,自动解锁
2 Lock 只有代码块锁,而synchronized可以代码块锁,可以方法锁
3 使用Lock还有更多功能扩展
使用
public class Thread_07_Lock {
public static void main(String[] args) {
Account1 account = new Account1(3000);
Thread t1 = new Thread(new Processor_07(account));
Thread t2 = new Thread(new Processor_07(account));
Thread t3 = new Thread(new Processor_07(account));
t1.setName("t1");
t2.setName("t2");
t3.setName("t3");
t1.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
t3.start();
}
}
class Processor_07 implements Runnable {
Account1 account;
public Processor_07(Account1 account) {
this.account = account;
}
@Override
public void run() {
account.withDraw(1000);
}
}
class Account1 {
private double balance;
public Account1(double balance) {
super();
this.balance = balance;
}
public double getBalance() {
return balance;
}
public void setBalance(double balance) {
this.balance = balance;
}
// 创建锁
Lock lock = new ReentrantLock(false);
public void withDraw(double money) {
System.out.println("不需要同步的其他功能");
// 加锁
lock.lock();
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
double after = balance - money;
balance = after;
System.out.println(Thread.currentThread().getName()
+ " --> 取款成功 : 1000.0 , 余额为 : " + balance);
// 解锁
lock.unlock();
}
}
死锁
死锁 : 就是大家在执行过程中,都遇到了对方进入加锁的方法,从而导致都访问不了的状态
1 某个线程执行完成 , 需要 先后 嵌套 锁定两个对象,在这个过程中,该线程先锁定了第一个对象
2 另一个线程执行完成 , 需要 先后 嵌套 锁定两个对象,在这个过程中,该线程先锁定了第二个对象
3 第一个线程执行中,执行到锁第二个对象的时候,发现第二个对象被第二个线程锁住了,只能等待
4第二个线程执行中,执行到锁第一个对象的时候,发现第一个对象被第一个线程锁住了,只能等待
代码描述
public class Thread_08_DeadLock {
public static void main(String[] args) {
for (int i = 0; i < 1220; i++) {
Object o1 = new Object();
Object o2 = new Object();
Thread t1 = new T1(o1, o2);
Thread t2 = new Thread(new T2(o1, o2));
t1.start();
t2.start();
System.out.println(i);
}
}
}
class T1 extends Thread {
Object o1;
Object o2;
@Override
public void run() {
// 当我们使用代码块锁,锁住o1的时候,那么 o1对象中的所有代码块锁和所有加锁的成员方法,全部锁定
synchronized (o1) {
// 加睡眠 是为了 让t2线程先锁住o2 , 保证一定死锁
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println("T1已经对o1加锁");
synchronized (o2) {
System.out.println("T1已经对o2加锁");
}
}
System.out.println("T1执行完成,o1和o2解锁");
}
public T1(Object o1, Object o2) {
super();
this.o1 = o1;
this.o2 = o2;
}
}
class T2 implements Runnable {
Object o1;
Object o2;
public T2(Object o1, Object o2) {
super();
this.o1 = o1;
this.o2 = o2;
}
@Override
public void run() {
synchronized (o2) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
// System.out.println("T2已经对o2加锁");
synchronized (o1) {
System.out.println("T2已经对o1加锁");
}
}
System.out.println("T2执行完成,o1和o2解锁");
}
}
线程通信
wait() 与 notify() 和 notifyAll()
a) wait():令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当 前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有 权后才能继续执行。
b) notify():唤醒正在排队等待同步资源的线程中优先级最高者结束等待
c) notifyAll ():唤醒正在排队等待资源的所有线程结束等待.
注意:这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报
java.lang.IllegalMonitorStateException异常
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁, 因此这三个方法只能在Object类中声
生产者消费者模式
生产者(Productor)将产品交给店员(Clerk),而消费者(Customer)从店员处 取走产品,店员一次只能持有固定数量的产品(比如:20),如果生产者试图 生产更多的产品,店员会叫生产者停一下,如果店中有空位放产品了(消费者消费了)再通知生产者继续生产;如果店中没有产品了,店员会告诉消费者等一下,如 果店中有产品了再通知消费者来取走产品。
这里可能出现两个问题:
生产者比消费者快时,消费者会漏掉一些数据没有取到。
消费者比生产者快时,消费者会取相同的数据。
public class Thread_09_Wait {
public static void main(String[] args) {
SynStack ss = new SynStack();
Thread t1 = new Thread(new Producer(ss));
Thread t2 = new Thread(new Consumer(ss));
t1.start();
t2.start();
}
}
// 生产者线程
class Producer implements Runnable{
SynStack ss = null;
public Producer(SynStack ss) {
this.ss = ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
char ch = (char) (i+97);
ss.push(ch);
}
}
}
// 消费者线程
class Consumer implements Runnable{
SynStack ss = null;
public Consumer(SynStack ss) {
this.ss = ss;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
ss.pop();
}
}
}
// 业务类
class SynStack {
// 缓冲区
char[] data = new char[6];
// 已生产的个数
int cnt = 0;
// 生产方法
public synchronized void push(char c){
// 缓冲区生产满之后,就不再生产
if (cnt == data.length) {
// 进入挂起状态,并交出锁
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
// 到这里说明没满,应该生产产品,然后唤醒消费者开始消费
data[cnt] = c;
cnt++;
System.out.println("生产了 : "+c+" , 目前剩余 : "+cnt+" 个字符");
this.notifyAll();
}
// 消费方法
public synchronized char pop(){
char ch = ' ';
// 判断个数是否是0, 是0说明消费完了,就进入挂起
if (cnt == 0) {
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
cnt--;
ch = data[cnt];
System.out.println("消费了 : "+ch+" , 目前剩余 : "+cnt+" 个字符");
this.notifyAll();
return ch;
}
}
单例模式
基本概念
让某个类只实例化一次对象
1 构造方法私有化
2 静态变量保存创建的对象
3 公共的静态方法,用于获取当前类对象
多线程创建了多个对象
解决
可以使用synchronized解决
可行,但是效率低了,实际上只需要让第一次创建对象时,线程排队就行,创建成功后后面线程直接用不用排队.
所以此时我们应该优先考虑代码块锁而不是方法锁
public class SingLeton_01 {
private SingLeton_01() {
}
// volatile防止指令重排
private volatile static SingLeton_01 s = null;
public static SingLeton_01 getInstance() {
if (s == null) {
// 类锁,类中所有加锁的静态方法和语句块锁 全部锁定
synchronized(SingLeton_01.class){
// 双重校验
if ( s == null) {
s = new SingLeton_01();
}
}
}
return s;
}
}