一、多线程
-
多线程、进程与程序概念区别 程序:一段静态代码,静态对象,完成特定任务的一组指令的集合
-
进程:(正在运行的程序)一个动态的过程,有它自身的产生存亡和消亡的过程——生命周期
-
线程:将进程进行细化,是程序内部的一条执行路径;每个线程都有自己的程序计数器和虚拟运行栈,共用进程的方法区和堆空间。
-
单核CPU和多核CPU理解:
a.单核CPU:是一种假的多线程,因为在一个时间单元内也只能执行一个线程的任务,通过挂起和调用来实现假的多线程。多核CPU:更好的发挥多线程的效率。
b.一个Java应用程序至少有:main主线程。gc垃圾回收线程,异常处理线程 -
并行与并发:并行指多个CPU同时执行多个任务;并发指同一个CPU同时执行多个任务
二、线程的创建和使用
多线程的创建,方式一:继承于Thread类
1、创建一个继承于Thread的子类
2、重写Thread类的run()方法——>将此线程将执行的操作声明在run()中
3、创建Thread类的子类对象
4、通过此对象调用start()
package com.xyb.demo01;
/**
* 多线程的创建,方式一:继承于Thread类
* 1、创建一个继承于Thread的子类
* 2、重写Thread类的run()方法——>将此线程将执行的操作声明在run()中
* 3、创建Thread类的子类对象
* 4、通过此对象调用start()
* 例子:遍历100以内的所有偶数
*/
//1、创建一个继承于Thread类的子类
class MyThread extends Thread{
//2、重写父类的run方法
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2==0)
System.out.println(Thread.currentThread().getName()+":"+i);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread2 extends Thread{
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if(i%2==1)
System.out.println(Thread.currentThread().getName()+":"+i);
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class MultiThreading {
public static void main(String[] args) {
//3、创建Thread类的子类对象
MyThread myThread=new MyThread();
MyThread2 myThread2=new MyThread2();
//4、通过此对象调用start():启动当前线程;调用当前线程的run()
myThread.start();
// 问题1:相当于直接调用run方法,还是在一个线程内,必须执行完run方法才能接着运行下面的程序
//myThread.run();
//问题2:再启动一个线程,不可以让已经启动的线程再次启动
myThread2.start();
}
}
Thread中的方法:
- 测试Thread中的常用方法
- 1、start():启动当前线程;调用当前的run方法
- 2、run():通常需要重写Thread类中的此方法,将创建的线程要执行的操作声明在此方法中
- 3、currentThread():静态方法,返回执行当前代码的线程
- 4、getName():获取当前线程的名字
- 5、setName():设置当前线程的名字
- 6、yield():释放当前CPU的执行权
- 7、join():在线程A中调用线程B的join()方法,此时线程A就进入阻塞状态,直到B完全执行完再执行A
- 8、stop():已经过时、当执行此方法时,强制停止线程
- 9、sleep():让当前线程睡眠指定的毫秒,在这个时间内,当前线程是阻塞状态
- 10、isAlive():判断当前线程是否存活
线程的优先级:
- 1、
-
MAX_PRIORITY:10
-
MIN_PRIORITY:1
-
NORM_PRIORITY:5
- 2、如何获取和设置当前线程的优先级
-
getPriority():获取当前优先级
-
setPriority(int p):设置当前优先级
-
说明:高优先级的线程会抢占低优先级的执行权,只是在高概率情况下会占用,并不说明一定会占用
*/
多线程的创建,方式二:实现Runnable接口
1、创建实现了Runnable接口的类
2、实现类去实现Runnable中的抽象方法:run()
3、创建实现类的对象
4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
5、通过Thread类的对象调用start()
package com.xyb.demo01;
/**
* 创建多线程的方式二:实现Runnable接口
* 1、创建实现了Runnable接口的类
* 2、实现类去实现Runnable中的抽象方法:run()
* 3、创建实现类的对象
* 4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
* 5、通过Thread类的对象调用start()
*
*
* 开发中:优先选择实现Runnable接口的方式
* 原因:1、实现的方式没有类的单继承性的局限性
* 2、实现的方式更适合来处理多个线程有共享数据的情况
*/
//1、创建实现了Runnable接口的类
class MThread implements Runnable{
//2、实现类去实现Runnable中的抽象方法:run()
@Override
public void run() {
for (int i = 0; i <100 ; i++) {
if (i%2==0)
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
public class MultiThreading2 {
public static void main(String[] args) {
//3、创建实现类的对象
MThread mThread = new MThread();
//4、将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
Thread thread = new Thread(mThread);
//5、通过Thread类的对象调用start()
thread.start();
}
}
两种方式实现多窗口卖票的例子:
1、继承Thread类
package com.xyb.demo01;
/**
* 例子:创建三个窗口卖票,总票数为100张(使用继承Thread类的方式)
* 存在线程的安全问题,待解决
*/
class Windows extends Thread{
private static int ticket=100;
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(getName()+":卖票,票号为"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowsTest {
public static void main(String[] args) {
Windows t1 = new Windows();
Windows t2 = new Windows();
Windows t3 = new Windows();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
2、实现Runnable接口
package com.xyb.demo01;
/**
* 例子:创建三个窗口卖票,总票数为100张(使用实现Runnable接口的方式)
*/
class Windows1 implements Runnable{
private int ticket=100;
@Override
public void run() {
while (true){
if(ticket>0){
System.out.println(Thread.currentThread().getName()+":卖票,票号为"+ticket);
ticket--;
}else {
break;
}
}
}
}
public class WindowsTest2 {
public static void main(String[] args) {
Windows1 windows1=new Windows1();
Thread t1=new Thread(windows1);
Thread t2=new Thread(windows1);
Thread t3=new Thread(windows1);
t1.start();
t2.start();
t3.start();
}
}
三、线程的生命周期
Thread类完整的生命周期包括五种状态:
- 新建、就绪、运行、阻塞、死亡
四、线程的同步(解决线程安全的方法——3种)
- 线程的死锁
-
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源就形成了线程的死锁
-
出现死锁后,不会出现异常,只是所有的线程都处于阻塞状态,无法继续
-
- 解决方法
- 专门的算法、原则
- 尽量减少同步资源的定义
- 尽量避免嵌套同步
package com.xyb.forth;
/**
* 演示线程的死锁问题
*/
public class DeadLock {
public static void main(String[] args) {
StringBuffer s1=new StringBuffer();
StringBuffer s2 =new StringBuffer();
new Thread(){
@Override
public void run() {
synchronized(s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} 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");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
}
}
五、线程的通信
-
线程通信的例子:使用两个线程打印1-100,线程1和线程2交替打印
-
wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
-
notify():一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤醒优先级高的
-
notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
-
面试题:sleep()和wait()的异同?
1、相同点:一旦执行方法,都可以使当前的线程进入阻塞状态 2、不同点:1):两个方法声明的位置不同,sleep在Thread中,wait在Object中 2):调用的要求不同;sleep在任何需要的场景下调用,wait只能在同步代码块中 3):sleep不会释放同步监视器,wait会释放同步监视器
package com.xyb.fifth;
/**
* 线程通信的例子:使用两个线程打印1-100,线程1和线程2交替打印
* wait():一旦执行此方法,当前线程就会进入阻塞状态,并释放同步监视器
* notify():一旦执行此方法,就会唤醒wait的一个线程,如果有多个线程被wait,就唤醒优先级高的
* notifyAll():一旦执行此方法,就会唤醒所有被wait的线程
* 面试题:sleep()和wait()的异同?
* 1、相同点:一旦执行方法,都可以使当前的线程进入阻塞状态
* 2、不同点:1):两个方法声明的位置不同,sleep在Thread中,wait在Object中
* 2):调用的要求不同;sleep在任何需要的场景下调用,wait只能在同步代码块中
* 3):sleep不会释放同步监视器,wait会释放同步监视器
*/
public class ThreadCommunication {
public static void main(String[] args) {
Number number = new Number();
Thread thread1 = new Thread(number);
thread1.setName("线程1");
Thread thread2 = new Thread(number);
thread2.setName("线程2");
thread1.start();
thread2.start();
}
}
class Number implements Runnable {
private int number = 1;
@Override
public void run() {
while (true) {
synchronized (this) {
notify();
//notifyAll();
if (number <= 100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
break;
}
}
}
}
}
六、JDK5.0新增线程创建方法(四种,5.0增加了2种)
新增方式一:实现Callable接口
- 与Runnable相比,Callable功能更强大些
- 相比run()方法,可以有返回值
- 方法可以跑出异常
- 支持泛型的返回值
- 需要借助FutureTask类,比如获取返回结果
package com.xyb.sixth;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
/**
* 创建线程的方式三:实现Callable接口——JDK5.0新增
* call()可以有返回值、可以跑出异常、支持泛型
*
*/
//1、创建一个实现Callable的实现类
class NumberCallable implements Callable {
//2、实现call方法,将此线程需要执行的操作声明放在call中
@Override
public Object call() {
int sum=0;
for (int i = 1; i <=100 ; i++) {
if(i%2==0){
System.out.println(i);
sum+=i;
}
}
return sum;
}
}
public class ThreadCallable{
public static void main(String[] args) {
//3、创建Callable接口实现类的对象
NumberCallable numberCallable=new NumberCallable();
//4、将此Callable接口实现类的对象作为传递到FutureTask构造器中,创建FutureTask的对象
FutureTask futureTask = new FutureTask(numberCallable);
//5、将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start
new Thread(futureTask).start();
try {
//get()返回值为FutureTask构造器参数Callable实现类重写的call()的返回值
Object sum = futureTask.get();
System.out.println("总和为:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
新增方式二:使用线程池
背景:经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
思路:提前创建好多个线程,放入线程池中,使用时直接获取,使用完之后放回池中。可以避免频繁创建、实现重复利用。
好处:提高响应速度、降低资源消耗、便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会中止
package com.xyb.sixth;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/**
* 创建线程的方式四:使用线程池
* 使用线程池,线程池相关API:ExecutorService(真正的线程池接口)和Executors(工具类、线程池的工厂类,用于创建并返回不同类型的线程池)
*/
class NumberThread implements Runnable{
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {
if(i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i = 0; i <=100 ; i++) {
if(i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1、提供指定线程数量的线程池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//设置线程池的属性
ThreadPoolExecutor service=(ThreadPoolExecutor) executorService;
service.setCorePoolSize(15);
//2、执行指定的线程的操作。需要提供实现Runnable接口或者Callable接口实现类的对象
executorService.execute(new NumberThread());//适合使用于Runnable
executorService.execute(new NumberThread1());//适合使用于Runnable
//executorService.submit();//适合使用与Callable
//关闭连接池
executorService.shutdown();
}
}