Java多线程入门

it2023-05-05  71

进程和线程

进程是系统中正在运行的一个程序,程序一旦运行就是进程。进程是系统资源分配的基本单位。每个进程都有独立的地址空间,一个进程无法直接访问另一个进程的变量和数据结构。如果要让一个进程访问另一个进程的资源,需要使用进程间通信,如管道、文件、套接字等。 比如Windows系统中我们打开任务管理器查看到每个.exe文件,都是进程 线程是操作系统能够运算调度(CPU调度)的基本单位,线程被包含到进程中,是进程中的实际运作单位。一个进程就是进程中一条单一控制的顺序流。一个进程中有1-n个线程。一个进程中可以并发多个线程,每个线程并发执行不同的任务。

串行、并行、并发

串行: 一个线程执行到底,相当于单线程。 并发:多个线程交替执行,抢占cpu时间片,但是速度很快,从宏观角度看起来像多个线程同时执行。 并行:多个线程在不同核心cpu中同时运行。

Java实现多线程的方式

继承Thread类,重写run方法 public class MyThread1 extends Thread{ private static int a = 1; @Override public void run() { for (int i=0;i<10;i++){ a++; System.out.println(getName()+"修改a="+a); } } public static void main(String[] args) { new MyThread1().start(); new MyThread1().start(); } }

2. 实现Runnable接口,重写run方法

package base.testthread; public class MyThread2 implements Runnable { private static int a = 1; private String name; public MyThread2(String threadName) { this.name = threadName; } public String getName() { return name; } @Override public void run() { for (int i = 0; i < 10; i++) { a++; System.out.println(getName() + "修改a=" + a); } } public static void main(String[] args) { MyThread2 ta = new MyThread2("Thread-a"); MyThread2 tb = new MyThread2("Thread-b"); new Thread(ta).start(); new Thread(tb).start(); } }

实现Callable接口,重写call方法(有返回值) package base.testthread; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class MyThread3 { private static int a = 1; static class CallableImpl1 implements Callable<Integer> { @Override public Integer call() throws Exception { for (int i = 0; i < 10; i++) { a++; System.out.println(Thread.currentThread().getName() + "第" + (i+1)+ "次 修改a=" + a); } return a; } } public static void main(String[] args) throws ExecutionException, InterruptedException { Callable<Integer> callable = new CallableImpl1(); FutureTask<Integer> futureTask = new FutureTask<>(callable); Callable<Integer> callable2 = new CallableImpl1(); FutureTask<Integer> futureTask1 = new FutureTask<>(callable2); new Thread(futureTask, "thread0").start(); new Thread(futureTask1, "thread1").start(); System.out.println(Thread.currentThread().getName() + " " + futureTask.get()); System.out.println(Thread.currentThread().getName() + " " + futureTask1.get()); } }

线程池(有返回值) package base.testthread; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; //线程线池,类似数据库连接池 //避免创建和销毁造成的开销 /* * Executor 负责现成的使用和调度的根接口 * |--ExecutorService 线程池的主要接口 * |--ThreadPoolExecutor 线程池的实现类 * |--ScheduledExecutorService 接口,负责线程的调度 * |--ScheduledThreadPoolExecutor (extends ThreadPoolExecutor implements ScheduledExecutorService) * * Executors 线程池工具类 提供创建线程池的方法 * */ public class MyThreadPool { private static int a = 1; public static void main(String[] args) { //使用Executors工具类中的方法创建线程池 ExecutorService pool = Executors.newFixedThreadPool(5); //为线程池中的线程分配任务,使用submit方法,传入的参数可以是Runnable的实现类,也可以是Callable的实现类 for (int i = 0; i < 5; i++) { pool.submit(new Runnable() { @Override public void run() { for (int i = 0; i < 5; i++) { a++; System.out.println(Thread.currentThread().getName() + " 修改a=" + a); } } }); } pool.shutdown(); } }

Thread类的start()和run()方法的区别

start()方法用来启动线程,真正实现类多线程运行。无需等待run()方法体执行完毕,可以直接执行下面代码;通过调用Thread类的start()方法来启动一个线程,此时此线程进入就绪状态。然后通过此Thread类调用run()方法完成其运行操作,这里的run()成为方法体,它包含了要执行的线程的内容,run方法结束,此线程终止,然后cpu调度其他线程run()方法当作普通方法的调用。程序还是要顺序执行

线程的几种状态

要想实现多线程,必须在主线程中创建新的线程对象。任何线程一般具有5种状态,即创建,就绪,运行,阻塞,终止 创建 在程序中用构造方法创建一个线程对象后,新的线程对象就处于新建状态,此时该线程以及有了相应的内存空间和资源,但还处于不可运行状态,新建一个线程对象可采用Thread类构造方法实现

Thread thread=new Thread();

就绪/可执行 新建线程对象后,调用该线程的start(),当线程启动后,线程处于就绪状态,此时,线程进入任务队列排队,等待cpu服务,这表明它具有了运行条件。 执行 当cpu通过轮询等方式从任务队列中选中了该线程,该线程就进入执行状态,调用run()方法,执行其逻辑 阻塞 一个正在执行的线程在某些特殊情况下(人为挂起或执行耗时的IO操作),会使得CPU暂停本线程执行,进入阻塞状态。在可执行状态下,如果调用sleep()、suspend()、wait()等方法,线程都将进入阻塞状态,,发生阻塞时线程不能进入排队队列,只有当引起阻塞的原因被消除后,线程才可以转入就绪状态。 死亡 线程调用stop()方法或run()方法体结束后,就处于死亡状态,处于死亡状态的线程不具有继续运行的能力。

线程的操作方法

线程的强制执行 在线程操作中,可以使用join()方法让一个线程强制执行,在线程强制执行期间,其他线程无法运行,必须等待该线程执行完成后其他线程才能继续执行。

package base.testthread; public class TestThreadForceRun { private static int a=1; public static void main(String[] args) { Thread t0=new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始执行"); for (int i=0;i<20;i++){ a++; System.out.println(Thread.currentThread().getName()+" a="+a); } } },"Thread-0"); Thread t1=new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+"开始执行"); for (int i=0;i<20;i++){ try { if (a>=10){ t0.join();//如果当前执行到t1,如果a>=10,强制让t0线程执行完再执行t1 } } catch (InterruptedException e) { e.printStackTrace(); } a++; System.out.println(Thread.currentThread().getName()+" a="+a); } } },"Thread-1"); t0.start(); t1.start(); } }

线程的休眠 在程序中允许一个线程进行暂时的休眠,使用Thread.sleep()实现

package base.testthread; public class TestThreadSleep { private static int a = 1; public static void main(String[] args) { Thread t0 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始执行"); for (int i = 0; i < 20; i++) { try { Thread.sleep(1000);//线程睡眠1s } catch (InterruptedException e) { e.printStackTrace(); } a++; System.out.println(Thread.currentThread().getName() + " a=" + a); } } }, "Thread-0"); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName() + "开始执行"); for (int i = 0; i < 20; i++) { try { Thread.sleep(1000);//线程睡眠1s } catch (InterruptedException e) { e.printStackTrace(); } a++; System.out.println(Thread.currentThread().getName() + " a=" + a); } } }, "Thread-1"); t0.start(); t1.start(); } }

线程的中断 使用interrupt方法中断线程,实际上interrupt方法并不会中断线程,这是为该线程设置一个中断标志, isInterrupted方法判断是否中断,也就是判断是否设置了中断标志 interrupted方法判断是否中断并清除中断标志

package base.testthread; public class TestThreadInterrupt { private static int a = 1; static class MyThread extends Thread { @Override public void run() { super.run(); System.out.println(Thread.currentThread().getName() + "开始执行"); for (int i = 0; i < 20; i++) { System.out.println("线程是否被中断了:" + this.isInterrupted()); // if (this.isInterrupted()) { // System.out.println("线程被中断了"); // break; // } a++; System.out.println(Thread.currentThread().getName() + " a=" + a); } } } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); myThread.interrupt(); } }

线程并不会停止执行

在主线程中调用interrupt方法,在Thread-0线程中使用isInterrupt方法判断该线程是否中断可以看出Thread-0始终没有中断

因此我们可以根据isInterrupted自行做处理,下面设置当isInterrupted时候,跳出循环

package base.testthread; public class TestThreadInterrupt { private static int a = 1; static class MyThread extends Thread { @Override public void run() { super.run(); System.out.println(Thread.currentThread().getName() + "开始执行"); for (int i = 0; i < 20; i++) { System.out.println("线程是否被中断了:" + this.isInterrupted()); if (this.isInterrupted()) { System.out.println("线程被中断了"); break; } a++; System.out.println(Thread.currentThread().getName() + " a=" + a); } } } public static void main(String[] args) { MyThread myThread = new MyThread(); myThread.start(); myThread.interrupt(); } }

守护线程 Java线程分为用户线程和守护线程。 定义:守护线程是一个比较特殊的线程,主要用来做程序中后台调度和支持工作。当JVM虚拟机中不存在非守护线程时候,守护线程随着JVM一同结束工作。 设置方法:通过setDaemon(true)来设置线程为“守护线程”;将一个用户线程设置为守护线程的方式是在启动线程 之前 用线程对象的setDaemon方法。 优先级:守护线程的优先级比较低,用户为系统中的其他对象和线程提供服务, 例子:垃圾回收器GC就是经典的守护线程,当程序中不再运行任何线程,程序就不会再产生垃圾,垃圾回收器也就无事可做,所以当垃圾回收线程是JVM上仅剩的线程时,垃圾回收线程会自动离开。它始终在低级别的状态中运行,用于实时监控和管理系统中的可回收资源。 生命周期:当JVM所以线程只剩下守护线程时候,JVM会直接退出,只要存在非守护线程,JVM就不会退出。

package base.testthread; public class TestDaemon { private static int a=0; static class MyThread extends Thread{ @Override public void run() { super.run(); System.out.println(Thread.currentThread().getName()+" has running."); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); }finally { a++; System.out.println(Thread.currentThread().getName()+" finally a="+a); } } } public static void main(String[] args) { MyThread myThread=new MyThread(); myThread.setDaemon(true); myThread.start(); } }

因为该线程为守护线程,所以直接结束,不输出任何结果 如果添加一个非守护线程

package base.testthread; public class TestDaemon { private static int a=0; static class MyThread extends Thread{ @Override public void run() { super.run(); System.out.println(Thread.currentThread().getName()+" has running."); try { Thread.sleep(1000); }catch (InterruptedException e){ e.printStackTrace(); }finally { a++; System.out.println(Thread.currentThread().getName()+" finally a="+a); } } } public static void main(String[] args) { MyThread myThread=new MyThread(); MyThread myThread1=new MyThread(); myThread.setDaemon(true); myThread.start(); myThread1.start(); } }

线程的优先级

线程启动后纳入到线程调度,线程时刻处于被动获取CPU时间片而无法主动获取。我们可以通过调整线程的优先级来最大程度的干涉线程调度分配时间片的几率。理论上优先级越高的线程获取CPU时间片的次数越多。调用线程的方法:setPriority()方法来设置优先级。线程优先级有10个等级,分别用整数1-10表示。其中1位最低优先级,10为最高优先级,5为默认值。 通过setPriority方法可设置线程的执行顺序 package base.testthread; public class TestThreadPriority { private static int a=0; static class MyThread extends Thread{ @Override public void run() { super.run(); for (int i=0;i<10;i++){ a++; System.out.println(Thread.currentThread().getName()+" a="+a); } } } public static void main(String[] args) { MyThread t0=new MyThread(); MyThread t1=new MyThread(); MyThread t2=new MyThread(); t0.setPriority(Thread.MAX_PRIORITY); t1.setPriority(Thread.MIN_PRIORITY); t2.setPriority(Thread.NORM_PRIORITY); t0.start(); t1.start(); t2.start(); } }

线程的礼让 调用当前线程的yield()方法,让出CPU资源交给其他线程执行

package base.testthread; public class TestYield { private static int a=0; public static void main(String[] args) { Thread t0=new Thread(new Runnable() { @Override public void run() { for (int i=0;i<20;i++){ a++; System.out.println(Thread.currentThread().getName()+" a="+a); } } }); Thread t1=new Thread(new Runnable() { @Override public void run() { for (int i=0;i<20;i++){ if (a<20){ Thread.currentThread().yield(); System.out.println(Thread.currentThread().getName()+"礼让,a="+a); } else { a++; System.out.println(Thread.currentThread().getName()+" a="+a); } } } }); t0.start(); t1.start(); } }

可以看出Thread-1线程只要执行了yield()方法,会立刻转向执行Thread-0线程 同步及死锁 一个JAVA多线程程序如果是通过Runnable接口实现,那么意味着类中的属性被多个线程共享,那么这样就会造成一种问题,如果这多个线程要操作同一个资源时就有可能出现资源同步问题。 先有两个线程,每个线程对a做100000次加操作,正常情况下最后一个a应该为200000

package base.testthread; public class TestThreadSync { private static int a = 0; static class MyThread extends Thread { @Override public void run() { for (int i = 1; i <=100000; i++) { addA(); System.out.println(Thread.currentThread().getName() + " a=" + a); } } } public static void addA() { a++; } public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); } }

运行结果发现每次最后一个a不尽相同,最后一个a<=2000000,原因在于这两个线程可能在某个时间同时进行了a++操作,这样a就少加了一次,所以a<=200000 那么如何解决该问题? 1.同步方法 给目标方法加锁

synchronized 类方法(){//发现synchronized 和public/private/protected好像可以交换位置 } package base.testthread; public class TestThreadSync { private static int a = 0; static class MyThread extends Thread { @Override public void run() { for (int i = 1; i <=100000; i++) { addA(); System.out.println(Thread.currentThread().getName() + " a=" + a); } } } public synchronized static void addA() { a++; } public static void main(String[] args) { MyThread t1 = new MyThread(); MyThread t2 = new MyThread(); t1.start(); t2.start(); } }

同步代码块

synchronized(对象){ } package base.testthread; public class TestThreadSyncCodeBlock extends Thread{ private static int a = 0; public static void main(String[] args) { Object o=new Object(); Thread t0=new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 100000; i++) { synchronized (o){ a++; System.out.println(Thread.currentThread().getName() + " a=" + a); } } } }); Thread t1=new Thread(new Runnable() { @Override public void run() { for (int i = 1; i <= 100000; i++) { synchronized (o){ a++; System.out.println(Thread.currentThread().getName() + " a=" + a); } } } }); t0.start(); t1.start(); } }

线程死锁

不同线程占用对方的同步资源不放弃,都在等待对方的同步资源从而形成死锁。 比如有两个对象o1,o2,线程1首先获得o1这把锁,然后等待o2释放资源后获得o2这把锁,操作完o2再释放o1,线程2首先获得o2,然后等待o1是否资源后获得o1这把锁,操作完o1再释放o2,这样线程1中在等待o2这把锁的释放,线程2中在等待o1这把锁的释放,就会形成循环等待的现象,就产生了死锁 产生死锁的必要条件: 互斥(两个事件为互斥事件)、不可剥夺(不可以强行占用一个未完成线程的资源)、占有并等待(占有当前资源并等待其他线程释放自己所需资源)、循环等待(不断循环,等待对方释放资源)

package base.testthread; public class TestThreadDeadLock { private static Object o1=new Object(); private static Object o2=new Object(); public static void main(String[] args) { Thread t1=new Thread(new Runnable() { @Override public void run() { synchronized (o1){ System.out.println(Thread.currentThread().getName()+" has get object o1"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o2){ System.out.println(Thread.currentThread().getName()+" has get object o2"); } } } },"Thread-1"); Thread t2=new Thread(new Runnable() { @Override public void run() { synchronized (o2){ System.out.println(Thread.currentThread().getName()+" has get object o2"); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (o1){ System.out.println(Thread.currentThread().getName()+" has get object o1"); } } } },"Thread-2"); t1.start(); t2.start(); } }

如何避免死锁产生

避免一个线程同时获得多个锁;避免一个线程在锁内同时占据多个资源,尽量保证每个锁只占据一个资源;尝试使用定时锁,使用lock.tryLock(timeout)来代替使用内部锁机制;对于数据库锁,加锁和解锁必须在一个数据库连接里,否则会出现解锁失败的情况。 使用wait()和notify()方法实现线程之间的通信 简单模拟生产者消费者问题,生产者生产产品,当缓冲区满了,生产者线程使用wait方法释放锁并阻塞,直到消费者消费了产品,即通过notify()方法唤醒生产者线程,消费者消费产品,当缓冲区没有产品,消费者线程使用wait方法释放锁并阻塞,直到生产者生产了产品,即通过notify()方法唤醒消费者线程 package base.testthread; public class TestProducerConsumer { static class Buffer{ private int product; private static int MAX_CAPACITY=20; private static int MIN_CAPACITY=0; public synchronized void addProduct(){ if (product>=MAX_CAPACITY){//缓冲产品超过最大容量时候,停止生产 try { System.out.println("Buffer is full."+Thread.currentThread().getName()+" has been waiting."); wait();//当前线程释放对象锁并进入等待(阻塞)状态。 } catch (InterruptedException e) { e.printStackTrace(); } } else { product++; System.out.println(Thread.currentThread().getName()+" has been producing "+ product+" product."); notifyAll(); //唤醒所有正在等待相应对象锁的线程,使它们进入就绪队列,以便在当前线程释放锁后竞争锁,进而得到CPU的执行。 } } public synchronized void removeProduct(){ if (product<=MIN_CAPACITY){//缓冲区没有产品时候 try { System.out.println("Buffer is empty."+Thread.currentThread().getName()+" has been waiting. "); wait(); }catch (InterruptedException e){ e.printStackTrace(); } } else { System.out.println(Thread.currentThread().getName()+" has been consuming "+ product+" product."); product--; notifyAll(); } } } static class Producer implements Runnable{ private Buffer buffer; public Producer(Buffer buffer){ this.buffer=buffer; } @Override public void run() { while (true){ try { Thread.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); } buffer.addProduct(); } } } static class Consumer implements Runnable{ private Buffer buffer; public Consumer(Buffer buffer){ this.buffer=buffer; } @Override public void run() { try { Thread.sleep(120); } catch (InterruptedException e) { e.printStackTrace(); } buffer.removeProduct(); } } public static void main(String[] args) { //这里使用了一个生产者线程,两个消费者模拟 Buffer buffer=new Buffer(); Producer producer=new Producer(buffer); Consumer c1=new Consumer(buffer); Consumer c2=new Consumer(buffer); Thread t1=new Thread(producer,"Thread-producer"); Thread t2=new Thread(c1,"Thread-consumer-1"); Thread t3=new Thread(c2,"Thread-consumer-2"); t1.start(); t2.start(); t3.start(); } }

最新回复(0)