多线程
多线程的基本概念,及其优点
并发
几个程序在一段时间内交替进行
并行
几个程序在一段时间内同时进行
进程
我们的软件都是存储在硬盘中的,当我们需要打开一个软件时,系统会将软件中我们需要用的数据传递到内存中。(因为内存中数据的传输速度比硬盘快很多倍),程序从硬盘进入到内存中开始运行这一过程叫做进程
线程
当我们启动程序的一个功能时,他就会从内存中开辟一条路径通往cpu,这个路径就叫做线程。cpu就会顺着这个路径来运行程序。
线程是进程的一个执行单元
单线程的弊端
当程序运行过程中一旦出现问题,程序将立即停止运行,不论后续的程序是否会受到这个问题的影响
多线程
一个程序往往有很多功能,当他的几个功能同时执行时,就会开辟多条到cpu的路径。这多条路径就是多线程
多线程的优点
1.执行效率高
2.多个线程之间互不影响
线程的使用
1.分时调度:所有线程轮流使用cpu,平均分配进程的使用时间
2.抢占式调度:优先给优先级高的线程使用cpu,当线程级别相同是,则随机分配。
java中线程就是采用抢占式调度的方法
java中当我们运行我们编写的程序时,jvm会执行main方法,main方法会进入到栈内存
jvm会在栈内存开辟一条路径通向cpu,此时cpu就可以通过这个路径
来执行此线程,main线程又叫做主线程
java中多线程的使用方法既运行原理
Thread类
首先我们创建Thread类的子类,在子类中重写run方法。也就是设置线程的任务,这个线程是用来干什么的。
在主函数中创建Thread类的子类对象,调用子类对象的start方法。即可启动多线程。
不可调用run方法,如果调用run方法,则只是简单的运行run方法。而不是开辟一个新线程
public class demo01thread extends Thread{ //创建一个类继承Thread
@Override
public void run() { //重写run方法
for (int i = 0; i < 5; i++) { //新线程的任务
System.out.println("我是新线程"+i);
}
}
}
主程序
public class Demo01Threadmain {
public static void main(String[] args) {
demo01thread de = new demo01thread(); //创建对象
de.start(); //启动新线程
for (int i = 0; i < 5; i++) { //主函数任务
System.out.println("我是主线程"+i);
}
}
}
Rannable接口
创建一个Rannable的实现类,在实现类中重写run方法(设置线程任务),在主函数中创建实现类对象,再创建Thread类含参对象,传入被重写了run方法的Ranable的实现类作为参数。用Thread类对象调用start方法
public class demo implements Runnable {
@Override
public void run() {
System.out.println("我是新线程");
}
}
主函数
public class Demom {
public static void main(String[] args) {
System.out.println("我是主线程");
demo de = new demo();
Thread th = new Thread(de);
th.start();
}
}
多线程运行原理
当我们使用多线程时,系统内部的运行是这样的
jvm先运行到主函数,此时开辟主线路,cpu在主线程上运行,在运行过程中,碰到了被调用的start方法,此时将会开辟一条新的线路到cpu。这两条线路争夺cpu的使用权运行。
多线程内存运行原理
main函数先进栈开始运行,在main函数进栈运行的过程中,执行到创建了Thread的继承类对象,进入内存创建对象,然后调用start方法,在调用start方法之前,系统一直都是单线程运行。当我们调用start方法时系统会开辟新的栈空间,Start方法会进入新的栈,两个栈开始争夺cpu的使用权运行
多线程常用的类方法
Thread.current()
获取当前线程
public class demo02Tg extends Thread{
@Override
public void run() { //线程任务
System.out.println("我是新线程");
System.out.println(Thread.currentThread()); //获取当前线程
}
}
主函数
public static void main(String[] args) {
demo02Tg de = new demo02Tg();
de.start(); //运行新线程
System.out.println("hello");
System.out.println(Thread.currentThread()); //获取当前线程,也就是主线程
System.out.println("java");
}
getname()
获取线程的名字
Thread类方法,因此必须继承了Thread类或者Thread类的子类才可以使用
public class demo02Tg extends Thread{
@Override
public void run() { //线程任务
System.out.println("我是新线程");
System.out.println(Thread.currentThread()); //获取当前线程
System.out.println(getName()); //获取当前线程的名字
}
}
主函数
public static void main(String[] args) {
demo02Tg de = new demo02Tg();
de.start(); //运行新线程
System.out.println("hello");
System.out.println(Thread.currentThread().getName()); //获取当前线程,也就是主线程的名字
System.out.println("java");
}
setName()
改变线程名字
Thread类方法,因此必须继承了Thread类或者Thread类的子类才可以使用
public class demo03sn extends Thread {
public demo03sn(String name) {
super(name);
}
@Override
public void run() {
System.out.println("我是新线程,这是我的新名字");
System.out.println(Thread.currentThread().getName());
}
}
主函数
public static void main(String[] args) {
demo03sn de = new demo03sn("A");
de.start();
System.out.println("我是主线程");
Thread.currentThread().setName("B");
System.out.println("我也有了新名字"+Thread.currentThread().getName());
}
多线程出现的问题及其解决办法
安全问题
但一个数据每次被调用都会发生该变时
此时如果多线程共享了这个数据,由于多线程在运行时互不干扰,就可能造成在一个线程调用他时,还没来得及改变,又被另一个线程调用,造成安全问题
解决办法
借用锁对象
synchronize(锁对象){
共享程序块
}
被synchronize包裹的程序块,只能有一个线程在运行
锁对象可以为任意对象,但是要对可能出现安全问题的线程使用相同的锁对象
原理:cpu在线程中运行的过程中遇到synchronize就会检查是否有
锁对象,如果有锁对象那么就会拿到锁对象,进入到同步
执行当中。同步执行结束,cpu归还锁对象。(可以抽象理解
锁对象为一把钥匙,必须有钥匙才可进入同步执行)
当下一个线程在运行的过程中,运行到synchronize也会检查是否
有锁对象,如果没有就会等待,其他线程归还锁对象
弊端:程序运行过程中频繁的判断锁,拿走锁,归还锁程序效率低
public class bksyn implements Runnable {
Object o=new Object(); //定义锁对象
int book=20; //定义初始变量20本书
@Override
public void run() {
while(true){ //死循环,一直卖书
if(book>0){
synchronized (o){ //共享代码块
System.out.println(Thread.currentThread().getName()+"卖出了第"+book+"本书");
book--;
}
}
}
}
}
主函数
public static void main(String[] args) {
bksyn bk = new bksyn();
Thread th = new Thread(bk); //创建Thread对象 bk作为参数
Thread th1 = new Thread(bk);
Thread th2 = new Thread(bk);
th.start(); //开启三个线程
th1.start();
th2.start();
}
调用被synchronize修饰的方法
创建一个被synchronize修饰的方法,方法内部为多线程共享数据代码块,在重写的run方法中调用此方法
原理:与上一个方法的原理相同,只不过这种方法的锁对象,默认为实现类本身
public class bksyn implements Runnable {
Object o=new Object(); //定义锁对象
int book=20; //定义初始变量20本书
@Override
public void run() {
while(true){ //死循环,一直卖书
if(book>0){
sell();
}
}
}
public synchronized void sell(){ //创建方法,被synchronize修饰共享数据,避免安全问题
System.out.println(Thread.currentThread().getName()+"卖出了第"+book+"本书");
book--;
}
}
public static void main(String[] args) {
bksyn bk = new bksyn();
Thread th = new Thread(bk); //创建Thread对象 bk作为参数
Thread th1 = new Thread(bk);
Thread th2 = new Thread(bk);
th.start(); //开启三个线程
th1.start();
th2.start();
}
lock类
lock类有两个方法
1.lock()获取锁
2.unlock()释放锁
使用方法
1.在多线程的类内部创建lock的实现类Reentrantlock
2.在线程可能出现安全问题的代码块前调用lock方法
3.在线程可能出现安全问题的代码块后调用unlock方法
原理:与上述方法原理相同,只不过是手动来获取和释放锁对象
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class bksyn implements Runnable {
Lock l = new ReentrantLock(); //创建lock的实现类
int book=20; //定义初始变量20本书
@Override
public void run() {
while(true){ //死循环,一直卖书
if(book>0){
l.lock(); //获取锁
System.out.println(Thread.currentThread().getName()+"卖出了第"+book+"本书");
book--;
l.unlock(); //释放锁
}
}
}
}
主函数
public class bksynm {
public static void main(String[] args) {
bksyn bk = new bksyn();
Thread th = new Thread(bk); //创建Thread对象 bk作为参数
Thread th1 = new Thread(bk);
Thread th2 = new Thread(bk);
th.start(); //开启三个线程
th1.start();
th2.start();
}
}
线程的几种状态
运行
cpu正在该线程中运行
阻塞
cou在该线程中运行到同步代码块,但是没有获取到锁对象
睡眠
线程主动放弃对cpu的使用权,但是在指定时间后会自动醒来
等待
一般出现在同步代码块中,锁对象主动脱离线程,从而线程失去对cpu的控制权。锁对象将转移到处于阻塞状态的线程
唤醒
将处于等待状态线程唤醒,使他重新获取锁对象,继续运行下面的代码
等待与唤醒代码演示
public class bkwnl {
Object o=new Object(); //借助来保证锁对象唯一
}
public class bkwn extends bkwnl implements Runnable{
private bkwnl o; //保证锁对象唯一,创建他们的继承对象
public bkwn(bkwnl o) { //有参构造,用参数赋值给继承对象来作为锁对象
this.o = o;
}
@Override
public void run() {
synchronized (o){
System.out.println(Thread.currentThread().getName()+"进入等待状态");
try {
o.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+"继续运行");
}
}
}
public class bkwn1 implements Runnable{
private bkwnl o;
public bkwn1(bkwnl o) { //有参构造,将参数作为锁对象
this.o = o;
}
@Override
public void run() {
synchronized (o){
System.out.println(Thread.currentThread().getName()+"我来唤醒你");
o.notify();
}
}
}
主函数
public class bkwnm {
public static void main(String[] args) {
bkwnl bkl = new bkwnl(); //创建对象作为他们的参数
bkwn bk = new bkwn(bkl);
bkwn1 bk1 = new bkwn1(bkl);
Thread th1 = new Thread(bk);
Thread th2 =new Thread(bk1);
th1.start();
th2.start();
}
}
线程池
因为创建出来的一个线程只能用一次很不方便
于是在jdk1.5之后,java内置了线程池,方便我们来使用
线程池原理
线程池内部其实是一个集合,集合中储存的是线程,当我们需要使用线程时,线程池内部的集合就会调用remove方法,将线程池删除出来供我们使用,我们使用完之后他会自动添加回去,从而达到反复使用
线程池的使用方法
线程池工厂(Executors)
1.使用线程池工厂的Executors里边提供的静态方法newFixedThreadPool(返回值用ExecutorService接收)生产出来一个含有指定数量的线程池
ExecutorService 线程池名称=Executors.newFixedThreadPool(数量)
2.创建类实现Rannable接口,重写run方法。(创建线程任务)
3.Executors的submit方法来来传递线程任务
线程池名称.submit(创建实现类对象);
public class demof implements Runnable {
@Override
public void run() {
System.out.println("我是新线程");
}
}
主函数
public class demofm {
public static void main(String[] args) {
ExecutorService ex=Executors.newFixedThreadPool(2); //创建一个线程池,线程池里面有两个线程
demof de = new demof(); //创建线程任务的实现类接口
ex.submit(de); //传递线程任务,并开启线程
ex.submit(de);
}
}
使用完之后也可用线程池的shutdown方法来销毁线程池(一般不建议执行)
线程池中的线程可以重复利用,因为他在用完之后会传递回去