07-多线程
基本概念
- 程序:某种语言编写的一组指令的集合,即一段静态的代码-静态对象
- 进程:程序的一次执行过程,或正在运行中的一个程序。动态过程:有生命周期(产生、存在、消亡)
- 如:运行中的QQ
- 进程作为资源分配的单位,系统在运行时会为每个进程分配不同的内存区域
- 线程:进程可进一步细化为线程,是一个程序内部的一条执行路径。
- 若一个进程同一时间并行执行多个线程,就是支持多线程的
- 线程作为调度和执行的单位,每个线程拥有独立的运行栈和程序计数器,线程切换的开销小
- 一个进程中的多个线程共享相同的内存单元/内存地址空间 -> 它们从同一堆中分配对象,可以访问相同的变量和对象。这就使得线程间通信更简便、高效。但多个线程操作共享的系统资源可能就会带来安全隐患(多个线程同时操作内存空间时可能出现问题)。
优点:
提高应用程序的响应(如图形化界面)
提高CPU的利用率
改善程序结构,易于理解和修改
线程的创建和使用
- Thread类
/*
1. 创建一个继承于Thread类的子类
2. 重写Thread类的run();
3. 创建Thread类的子类的对象
4. 通过此对象调用start();
* */
//1.
class MyThread extends Thread{
//2.
@Override
public void run(){
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadTest{
public static void main(String[] args) {
//3.
MyThread t1 = new MyThread();
//4. 不能直接调用run()方法,需要通过start()方法类启动线程
//要想实现多个线程,需要new多个线程,不能反复掉start()
t1.start();
for(int i = 0;i < 100;i++){
if(i % 2 == 0){
System.out.println("-----main()-----");
}
}
//i 和 "main()" 字符串的执行是独立的,即可能字符串与数字会交替出现。
}
}
public class ThreadTest{
public static void main(String[] args){
new Thread(){
@Override
public void run(){
for(int i = 0; i <100; i++){
System.out.println(i);
}
}
}.start(); //创建Thread类的匿名子类的方式类创建线程
}
}
Thread类的有关方法
start()
:启动当前线程,调用当前线程的run()方法run()
:通常需要重写此方法,将创建的线程要执行的操作写进此方法currentThread()
:静态方法
,返回执行当前代码线程getName()
:获取当前线程名称setName()
:设置当前线程名称
public class ThreadTest{
public static void main(String[] args){
Thread.currentThread().setName("MainThread"); //给主线程设置名称
}
}
yield()
:静态方法
,释放当前CPU的执行(但是有可能在下一次又分配到了)join()
:线程插入||在线程a中调用线程b的join(),线程a就进入阻塞状态,直到线程b完全执行,a才继续等待CPU分配stop()
:已过时,强制结束此线程sleep()
:静态方法
,休眠一段时间(毫秒)isAlive()
:判断此线程是否还存活
线程优先级
MAX_PRIORITY:10
MIN_PRIORITY
:1NORM_PRIORITY
:10thread.setPripority()
高优先级的线程要抢占低优先级线程CPU的执行权,但是只是从概率上来讲,高优先级的线程高概率的情况下被执行,并不意味着只有当高优先级的线程执行完以后,低优先级的线程才执行
创建多线程两种方式
- 继承Thread类
- 创建一个继承于Thread类的子类
- 重写Thread类的run();
- 创建Thread类的子类的对象
- 通过此对象调用start();
class Window 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 WindowTest {
public static void main(String[] args) {
Window t1 = new Window();
Window t2 = new Window();
Window t3 = new Window();
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
- 实现Runnable接口
- 创建一个实现了Runnable接口的类
- 实现类去实现Runnable中的抽象方法run()
- 创建实现类的对象
- 将此对象作为参数传递到Thread类的构造器中,创建Thread类的对象
- 通过Thread类的对象调用start()
class Window1 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 WindowTest1 {
public static void main(String[] args) {
Window1 w = new Window1(); //因为此处只new了一次Window1,后面三个对象都是基于此来创建,所以只有一个ticket变量,不需要加static将之变为静态变量,就可实现三个窗口卖同一批票
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
-
- 开发中优先选择实现接口的方式
- 实现的方式没有类的单继承限制
- 实现的方式更适合来处理多个线程有共享数据的情况
- 线程的分类:
- 守护线程:用于服务用户线程,如GC垃圾回收
- 用户线程:在调用start()方法前调用
thread.setDaemon(true);
可以将一个用户线程变为一个守护线程 - 当JVM中都是守护线程时,JVM将退出
线程的生命周期
- Thread.state:线程的状态
- 新建:new一个实例
- 就绪:调用start()方法 || 或者运行后失去CPU执行权 || 或者运行状态调用yield()方法后 || 阻塞状态sleep()完毕 || 阻塞状态join()结束 || 阻塞状态获取同步锁完毕 || 阻塞状态notify()/notifyAll()(wait()之后) || 阻塞挂起后resume()方法
- 运行:获取CPU的执行权
- 阻塞:调用sleep()方法 || 或者线程中调用join()插入其他线程 || 或者等待同步锁 || wait()方法 || suspend()方法(挂起)
- 死亡:执行完run() || 或者调用线程的stop()方法 || 或者出现Error/Exception且没处理
线程的同步
-
问题:如上三个窗口卖车票,出现重票、错票
-
原因:当某个线程操作车票的过程中,尚未操作完成时,另一个线程也参与进来,也操作车票—即操作了多次
-
解决:当一个线程a在操作ticket的时候,其他线程不能参与进来,直到线程a操作完ticket时,其他线程才可以开始操作ticket,这种情况下即使线程a出现了阻塞,也不能改变
-
实现:
-
同步代码块
synchronized(同步监视器){ //需要被同步的代码 } //操作共享数据的方法,即为需要被同步的代码 //共享数据:多个线程共同操作的变量。比如:ticket就是共享数据 //同步监视器,俗称:锁。任何一个类的对象,都可以充当锁。 //要求多个线程必须使用同一把锁
class Window1 implements Runnable{ private int ticket = 100; //Object obj = new Object(); @Override public void run() { while(true){ //synchronized (obj) { //可以使用this代替---因为只创建了一次,所以this也是唯一的,即下方的w synchronized (this) { if (ticket > 0) { System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket); ticket--; } else { break; } } } } } public class WindowTest1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
在继承Thread类所创建的线程中,可以将锁换为 类.class 因为类也只加载一次,所以类.class也只有一个
-
同步方法
如果操作共享数据的代码完整的声明在一个方法中,我们不妨将此方法声明为同步的
- 同步方法仍然涉及到同步监视器,只是不需要显式的声明
- 非静态的同步方法,同步监视器是:this
- 静态的同步方法,同步监视器是:当前类本身
/**
* @Description
* @Author xxxxx
* @Date 2020/12/3 20:24
*/
class Window2 implements Runnable{
private int ticket = 100;
@Override
public void run() {
while(ticket>0){
show();
}
}
private synchronized void show(){
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}
}
}
public class WindowTest2 {
public static void main(String[] args) {
Window2 w = new Window2();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
在继承Thread类所创建的线程中,将同步方法声明为静态方法,且期内调用的方法需要通过对象来调用
使用同步机制:虽然保护了线程安全,但操作同一段代码时,只有一个线程参与,相当于单线程,效率低
懒汉式-单例模式-改写:
class Bank{
private Bank(){}
private static Bank instance = null;
public static Bank getInstance(){
if(instance == null){ //多个线程同时进来时,可能会同时满足null情况
instance = new Bank();
}
return instance;
}
}
class Bank2{
private Bank(){}
private static Bank instance = null;
public static synchronized Bank getInstance(){
//方式一:null修改后,多个线程仍然等待解锁-效率低
// if(instance == null){
// instance = new Bank();
// }
// return instance;
//方式二--null修改后,之后进入的线程不再等待解锁,直接跳过--效率较高
if(instance == null){
synchronized(Bank2.class){
if(instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
线程的死锁
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
- 出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞窗台,无法继续
public class Test{
new Thread(){
@Override
public void run(){
public
}
}.start();
}
Lock锁-JDK5
class Window3 implements Runnable{
private int ticket = 100;
//1. 实例化ReentrantLock
private ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
while(true){
try {
//2. 上锁
lock.lock();
if (ticket > 0) {
System.out.println(Thread.currentThread().getName() + ":卖票,票号为:" + ticket);
ticket--;
}else{
break;
}
}finally {
//3. 解锁
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window3 w = new Window3();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("窗口1");
t2.setName("窗口2");
t3.setName("窗口3");
t1.start();
t2.start();
t3.start();
}
}
synchronized 和 ReentrantLock
同:二者都可以解决线程安全问题
异:synchronized机制在执行完相应的同步代码以后,自动的释放同步监视器
Lock需要手动的启动同步(lock()),同时结束同步后也需要手动关闭(unclock())
线程的通信
- wait():一旦执行此方法,当前线程就进入阻塞状态,并释放同步监视器。
- notify():一旦执行此方法,就会唤醒被wait()的一个线程,如果有多个线程wait()则唤醒优先级高的,同优先级随机唤醒。
- notifyAll():一旦执行此方法,则唤醒所有被wait()的线程。
上面三个方法都只能在同步代码块或同步方法中调用
上面三个方法的调用者必须是同步代码块或者同步方法中的同步监视器(this可省略)
public class CommunicationTest {
public static void main(String[] args) {
Number num = new Number();
Thread t1 = new Thread(num);
Thread t2 = new Thread(num);
t1.setName("线程1");
t2.setName("线程2");
t1.start();
t2.start();
}
}
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while(true){
synchronized (this) {
notify(); //唤醒一个被wait()的线程--如果优先级一样,则随机唤醒,否则唤醒优先级高的
if (number < 100) {
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
} else {
break;
}
//睡--调用wait()后线程进入阻塞状态,并释放锁
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
JDK5.0新增的创建方式
Callable接口
- 创建一个实现Callable接口的实现类
- 实现call方法,将此线程需要执行的操作声明在call()中
- 创建Callable接口实现类的对象
- 将Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask对象
- 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
- –获取Callable中call方法的返回值–调用get方法
//1. 创建一个实现Callable接口的实现类
class NumThread implements Callable{
@Override
//2. 实现call方法,将此线程需要执行的操作声明在call()中
public Object call() throws Exception {
int sum = 0;
for(int i = 1; i <= 100; i++){
if(i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class CallTest {
public static void main(String[] args) {
//3. 创建Callable接口实现类的对象
NumThread numThread = new NumThread();
//4. 将Callable接口实现类的对象传递到FutureTask构造器中,创建FutureTask对象
FutureTask futureTask = new FutureTask(numThread);
//5. 将FutureTask的对象作为参数传递到Thread类的构造器中,创建Thread对象,并调用start()方法
new Thread(futureTask).start();
Object sum = null;
try {
//6. --获取Callable中call方法的返回值--调用get方法
sum = futureTask.get();
System.out.println(sum);
} catch (InterruptedException interruptedException) {
interruptedException.printStackTrace();
} catch (ExecutionException executionException) {
executionException.printStackTrace();
}
}
}
使用线程池
- 经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,对性能影响很大
- 提前创建多个线程,放入线程池中,使用时直接获取,使用完放回池中。可以避免频繁创建销毁、实现重复利用。
- 提高响应效率(减少了创建新线程的时间)
- 降低资源消耗(重复利用线程池中线程,不需要每次都创建)
- 便于线程管理
- corePoolSize:核心池的大小
- maximumPoolSize:最大线程数
- keepAliveTime:线程没有任务时最多保持多长时间后会终止
class NumberThread implements Runnable{
@Override
public void run() {
for(int i = 0;i < 100; i++){
if(i % 2 == 0){
System.out.println(i);
}
}
}
}
public class ThreadPool {
public static void main(String[] args) {
//1. 提供指定线程数量的线程池
ExecutorService service = Executors.newFixedThreadPool(10);
//设置属性
ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //强制转换-目的是为了使用子类的特有方法
service1.setCorePoolSize(15);
//2. 执行指定的线程的操作,需要提供实现Runnable接口或Callable接口的实现类的对象
service.execute(new NumberThread()); //适用于Runnable
// service.submit(); //适用于Callable
//3. 关闭连接池
service.shutdown();
}
}
- 关注我的公众号【次烦】,获取更多信息(*^▽^*)