初识Java多线程

创建多线程

多线程的作用:一个时间点,同时做多个事情
(一个时间点,同时执行多行代码)

Thread

new 一个Thread类的子类

  1. 使用匿名内部类
public class Main {     public static void main(String[] args) {              Thread thread = new Thread() {             @Override             public void run () {                 System.out.println("这是一个子线程");             }         };              } } 
  1. 使用静态内部类,该类继承自Thread类
static class A extends Thread {         @Override         public void run () {             System.out.println("这是一个子线程");         } } 

Runnable

new 一个 Runnable 子类,传入Thread构造函数中执行

public class Main {     public static void main(String[] args) {          Runnable runnable = new Runnable() {             @Override             public void run() {                 System.out.println("这是一个子线程");             }         };         Thread thread = new Thread(runnable, "线程1");      }  } 

Thread thread = new Thread(runnable, “线程1”);

这里的第二个参数,是给该子线程起一个名字

启动一个线程

启动线程使用 start() 方法

public class Main {     public static void main(String[] args) {          Runnable runnable = new Runnable() {             @Override             public void run() {                 System.out.println("这是一个子线程");             }         };         Thread thread = new Thread(runnable, "线程1");         //启动线程         thread.start();     }  } 

线程的执行顺序

Runnable runnable = new Runnable() {             @Override             public void run() {                 System.out.println("这是一个子线程");             }         };         Thread thread = new Thread(runnable, "线程1");         thread.start();         System.out.println("main线程执行"); 

上面这段代码,执行顺序是:

main线程的打印语句执行的概率大于子线程打印语句执行的概率

因为创建子线程比较耗时

Thread类的部分方法

1.public static Thread currentThread()
返回当前正在执行的线程对象的引用

2.public static void yield()
线程让步
即让当前线程由运行状态转变为就绪状态

3.public static void sleep​(long millis) throws InterruptedException
使当前正在执行的线程在指定的毫秒数内休眠(暂时停止执行)

中断一个线程

  1. 使用自定义标志位中断
public class Main {     //标志位     private static boolean flag = false;      public static void main(String[] args) throws InterruptedException {          Runnable runnable = new Runnable() {             @Override             public void run() {                 try {                     while (!flag) {                         System.out.println("这是一个子线程");                         Thread.sleep(5000);                     }                 } catch (InterruptedException e) {                     e.printStackTrace();                 }             }         };         Thread thread = new Thread(runnable, "线程1");         thread.start();         Thread.sleep(3000);         flag = true;         System.out.println("main线程执行");     }  } 
  1. 调用 interrupt() 函数直接中断线程
public class Main {      public static void main(String[] args) throws InterruptedException {          Runnable runnable = new Runnable() {             @Override             public void run() {                 while (Thread.currentThread().isInterrupted() == false) {                     System.out.println("这是一个子线程");                 }             }         };         Thread thread = new Thread(runnable, "线程1");         thread.start();         Thread.sleep(1000);         thread.interrupt();         System.out.println("main线程执行");     }  } 

Thread类中有一个中断标志位,通过调用 interrupt() 函数就会让中断标志位变成 true,此时调用 isInterrupted() 方法获取中断标志位的值,通过条件判断使线程中断

等待一个线程

通过调用 join 方法

public class Main {      public static void main(String[] args) throws InterruptedException {          Runnable runnable = new Runnable() {             @Override             public void run() {                 for (int i = 0; i < 5; i++) {                     System.out.println(i);                 }             }         };         Thread thread = new Thread(runnable, "线程1");         thread.start();         thread.join();         System.out.println("main线程执行");     }  } 

执行结果

0
1
2
3
4
main线程执行

在等待子线程运行时,main线程,会卡在join()方法的调用处,直到子线程结束后,main线程才会向下运行

当然join也有参数
public final void join​(long millis) throws InterruptedException

等待线程多少毫秒,到了时间后就不等待继续执行了,如果在时间之内子线程运行结束,那么就直接向下运行

线程状态

public static enum State {         NEW,		//创建         RUNNABLE,   //可运行         BLOCKED,	//阻塞         WAITING,	//等待         TIMED_WAITING,//超时等待         TERMINATED;	//终止          private State() {         } } 

上面是 Thread.State 枚举类

里面是线程的六个状态

线程安全

由于多线程之间可以相互共享资源(变量),所以会出现一些问题,这些问题就是线程安全问题

不安全的原因:多个线程对同一个变量的操作

如:两个线程同时修改同一个变量的值,这就会导致变量的值不会出现我们预期的结果

public class Main {      private static int x = 0;      public static void main(String[] args) throws InterruptedException {          Runnable runnable = new Runnable() {             @Override             public void run() {                 for (int i = 0; i < 10000; i++) {                     x++;                 }             }         };         Thread thread1 = new Thread(runnable, "线程1");         Thread thread2 = new Thread(runnable, "线程2");         thread1.start();         thread2.start();         while (Thread.activeCount() > 1)             Thread.yield();         System.out.println(x);     }  } 

上面代码的结果每次都不一样,有的时候是20000,有的时候不是20000,这就是线程不安全引起的原因

不安全的原因之一是:不具有原子性
原子性是指:多行代码执行时,是不可再分的

举个例子,就好比你打开你的笔记本电脑,先翻开笔记本,再按电源键,这两个事情必须连在一起执行,不能分开,原子性也类似,对变量进行操作,必须一次性完成,不可分

解决方法

加锁共享变量

举个简单的例子:

共享变量就好比打印机,线程好比人,两个人同时使用一个打印机,肯定不行,会导致两个人要打印的内容打印到同一张纸上,为了防止这个现象的发生,那就要规定一个打印机在被一个人使用的时候,另一个人不能使用,这就相当于使用打印机的人给打印机加了锁,自己在使用时,其他人是不能使用的,对于线程和共享变量,也是这个道理

多个线程访问共享变量,最终表现为:

一个线程加锁,操作变量,释放锁,其他线程加锁失败,需要等待(等待可以是一直等着,也可以是执行其他代码,过一会再回来再尝试加锁)

这就好比一个人正在用打印机,你也想用,但是已经被占了,要么你就排队,排在他后面,等着他用完你用,此时你只能等着,做不了其他事;要么你先做其他事情,过一会来看一次,看一看打印机有人用没,如果有人用,就继续做其他事,没人用就使用打印机

锁,在java层面是一个对象,多个线程加锁,是对同一个对象加锁,解锁也是

加锁

public class Main { private static int x = 0; public static void main(String[] args) throws InterruptedException { Runnable runnable = new Runnable() { @Override public void run() { for (int i = 0; i < 10000; i++) { add(); } } }; Thread thread1 = new Thread(runnable, "线程1"); Thread thread2 = new Thread(runnable, "线程2"); thread1.start(); thread2.start(); while (Thread.activeCount() > 1) Thread.yield(); System.out.println(x); } private synchronized static void add() { x++; } }

add() 方法是静态方法

  1. 实例方法:加锁整个方法,锁对象为this
public class Main {      private static int x = 0;      public static void main(String[] args) throws InterruptedException {          Runnable runnable = new Runnable() {             @Override             public void run() {                 for (int i = 0; i < 10000; i++) {                     increase();                 }             }              private synchronized void increase() {                 x++;             }         };         Thread thread1 = new Thread(runnable, "线程1");         Thread thread2 = new Thread(runnable, "线程2");         thread1.start();         thread2.start();         while (Thread.activeCount() > 1)             Thread.yield();         System.out.println(x);     }  }  

increase() 方法是实例方法

  1. 同步代码块
public class Main {      private static int x = 0;          public static void main(String[] args) throws InterruptedException {          Runnable runnable = new Runnable() {             @Override             public void run() {                 for (int i = 0; i < 10000; i++) {                     synchronized (o) {                         x++;                     }                 }             }             Object o = new Object();         };         Thread thread1 = new Thread(runnable, "线程1");         Thread thread2 = new Thread(runnable, "线程2");         thread1.start();         thread2.start();         while (Thread.activeCount() > 1)             Thread.yield();         System.out.println(x);     }  } 

具体用法:

synchronized (锁对象) {
共享变量操作;
}

当然也可以对一个锁反复加锁

Runnable runnable = new Runnable() {             @Override             public void run() {                 for (int i = 0; i < 10000; i++) {                     synchronized (o) {                         synchronized (o) {                             synchronized (o) {                                 synchronized (o) {                                     x++;                                 }                             }                         }                     }                 }             }             Object o = new Object(); }; 

volatile 关键字

不需要加锁即可保证线程安全性
用于保证了原子性的变量

读取变量值的操作:原子性保证了
修改变量值的操作:变量值不依赖共享变量,才保证了原子性
如:用常量给共享变量赋值,算保证了原子性

保证了线程的安全情况下,要尽可能的提高效率,这就需要我们加锁的时候要注意了,只加锁访问共享变量的代码,不访问共享变量的代码不要加锁

Runnable runnable = new Runnable() {             @Override             public void run() {                 for (int i = 0; i < 10000; i++) {                     synchronized (o) {                         System.out.println("子线程操作变量");                         x++;                     }                 }             }             Object o = new Object(); }; 

上面这段代码,实际上
System.out.println(“子线程操作变量”);
不需要加锁

举个例子,就好比打印机在一个房间内,房间内有饮水机等其他设备,你在使用打印机的时候,就不需要对整个房间加锁,你使用的是打印机,并不是房间内的其他设备,万一有人想喝水了,还得等你用完打印机,这就很影响其他人,线程之间也是

所以加锁一定要锁对代码

版权声明:玥玥 发表于 2021-06-06 14:57:12。
转载请注明:初识Java多线程 | 女黑客导航