文章目录
多线程
一、进程和线程
进程:进程就是正在运行的程序,是系统进行资源分配和调用的独立单位。每一个进程都有它自己的内存空间和系统资源。
多进程:因为单核CPU在某个时间点上只能做一件事情,计算机是在多个进程间做着频繁切换,且切换速度很快,所以,我们感觉多个进程在同时进行,其实并不是同时执行的。多进程的作用不是提高执行速度,而是提高CPU的使用率。
线程:在一个进程内部又可以执行多个任务,而这每一个任务我们就可以看成是一个线程。线程是CPU调度的基本单位。
多线程:Java使用的线程模式是抢占式调度,即多个线程抢占CPU的时间片(执行权),线程的优先级高的抢占的概率相对较大,优先级相同的,抢占的概率的相等,具有随机性,不确定性;多线程可以解决负载均衡问题,充分利用CPU的资源。
并发:指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
并行:指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
二、多线程实现
Thread类的基本获取和设置方法
public final String getName() //获取线程名称
public final void setName(String name) //设置线程名称
public static Thread currentThread()//获取当前执行的线程
1、方式一
public class Blog {
public static void main(String[] args) {
//创建MyThread的对象
MyThread th1 = new MyThread();
MyThread th2 = new MyThread();
th1.setName("线程1");
//通过对象调用start()方法开启线程
th1.start();
th2.setName("线程2");//设置线程名称
th2.start();
}
}
//定义一个类继承Thread类
class MyThread extends Thread{
//重写run()方法
@Override
public void run() {
//获取当前线程的名称
String name = Thread.currentThread().getName();
System.out.println(name+":需要执行的代码!");
}
}
/*注意:
a: 开启线程调用的是start()方法,不是run()方法,调用run()方法仅仅只是将run方法执行一遍,并没有开启线程;
b: run()方法中一般执行比较耗时的代码
*/
2、方式二
public class Blog1 {
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
//将Runnable子类对象作为参数传给Thread,获取Thread的对象
Thread th1 = new Thread(runnable);
th1.setName("线程1");//设置线程名称
//也可通过构造方法设置线程名称
Thread th2 = new Thread(runnable, "线程2");
//开启线程
th1.start();
th2.start();
}
}
//定义一个类实现Runnable接口
class RunnableImpl implements Runnable{
@Override
public void run() {
String name = Thread.currentThread().getName();
System.out.println(name+"需要执行的代码");
}
}
3、方式三
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Blog2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable = new MyCallable();
FutureTask<Integer> task = new FutureTask<>(myCallable);
//将task对象传递给Thread有参构造,获取Thread对象
Thread th1 = new Thread(task,"线程1");
th1.start();//开启线程
Integer sum = task.get();//获取call方法返回值
System.out.println(sum);//49995000
}
}
//定义一个类实现Callable接口
class MyCallable implements Callable<Integer>{
//实现call()方法
@Override
public Integer call() throws Exception {
int sum=0;
for (int i = 0; i < 10000; i++) {
sum+=i;
}
return sum;
}
}
三、线程控制
1、设置线程优先级
public final int getPriority() //获取线程的优先级
public final void setPriority(int newPriority)//设置线程的优先级,默认优先级为5,最高优先级为10,最低为1
//注意:线程优先级高只是抢占CPU时间片的概率大,而不是该线程就一定比其他线程先抢占,多线程抢占CPU执行权具有随机性,不确定性,一两次运行不能说明问题。
//演示
th1.setPriority(Thread.MIN_PRIORITY);//设置为最低优先级
int priority1 = th1.getPriority();
System.out.println(priority1);//1
th2.setPriority(Thread.MAX_PRIORITY);//设置为最低优先级
int priority2 = th2.getPriority();
System.out.println(priority2);//10
//获取主线程的优先级
int priority = Thread.currentThread().getPriority();
System.out.println(priority);//5
2、线程休眠
public static void sleep(long millis) //让线程休眠millis毫秒
3、加入线程
public final void join() //等待该线程执行完毕了以后,其他线程才能再次执行;注意事项: 在线程启动之后,再调用该方法
//演示
//可以实现让多个线程串行执行
th1.start();
th1.join();
th2.start();
th2.join();
th3.start();
th3.join();
4、礼让线程
public static void yield() //暂停当前正在执行的线程对象,并执行其他线程。
注意:线程礼让是暂停当前正在执行的线程,由于暂停的时间是相当短的,如果在这个线程暂停完毕以后,其他的线程还没有抢占到CPU的执行权,那么这个时候这个线程就会再次和其他线程抢占CPU的执行权.
5、守护线程
public final void setDaemon(boolean on):
//将该线程标记为守护线程或用户线程。当所有的用户线程都结束之后,Java 虚拟机就会退出,不管是否还有未完成的守护线程。
//注意:该方法必须在启动线程前调用。
6、中断线程
public final void stop(): //停止线程的运行(已过时)
public void interrupt(): //中断线程的阻塞状态
//演示
public class Blog1 {
public static void main(String[] args){
RunnableImpl runnable = new RunnableImpl();
Thread th1 = new Thread(runnable,"线程1");
th1.start();
th1.interrupt();//中断此线程阻塞状态
}
}
class RunnableImpl implements Runnable{
@Override
public void run() {
try {
Thread.sleep(1000);//让线程休眠1000毫秒
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name+"需要执行的代码");
}
}
运行结果:
java.lang.InterruptedException: sleep interrupted //中断异常:休眠被interrupted终止
at java.lang.Thread.sleep(Native Method)
at myblog1.RunnableImpl.run(Blog1.java:17)
at java.lang.Thread.run(Thread.java:748)
线程1需要执行的代码
Process finished with exit code 0
7、线程间的等待唤醒机制
Object 类中
void wait () 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法前,导致当前线程等待。
void wait (long timeout) 在其他线程调用此对象的 notify () 方法或 notifyAll () 方法,或者超过指定的时间量前,导致当前线程等待。
void notify () 唤醒在此对象监视器上等待的单个线程。
void notifyAll () 唤醒在此对象监视器上等待的所有线程。
public class MyTest {
public static void main(String[] args) {
//生产线程生产一个资源,通知消费线程消费一个资源,
//消费线程消费完一个资源,再通知生产线程生产一个资源
Student student = new Student();
new SetThread(student).start();
new GetThread(student).start();
}
}
class SetThread extends Thread{
private static Student student;
private static int i;
public SetThread(Student student) {
this.student=student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if(student.flag){
try {
student.wait();//有资源,当前线程进入等待状态,等待消费线程消费资源,并释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//模拟生产资源
if(i%2==0){
student.name="张三";
student.age=23;
}else {
student.name="李四";
student.age=24;
}
i++;
student.flag=true;//上面生产了资源,修改标记
student.notify();//唤醒消费线程等待状态,让其消费资源
}
}
}
}
class GetThread extends Thread{
private static Student student;
public GetThread(Student student) {
this.student=student;
}
@Override
public void run() {
while (true) {
synchronized (student) {
if(!student.flag){
try {
student.wait();//没有资源,当前线程进入等待状态,等待生产线程生产资源,并释放锁
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(student.name+"=="+student.age);//打印模拟消费资源
student.flag=false;//上面了消费资源,修改标记
student.notify();//唤醒生产线程等待状态,让其生产资源
}
}
}
}
class Student{
public String name;
public int age;
public boolean flag=false;//true表示有资源(即学生信息),false表示没有资源
}
运行结果:
张三==23 李四==24 张三==23 李四==24 张三==23 ...(死循环)
8、线程状态转换图
四、线程安全问题
判断一个多线程应用程序是否有问题的标准:
- a: 是否是多线程环境
- b: 是否存在共享数据
- c: 是否存在多条语句同时操作共享数据
解决问题:
由于我们要使用多线程环境,和共享数据,所以只能对"多条语句同时操作共享数据"这部分进行处理。
1、解决方式一:使用同步代码块处理
public class Blog1 {
public static void main(String[] args){
RunnableImpl runnable = new RunnableImpl();
new Thread(runnable,"窗口1").start();
new Thread(runnable,"窗口2").start();
new Thread(runnable,"窗口3").start();
}
}
class RunnableImpl implements Runnable{
private static int num=100;//定义票的数量
private static Object obj=new Object();//定义共享锁对象
@Override
public void run() {
while (true){
//同步代码块作用:当一个线程正在执行同步代码块时,其他线程必须等待其执行完,才能执行此代码块
synchronized (obj){
try {
Thread.sleep(30);//让线程休眠30毫秒,模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
if(num>=1){
System.out.println(Thread.currentThread().getName()+"已出售第"+(num--)+"张票!");
}else {
break;
}
}
}
}
}
同步代码块格式:
synchronized(锁对象){ //这里的锁对象可以为任意对象
需要同步的代码;
}
同步的好处:解决了多线程的安全问题。
同步的弊端:当线程相当多时,因为每个线程都会去判断同步上的锁,这是很耗费资源的,无形中会降低程序的运行效率。
2、解决方式二:使用同步方法
public class Blog1 {
public static void main(String[] args) {
RunnableImpl runnable = new RunnableImpl();
new Thread(runnable, "窗口1").start();
new Thread(runnable, "窗口2").start();
new Thread(runnable, "窗口3").start();
}
}
class RunnableImpl implements Runnable {
private static int num = 100;//定义票的数量
@Override
public void run() {
while (true) {
sell();
if (num < 1) {
break;
}
}
}
//同步方法
private synchronized void sell() {
if (num>=1) {
try {
Thread.sleep(30);//让线程休眠30毫秒,模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
String name = Thread.currentThread().getName();
System.out.println(name + "已出售第" + (num--) + "张票!");
}
}
}
注意:同步方法和同步代码块类似,不过使用锁对象不同,同步代码块锁对象为任意对象,而同步方法锁对象是this,静态同步方法锁对象是字节码文件(A.class)
3、解决方式三:使用Lock锁
import java.util.concurrent.locks.ReentrantLock;
public class Blog2 {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread, "窗口1").start();
new Thread(myThread, "窗口2").start();
new Thread(myThread, "窗口3").start();
}
}
class MyThread implements Runnable {
private static int num = 100;//定义票的数量
static ReentrantLock lock = new ReentrantLock();//创建锁对象
@Override
public void run() {
while (true) {
lock.lock();//开启锁
try {
Thread.sleep(30);//让线程休眠30毫秒,模拟网络延迟
} catch (InterruptedException e) {
e.printStackTrace();
}
if (num >= 1) {
System.out.println(Thread.currentThread().getName() + "已出售第" + (num--) + "张票!");
} else {
//此处也应关闭锁,因为程序执行到这,会直接跳出循环,不执行最后的关锁语句,若不关闭锁,则会出现死锁现象
lock.unlock();
break;
}
lock.unlock();//关闭锁
}
}
}
拓展:死锁现象
public class MyTest {
public static void main(String[] args) {
new MyThread(true).start();
new MyThread(false).start();
}
}
interface ObjectUtils{
Object objA=new Object();
Object objB=new Object();
}
class MyThread extends Thread{
boolean flag;
public MyThread(boolean flag) {
this.flag=flag;
}
@Override
public void run() {
if(flag){
synchronized (ObjectUtils.objA){
System.out.println("true====ObjA 进来了");
synchronized (ObjectUtils.objB){
System.out.println("true====ObjB进来了");
}
} //objA不释放,下面的objA同步代码块执行不了
}else{
synchronized (ObjectUtils.objB) {
System.out.println("false====ObjB 进来了");
synchronized (ObjectUtils.objA) {
System.out.println("false====ObjA 进来了");
}
}//objB不释放,上面的objB同步代码块执行不了
}
}
}
4、volatile关键字
JVM的内存模型:
Java内存模型规定了所有的变量都存储在主内存中。每条线程中还有自己的工作内存,线程的工作内存中保存了被该线程所使用到的变量(这些变量是从主内存中拷贝而来)。
线程对变量的所有操作(读取,赋值)都必须在工作内存中进行。
不同线程之间也无法直接访问对方工作内存中的变量,线程间变量值的传递均需要通过主内存来完成。
volatile关键字:当多个线程操作共享数据时,可以保证内存中的数据可见。即相当于若工作内存中的变量发生改变,则会立即刷回主内存。
5、CAS算法
概述:CAS (Compare-And-Swap) 是一种硬件对并发的支持,针对多处理器操作而设计的处理器中的一种特殊指令,用于管理对共享数据的并发访问。CAS 是一种无锁的非阻塞算法的实现。
java.util.concurrent.atomic 包下提供了一些原子操作的常用类:
AtomicBoolean 、 AtomicInteger 、 AtomicLong 、 AtomicReference
AtomicIntegerArray 、 AtomicLongArray
AtomicMarkableReference
AtomicReferenceArray
import java.util.concurrent.atomic.AtomicInteger;
public class MyTest1 {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(new MyRunnable()).start();
}
}
}
class MyRunnable implements Runnable{
static AtomicInteger i=new AtomicInteger(1);//给原子操作数i赋初值1
@Override
public void run() {
while (true) {
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
//让i自加1,并打印
System.out.println(Thread.currentThread().getName()+":"+i.getAndIncrement());
}
}
}
运行结果:
Thread-0:1 Thread-2:2 Thread-1:3 Thread-3:4 Thread-7:5 Thread-5:6 ...