多线程
一、三种实现方式
1.继承Thread类
-
步骤
- 定义一个Thread子类;
- 覆盖run方法(线程执行事件方法);
- 创建该线程的一个实例:Thread t=new MyThread();
- 启动线程t1.start;
-
实例:
public class Main3 extends Thread{ int a; Main3(int a) { this.a=a; } @Override public void run() { super.run(); while (true) System.out.println(a); } public static void main(String[] args) { Thread t=new Main3(1); t.start(); Thread t1=new Main3(2); t1.start(); } }
2. 实现Runable接口
-
步骤
- 定义一个实现Runable接口的类,在类中实现run()方法(线程执行事件的方法)。
- 创建一个上述类的对象:Thread t=new Thread(new MyThreadt.start());
- 调用start 方法:t.start();
-
实例:
public class Main2 implements Runnable { int a; Main2(int a) { this.a = a; } @Override public void run() { while (true) System.out.println(a); } public static void main(String[] args) { Thread t = new Thread(new Main2(1)); t.start(); Thread t2 = new Thread(new Main2(2)); t2.start(); } }
实现Runnable接口比继承Thread类所具有的优势:
-
适合多个相同的程序代码的线程去处理同一个资源
-
可以避免java中的单继承的限制
-
增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
-
线程池只能放入实现Runable或callable类线程,不能直接放入继承Thread的类
3.实现Callable接口,并与Future结合使用 (可获取线程返回值)
-
实现步骤:
-
创建 Callable 接口的实现类,并实现 call() 方法,该 call() 方法将作为线程执行体,并且有返回值。
-
创建 Callable 实现类的实例,使用 FutureTask 类来包装 Callable 对象,该 FutureTask 对象封装了该 Callable 对象的 call() 方法的返回值。
-
使用 FutureTask 对象作为 Thread 对象的 target 创建并启动新线程。
-
调用Tread对象的start()方法启动线程,调用 FutureTask 对象的 get() 方法来获得子线程执行结束后的返回值。
-
-
实例:
public class CallableThreadTest implements Callable<Integer> { @Override public Integer call() throws Exception { int i = 0; for(;i<100;i++) { System.out.println(Thread.currentThread().getName()+" "+i); } return i; } public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask<Integer> ft = new FutureTask<>(new CallableThreadTest()); for(int i = 0;i < 100;i++) { System.out.println(Thread.currentThread().getName()+" 的循环变量i的值"+i); if(i==20) { new Thread(ft).start(); } } System.out.println("子线程的返回值:"+ft.get()); } }
二、多线程相关方法
-
线程睡眠:Thread.sleep(long millis)方法,使线程转到阻塞状态。millis参数设定睡眠的时间,以毫秒为单位。
-
线程等待:Object类中的wait()方法,导致当前的线程等待,直到其他线程调用此对象的 notify() 方法或 notifyAll() 唤醒方法。
-
线程唤醒:Object类中的notify()方法,唤醒在此对象监视器上等待的单个线程。如果所有线程都在此对象上等待,则会选择唤醒其中一个线程。选择是任意性的,并在对实现做出决定时发生。
注意:- 从语法角度来说就是Obj.wait(),Obj.notify必须在synchronized(Obj){…}语句块内。
- notify()调用后,并不是马上就释放对象锁的,而是在相应的synchronized(){}语句块执行结束,自动释放锁后,JVM会在wait()对象锁的线程中随机选取一线程,赋予其对象锁,唤醒线程,继续 执行
- Thread.sleep()与Object.wait()二者都可以暂停当前线程,释放CPU控制权,主要的区别在于Object.wait()在释放CPU同时,释放了对象锁的控制。(拥有对象锁才能继续执行)
-
线程让步:Thread.yield() 方法, yield()做的是让当前运行线程回到可运行状态,以允许具有相同优先级的其他线程获得运行机会。因此,使用yield()的目的是让相同优先级的线程之间能适当的轮转执行。但是,实际中无法保证yield()达到让步目的,因为让步的线程还有可能被线程调度程序再次选中。
-
线程加入:join()方法,等待其他线程终止。在当前线程中调用另一个线程的join()方法,则当前线程转入阻塞状态,直到另一个进程运行结束,当前线程再由阻塞转为就绪状态。
-
interrupt():它是向线程发送一个中断信号,让线程在无限等待时(如死锁时)能抛出抛出,从而结束线程,但是如果你吃掉了这个异常,那么这个线程还是不会中断的!
三、线程的优先级
-
每一个 Java 线程都有一个优先级,这样有助于操作系统确定线程的调度顺序。
-
Java 线程的优先级是一个整数,其取值范围是 1 (Thread.MIN_PRIORITY ) - 10 (Thread.MAX_PRIORITY )。
-
默认情况下,每一个线程都会分配一个优先级 NORM_PRIORITY(5)。
-
Thread类的setPriority()和getPriority()方法分别用来设置和获取线程的优先级。
-
具有较高优先级的线程对程序更重要,并且应该在低优先级的线程之前分配处理器资源。但是,线程优先级不能保证线程执行的顺序,而且非常依赖于平台。
四、注意事项
- main方法本身也是一个线程。
- main线程结束后,子线程也会接着执行完毕。
- 在java中,每次程序运行至少启动2个线程。一个是main线程,一个是垃圾收集线程。因为每当使用java命令执行一个类的时候,实际上都会启动一个JVM
五、线程池
1.含义:
线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多资源。
2.两种创建方式
2.1 使用Runnable接口创建线程池
-
步骤:
- 创建线程池对象
- 创建 Runnable 接口子类对象
- 提交 Runnable 接口子类对象
- 关闭线程池
-
实例:
class TaskRunnable implements Runnable{ @Override public void run() { for (int i = 0; i < 10; i++) { System.out.println("自定义线程任务在执行"+i); } } } public class ThreadPool { public static void main(String[] args) { //创建线程池对象 参数5,代表有5个线程的线程池 ExecutorService service = newFixedThreadPool(5); //创建Runnable线程任务对象 TaskRunnable task = new TaskRunnable(); //从线程池中获取线程对象 service.submit(task); System.out.println("----------------------"); //再获取一个线程对象 service.submit(task); //关闭线程池 service.shutdown(); } }
2.2使用Callable接口创建线程池
-
步骤:
- 创建线程池对象
- 创建 Callable 接口子类对象
- 提交 Callable 接口子类对象
- 关闭线程池
-
实例:
class TaskCallable implements Callable<Integer> { @Override public Integer call() throws Exception { for (int i = 0; i < 10; i++) { System.out.println(Thread.currentThread().getName()+" "+i); } return null; } } public class ThreadPoolOfCallable { public static void main(String[] args) { ExecutorService service = Executors.newFixedThreadPool(3); TaskCallable c = new TaskCallable(); TaskCallable c1 = new TaskCallable(); //线程池中获取线程对象,调用run方法 service.submit(c); //再获取一个 service.submit(c1); //关闭线程池 service.shutdown(); } }
六、线程同步
1.synchronized关键字
- synchronized的作用域有二种:
- 是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
- 是某个类的范围,synchronized static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
- 注意
-
除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/区块/},它的作用域是当前对象;
-
synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法;
-
2. 线程同步示例:
-
题意:建立三个线程,A线程打印10次A,B线程打印10次B,C线程打印10次C,要求线程同时运行,交替打印10次ABC。这个问题用Object的wait(),notify()就可以很方便的解决。
-
代码如下:
public class MyThreadPrinter2 implements Runnable { private String name; private Object prev; private Object self; private MyThreadPrinter2(String name, Object prev, Object self) { this.name = name; this.prev = prev; this.self = self; } @Override public void run() { int count = 10; while (count > 0) { synchronized (prev) { synchronized (self) { System.out.print(name); count--; self.notify(); } try { prev.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) throws Exception { Object a = new Object(); Object b = new Object(); Object c = new Object(); MyThreadPrinter2 pa = new MyThreadPrinter2("A", c, a); MyThreadPrinter2 pb = new MyThreadPrinter2("B", a, b); MyThreadPrinter2 pc = new MyThreadPrinter2("C", b, c); new Thread(pa).start(); new Thread(pb).start(); new Thread(pc).start(); } }
-
解析:由于是小白,原博客对此例题的解析看了很久才看懂,所以以小白角度写下对原文的理解:
- 程序一开始pa线程启动:执行打印A->释放A(此次无意义,后面循环有意义)->锁住C(注意:这里不是锁住c线程,而是暂停当前线程,等待C释放后继续执行当前线程,因为后面pc线程执行完毕后同样会释放C,那时候再执行当前线程,就能实现循环打印ABC,一开始我自己理解以为是锁住c进程,走了很多弯路)
- 当线程pa被暂停后,执行线程pb: 执行打印B—>释放B(此次无意义,后面循环有意义)—>锁住A(等待A释放,也就是当pa线程执行完后再执行当前线程)
- 当线程pb暂停后,执行线程pc:执行打印C—>释放C(现在开始就有意义了,因为一开始pa是锁住了C,现在释放后,意味着pa线程可以继续执行)—>锁住B(锁住当前线程,等待线程pb执行完毕后释放B,然后再执行当前线程)
- 由于上一步骤释放了C,所以pa线程启动:打印A->释放A(现在就有意义了,释放A后,线程pb就可以执行)->锁住C(暂停当前线程)后面就一直循环执行线程 pa,pb,pc 依次打印A、B、C
转载文章及博客:
```
线程同步博客:
https://blog.youkuaiyun.com/zyplus/article/details/6672775
https://www.cnblogs.com/yjd_hycf_space/p/7526608.html
//线程学习文章
https://www.runoob.com/java/java-multithreading.html
```