java多线程
java多线程
程序:为了完成特定任务,用某种语言编写的一组指令的集合。即指一段 静 态的代码,静态对象
进程:是程序的一次执行过程(正在运行的一个程序)。是一个动态的过程: 产生、存在、消亡---生命周期。
进程是资源分配的单位。
线程:进程可以进一步细化成线程。是一个程序内部的执行路径
线程是调度和执行的单位,每个线程拥有独立的运行栈和程序计数器 (pc),线程切换开销小
一个进程中的多个线程共享相同的内存单元/内存地址空间----->从同一堆中分配 对象,可以访问相同的变量和对象。这就使得线程间通信变得简便、高效。但是 多个线程操作共享的系统资源可能会带来安全的隐患。
线程的五种状态(作为一个内部的枚举类定义在Thread类内部):
1、新建、初始状态(New):线程对象被创建Thread thread=new Thread()
2、就绪状态(Runnable):被称为可执行状态,线程进入“可运行线程池”,只等待获取CPU的使用权(除CPU之外,其他资源已经就绪)
3、运行状态(Running)线程获取到CPU的使用权(线程只能由Runnable到达Running)
4、阻塞(Bloacked):阻塞状态是因为线程因为某种原因放弃CPU的使用权,暂时停止运行,直到线程进入Runnable才有机会再次Running
5、死亡(Dead)线程执行结束或者因为异常退出了run方法(结束生命周期)
阻塞分为三种:
等待阻塞:
运行的线程执行wait()方法,该线程会释放占用的所有资源,JVM会把该线程放入“等待池中”。进入这个状态后是不能自动唤醒的,必须依靠其他线程调用notify()或者notifyAll()方法才能被唤醒。
同步阻塞:
运行的线程在获取对象的(synchronized)同步锁时,若该同步锁被其他线程占用,则JVM会吧该线程放入“锁池”中。
其他阻塞:
通过调用线程的sleep()或者join()或发出了I/O请求时,线程会进入到阻塞状态。当sleep()状态超时、join()等待线程终止或者超时、或者I/O处理完毕时,线程重新回到就绪状态
内存结构:
单核多核CPU:单核cpu可以多线程,但其实是一种假的多线程(单个时间单元内只能执行一个线程的任务)
一个java应用程序至少需要三个线程:main主线程 gc垃圾回收线程 异常处理线程
并行:多个CPU同时执行多个任务
并发:一个cpu(采用时间片)同时执行多个任务
并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
并发不是真正意义上的“同时进行”,只是CPU把一个时间段划分成几个时间片段(时间区间),然后在这几个时间区间之间来回切换,由于CPU处理的速度非常快,只要时间间隔处理得当,即可让用户感觉是多个应用程序同时在进行。如:打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结束的动作。那么,就可以说听音乐和打游戏是并发的。
并行:
并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
并行和并发的区别:
并发,指的是多个事情,在同一时间段内同时发生了。
并行,指的是多个事情,在同一时间点上同时发生了。并发的多个任务之间是互相抢占资源的。
并行的多个任务之间是不互相抢占资源的、只有在多CPU或者一个CPU多核的情况中,才会发生并行。否则,看似同时发生的事情,其实都是并发执行的。
如果是单核CPU,只使用单个线程完成多个任务,肯定要比使用假多线程(并发处理)要快(任务切换需要时间)
多线程优点:
1、提高应用程序的响应,对图形化界面更有意义,可以增强用户体验。
2、提高计算机系统的cpu利用率
3、改善程序结构,将长切复杂的进程分为多个线程,独立运行方便理解
创建多线程:
1、继承Thread类,重写父类的run方法(这个线程执行的操作放进run方法),创建子类对象,调用start()方法。
public class ThreadTest {
public static void main(String[] args){
MyThread1 myThread1=new MyThread1();
MyThread2 myThread2=new MyThread2();
myThread1.start();
myThread2.start();
}
}
class MyThread1 extends Thread{
@Override
public synchronized void start() {
super.start();
}
@Override
public void run() {
for(int i=1;i<=100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
class MyThread2 extends Thread{
@Override
public synchronized void start() {
super.start();
}
@Override
public void run() {
for(int i=101;i<=200;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
2、实现Runnable接口,实现类中重写run方法,创建实现类对象,将它作为参数放进Thread类的构造器中创建出Thread类的对象,调用Thread对象的start方法
public class ThreadTest {
public static void main(String[] args){
Thread thread1=new Thread(new MyThread(),"geng");
Thread thread2=new Thread(new MyThread(),"peng");
thread1.start();
thread2.start();
}
}
class MyThread implements Runnable{
@Override
public void run() {
for(int i=1;i<=100;i++){
if(i%2==0){
System.out.println(Thread.currentThread().getName()+" "+i);
}
}
}
}
我们执行Thread对象的run为什么会执行MyThread的run方法。我们可以看到start方法的源代码:
public void run() {
if (target != null) {
target.run();
}
}
target就是Thread的一个Runable类型的成员变量。实际上Thread也是实现了Runnable接口,它里面的run方法也是实现的接口中的run方法。
3、实现Callable接口
和Runnable相比,Callable更加强大。它可以有返回值;也可以抛出异常;支持泛型的返回值。但是他的返回值需要借助FutureTask类来获取结果。
Future接口:可以对具体的Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等等
FutureTask是Future接口的唯一实现类,同时实现了Runnable、Future接口,说明他既可以作为Runnable被线程执行,也可以作为Future得到Callable的返回值。
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable myCallable=new MyCallable();
FutureTask futureTask=new FutureTask(myCallable);
Thread thread=new Thread(futureTask);
thread.start();
System.out.println(futureTask.get());
}
}
class MyCallable implements Callable{
@Override
public Object call() throws Exception {
int sum=0;
for(int i=1;i<=100;i++){
if(i%2==0){
sum+=i;
}
}
return sum;
}
}
4、使用线程池创建新线程
提前创建号多个线程,放入线程池中,使用的时候直接获取,使用完放回线程池中。可以避免频繁创建和销毁线程,实现重复使用。
优势:
1、提高响应速度
2、降低资源损耗
3、便于线程管理
jdk5.0之后提供了线程池支持,并且提供了相关API:ExecutorService和Executors
ExecutorService:真正的线程池接口,常见子类ThreadPoolExecutor
属性:
corePoolSize:核心池的大小
maxmumPoolSize:最大线程数
keepAliveTime:线程没有任务时最多保持多久会终止
Executors:工具类、线程池的工厂类,用户创建并且返回不同类型的线程池。
两种方式的对比:
由于java不支持多继承,所以我们一般是是使用实现的方法来创建多线程。
实现接口的方式创建多线程,数据的共享更加自然。(参考买票实例)
Thread类方法:
start方法:
public synchronized void start() {
if (threadStatus != 0)
throw new IllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
}
}
}
启动当前线程,调用当前线程的run方法
run方法:
public void run() {
if (target != null) {
target.run();
}
}
一般需要在子类中重写,在里面定义自己需要进行的操作
currentThread方法:
public static native Thread currentThread();
返回当前执行代码所属于的线程(静态方法)
getName setName方法
获取和设置线程的名字(name属性)
yield方法
使当前线程从running进入runnable
join方法:
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。
比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B(进入堵塞状态)。
t.join(); //调用join方法,等待线程t执行完毕
t.join(1000); //等待 t 线程,等待时间是1000毫秒。
stop:
强制线程结束(不建议使用)
sleep(静态方法):
sleep() 和 yield()方法
这两个方法都定义在Thread.java中sleep()的作用是让当前线程休眠(正在执行的线程主动让出cpu,然后cpu就可以去执行其他任务),即当前线程会从“运行状态”进入到“休眠(阻塞)状态”。sleep()会指定休眠时间,线程休眠的时候会大于或者等于该休眠时间,当时间过后该线程重新被唤醒,他会由“阻塞状态”编程“就绪状态”,从而等待cpu的调度执行,注意:sleep方法只是让出了cpu的执行权,并不会释放对象锁(join方法会释放对象锁)。
yield()的作用是让步,它能够让当前线程从“运行状态”进入到“就绪状态”,从而让其他等待线程获取执行权,但是不能保证在当前线程调用yield()之后,其他线程就一定能获得执行权,也有可能是当前线程又回到“运行状态”继续运行,注意:这里我将上面的“具有相同优先级”的线程直接改为了线程,很多资料都写的是让具有相同优先级的线程开始竞争,但其实不是这样的,优先级低的线程在拿到cpu执行权后也是可以执行,只不过优先级高的线程拿到cpu执行权的概率比较大而已,并不是一定能拿到。
wait 方法是属于 Object 类中的,wait 过程中线程会释放对象锁,只有当其他线程调用 notify 才能唤醒此线程。wait 使用时必须先获取对象锁,即必须在 synchronized 修饰的代码块中使用,那么相应的 notify 方法同样必须在 synchronized 修饰的代码块中使用,如果没有在synchronized 修饰的代码块中使用时运行时会抛出IllegalMonitorStateException的异常
我们可以这样理解,notify是唤醒当前使用这个锁的线程中除本线程之外的随机一个线程(当然优先级高的唤醒几率大),notifyAll就是唤醒当前使用这个锁的线程中除本线程之外的所有线程
等待调用join方法的线程结束之后,程序再继续执行,一般用于*等待异步线程执行完结果之后才能继续运行的场景*。例如:主线程创建并启动了子线程,如果子线程中药进行大量耗时运算计算某个数据值,而主线程要取得这个数据值才能运行,这时就要用到 join 方法了(join方法会释放对象锁)
线程的通信:
实现了两个线程交替打印通过notify和wait方法
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.ReentrantLock;
public class Bank implements Runnable{
private int a=0;
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
while(true){
synchronized (this){
if(a<=100){
notify();
a++;
System.out.println(Thread.currentThread().getName()+" "+a);
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}
class Test{
public static void main(String[] args){
Bank bank=new Bank();
Thread thread1=new Thread(bank,"用户甲");
Thread thread2=new Thread(bank,"用户乙");
thread1.start();
thread2.start();
}
}
线程的调度:
java的调度方法:
同优先级线程组成先进先出的队列
高优先级使用优先调度的抢占式策略(只是说获得CPU使用权的几率比较高)
1、MAX_PRIORITY 10
2、NORM_PRIORITY 5(默认优先级)
3、MIN_PRIORITY 1
getPriority获取当前线程的优先级
setPriority设置当前线程的优先级
public class ThreadTest {
public static void main(String[] args){
MyThread myThread1=new MyThread();
MyThread myThread2=new MyThread();
MyThread myThread3=new MyThread();
myThread1.setName("窗口1");
myThread2.setName("窗口2");
myThread3.setName("窗口3");
myThread2.setPriority(10);
myThread1.start();
myThread2.start();
myThread3.start();
}
}
class MyThread extends Thread{
private static int ticket=100;
@Override
public void run() {
while(ticket>0){
System.out.println(getName()+" "+ticket);
ticket--;
}
}
}
public class ThreadTest {
public static void main(String[] args){
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"窗口一");
Thread thread2=new Thread(myThread,"窗口二");
Thread thread3=new Thread(myThread,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable{
private int ticket=100;
@Override
public void run() {
while(ticket>0){
System.out.println(Thread.currentThread().getName()+" "+ticket);
ticket--;
}
}
}
其实这两个个程序存在线程不安全的隐患。
线程安全问题:
解决方法------当一个线程执行run方法中某一操作的时候,其他线程不能参与进来,只有当该线程操作结束才可以开始。(即使该线程堵塞,其他线程也要等待)
方式1:同步代码块
//同步监听器----锁 任何一个类的对象都可以充当锁(但是多个线程必须公用一把锁)
synchronized(同步监视器){
//需要被同步的代码(操作共享数据的代码)
}
使用了这个同步代码块,相当于将原本多线程的操作,在操作同步代码块的时候,由于这个同步监视器作为资源被占用,其他要是用这个同步监听器的线程只能等待。
public class ThreadTest {
public static void main(String[] args){
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"窗口一");
Thread thread2=new Thread(myThread,"窗口二");
Thread thread3=new Thread(myThread,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable{
private int ticket=100;
private int version=0;
private Object object=new Object();
@Override
public void run() {
while(true){
//如果把同步监听器定义在这里就不是共用一把锁了,就依然无法保证线程安全
//Object object=new Object();
synchronized(object){
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+" "+ticket);
}
}
}
}
}
public class MyObject {
private static MyObject myObject;
private MyObject(){
}
public static MyObject getMyObject(){
/*
性能稍差
synchronized(MyObject.class){
if(myObject==null){
myObject=new MyObject();
}
return myObject;
}
*/
if(myObject==null){
synchronized(MyObject.class){
if(myObject==null){
myObject=new MyObject();
}
//这一句位置放在哪都一样
//return myObject;
}
return myObject;
}
else{
return myObject;
}
}
}
方式2:同步方法(如果操作共享数据的代码全部都在一个方法内,我们可以将这个方法声明成为一个同步方法)
方法返回值类型前面加上synchronized
public class ThreadTest {
public static void main(String[] args){
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"窗口一");
Thread thread2=new Thread(myThread,"窗口二");
Thread thread3=new Thread(myThread,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable{
private int ticket=100;
private int version=0;
@Override
public void run() {
while(true){
show();
}
}
public synchronized void show(){
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+" "+ticket);
}
}
}
同步方法中也有同步监听器,他的同步监听器就是this。此时如果我们使用继承的方式进行多线程的创建,每个线程的this是不同的,我们就需要将该方法改成静态方法,来确保同步监听器的统一。(此时的同步监听器是类.class)
方式3--Lock锁
jdk5.0之后,我们可以通过显示定义同步锁来实现同步,同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具,锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应该获得Lock对象
ReentrantLock类(Lock的实现类)拥有和synchronized相同的并发性和内存语义。
当然我们也要保证这几个线程使用的lock是唯一的(继承的方式创建多线程,将ReentranLock属性设置为静态)。
import java.util.concurrent.locks.ReentrantLock;
public class ThreadTest {
public static void main(String[] args){
MyThread myThread=new MyThread();
Thread thread1=new Thread(myThread,"窗口一");
Thread thread2=new Thread(myThread,"窗口二");
Thread thread3=new Thread(myThread,"窗口三");
thread1.start();
thread2.start();
thread3.start();
}
}
class MyThread implements Runnable{
private int ticket=100;
private ReentrantLock lock=new ReentrantLock(true);
//fair属性表示是否公平,比如说线程a b c访问这个资源,我们会建立一个队列按照队列顺序进行排队访问,
//不会出现线程a执行中,b c等待,结果a执行,又是a执行这种情况
@Override
public void run() {
while(true){
lock.lock();
if(ticket>0){
ticket--;
System.out.println(Thread.currentThread().getName()+" "+ticket);
}
lock.unlock();
}
}
}
synchronized和lock接口两种方式的异同:
相同点:都可以解决线程同步问题
不同点:synchronized中的同步监听器只有当同步代码块或者同步方法执行结束才会释放资源,但是lock可以手动通过unlock释放资源。
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.locks.ReentrantLock;
public class Bank implements Runnable{
private double balance=0;
private ReentrantLock lock=new ReentrantLock();
@Override
public void run() {
//lock.lock();
this.balance+=1000;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("当前用户为"+Thread.currentThread().getName()+" "+this.balance);
//lock.unlock();
}
}
class Test{
public static void main(String[] args){
Bank bank=new Bank();
Thread thread1=new Thread(bank,"用户甲");
Thread thread2=new Thread(bank,"用户乙");
Thread thread3=new Thread(bank,"用户丙");
thread1.start();
thread2.start();
thread3.start();
}
}
线程的死锁:
不同的线程分别占用对方需要的资源不放弃,都在等待对方放弃自己需要的同步资源(共享资源),就形成了死锁。
出现了死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
public class ThreadLock {
public static void main(String[] args){
StringBuffer s1=new StringBuffer();
StringBuffer s2=new StringBuffer();
new Thread(){
public void run(){
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
}
System.out.println(s1);
System.out.println(s2);
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
synchronized (s2){
s1.append("c");
s2.append("3");
synchronized (s1){
s1.append("d");
s2.append("4");
}
System.out.println(s1);
System.out.println(s2);
}
}
}).start();
}
}
以上代码产生死锁的一种情况:
当我们执行第一个线程的时候,使用对象s1作为同步监听器,然后堵塞,此时sleep是不会释放资源的(join会释放)。然后cpu肯定会执行第二个线程他执行完s2锁的那个部分,需要s1的时候,发现该资源没有被释放,然后他就会也进入堵塞状态,等到第一个线程从休眠中醒来,他就会请求s2的资源,两个线程互相占用着彼此的资源形成了死锁。
生产者/消费者问题:
import java.util.concurrent.ThreadLocalRandom;
public class ThreadCP {
public static void main(String[] args){
Clerk clerk=new Clerk();
Customer customer1=new Customer(clerk);
Customer customer2=new Customer(clerk);
Productor productor1=new Productor(clerk);
Productor productor2=new Productor(clerk);
customer1.setName("消费者一号");
customer2.setName("消费者二号");
productor1.setName("生产者一号");
productor2.setName("生产者二号");
customer1.start();
customer2.start();
productor1.start();
productor2.start();
}
}
class Clerk{
private int total=0;
public synchronized void product() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"来生产了");
while(true){
if(total<20){
Thread.sleep(1000);
total++;
notifyAll();
System.out.println(Thread.currentThread().getName()+"生产了"+total+"号商品");
}
else{
wait();
}
}
}
public synchronized void custom() throws InterruptedException {
System.out.println(Thread.currentThread().getName()+"来消费了");
while(true){
if(total>0){
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"消费了"+total+"号商品");
total--;
}
else{
notifyAll();
wait();
}
}
}
}
class Productor extends Thread{
private Clerk clerk;
public Productor(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
clerk.product();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk){
this.clerk=clerk;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
try {
clerk.custom();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}