一、实现线程三种方式

一、未开启线程并发

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. BranchThread t=new BranchThread();
  4. t.run();
  5. for(int i=0;i<=100;i++){
  6. System.out.println("主线程"+i);
  7. }
  8. }
  9. }
  10. class BranchThread extends Thread{
  11. public void run(){
  12. for(int i=0;i<=1000;i++){
  13. System.out.println("分支线程------------->"+i);
  14. }
  15. }
  16. }
  1. 1: 自上而下的顺序
  2. 2:先输出分支线程1-1000,输出完之后再输出
  3. 主线程1-100

JUC基础 - 图1

一、第一种方式

实现线程的第一种方式:
编写一个类,直接继承java.lang.Thread,重写run方法。

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. BranchThread t=new BranchThread();
  4. //start()方法的作用是:启动一个分支线程,在JVM中开辟一个新的栈空间,这段代码任务完成之后,瞬间就结束了。
  5. t.start();
  6. for(int i=0;i<=1000;i++){
  7. System.out.println("主线程"+i);
  8. }
  9. }
  10. }
  11. class BranchThread extends Thread{
  12. public void run(){
  13. for(int i=0;i<=1000;i++){
  14. System.out.println("分支线程------------->"+i);
  15. }
  16. }
  17. }

JUC基础 - 图2

  1. "C:\Program Files\Java\jdk-15.0.1\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\lib\idea_rt.jar=64875:C:\Program Files\JetBrains\IntelliJ IDEA 2020.3.1\bin" -Dfile.encoding=UTF-8 -classpath C:\Users\cao\IdeaProjects\untitled3\out\production\untitled Thread.ThreadTest02
  2. 分支线程--------->0
  3. 主线程0
  4. 分支线程--------->1
  5. 主线程1
  6. 分支线程--------->2
  7. 分支线程--------->3
  8. 主线程2
  9. 分支线程--------->4
  10. 主线程3
  11. 分支线程--------->5
  12. 主线程4
  13. 分支线程--------->6
  14. 主线程5
  15. 分支线程--------->7
  16. 主线程6
  17. 分支线程--------->8
  18. 主线程7
  19. 分支线程--------->9
  20. 主线程8
  21. 分支线程--------->10
  22. 主线程9
  23. 分支线程--------->11
  24. 主线程10
  25. 分支线程--------->12
  26. 主线程11
  27. 主线程12
  28. 分支线程--------->13
  29. 主线程13
  30. 分支线程--------->14
  31. 主线程14
  32. 分支线程--------->15

二、第二种方式

实现线程的第二种方式,编写一个类实现java.lang.Runnable接口。

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. /**
  4. * 创建一个可运行的对象
  5. * BranchThread r= new BranchThread();
  6. * 将可运行的对象封装成一个线程对象
  7. * Thread t=new Thread(r)
  8. */
  9. Thread t = new Thread(new BranchThread());//合并代码
  10. t.start();
  11. for(int i=0;i<=100;i++){
  12. System.out.println("主线程"+i);
  13. }
  14. }
  15. }
  16. class BranchThread implements Runnable{
  17. public void run(){
  18. for(int i=0;i<=100;i++){
  19. System.out.println("分支线程------------->"+i);
  20. }
  21. }
  22. }

采用匿名内部类的方式【第二种变形】

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. //创建线程对象,采用匿名内部类方式。
  4. Thread t = new Thread(new Runnable() {
  5. @Override
  6. public void run() {
  7. for (int i = 0; i <= 100; i++) {
  8. System.out.println("t线程----->" + i);
  9. }
  10. }
  11. });
  12. t.start();
  13. for (int i = 0; i <= 100; i++) {
  14. System.out.println("main线程----->" + i);
  15. }
  16. }
  17. }

三、第三种方式

实现线程的第三种方式:
实现Callable接口
这种方式的优点:可以获取到线程的执行结果。
这种方式的缺点:效率比较低,在获取t线程执行结果的时候,当前线程受阻塞,效率较低。

  1. import java.util.concurrent.Callable;
  2. import java.util.concurrent.FutureTask; // JUC包下的,属于java的并发包,老JDK中没有这个包。新特性。
  3. public class MyThread {
  4. public static void main(String[] args) throws Exception {
  5. // 第一步:创建一个“未来任务类”对象。
  6. // 参数非常重要,需要给一个Callable接口实现类对象。
  7. FutureTask task = new FutureTask(new Callable() {
  8. @Override
  9. public Object call() throws Exception { // call()方法就相当于run方法。只不过这个有返回值
  10. // 线程执行一个任务,执行之后可能会有一个执行结果
  11. // 模拟执行
  12. System.out.println("call method begin");
  13. Thread.sleep(1000 * 10);
  14. System.out.println("call method end!");
  15. int a = 100;
  16. int b = 200;
  17. return a + b; //自动装箱(300结果变成Integer)
  18. }
  19. });
  20. // 创建线程对象
  21. Thread t = new Thread(task);
  22. // 启动线程
  23. t.start();
  24. // 这里是main方法,这是在主线程中。
  25. // 在主线程中,怎么获取t线程的返回结果?
  26. // get()方法的执行会导致“当前线程阻塞”
  27. Object obj = task.get();
  28. System.out.println("线程执行结果:" + obj);
  29. // main方法这里的程序要想执行必须等待get()方法的结束
  30. // 而get()方法可能需要很久。因为get()方法是为了拿另一个线程的执行结果
  31. // 另一个线程执行是需要时间的。
  32. System.out.println("hello world!");
  33. }
  34. }
  1. call method begin
  2. /*等待sleep */
  3. call method end!
  4. /*等待另一个线程结束拿到300*/
  5. 线程执行结果:300
  6. hello world!

二、线程的生命周期

JUC基础 - 图3

三、线程的三个基本方法

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. MyThread2 t = new MyThread2();
  4. //获取线程名字
  5. System.out.println(t.getName()); //默认值为 Thread-0
  6. //修改线程名字
  7. t.setName("曹1");
  8. System.out.println(t.getName());//曹1
  9. //获取当前线程对象?
  10. Thread y =Thread.currentThread();
  11. System.out.println(y.getName());//main
  12. t.start();
  13. }
  14. }
  15. class MyThread2 extends Thread{
  16. public void run(){
  17. //获取当前线程对象,t.start();谁启动就是谁
  18. Thread y1= Thread.currentThread();
  19. System.out.println(y1.getName());//曹1
  20. }
  21. }

四、Thread.sleep()

关于Thread.sleep()方法的一个面试题;
1、静态方法:Thread.sleep(1000);
2、参数是毫秒
3、作用:让当前线程进入休眠,进入“阻塞状态”,放弃占有CPU时间片,让给其它线程使用。
这行代码出现在A线程中,A线程就会进入休眠。
这行代码出现在B线程中,B线程就会进入休眠。
4、Thread.sleep()方法,可以做到这种效果:
间隔特定的时间,去执行一段特定的代码,每隔多久执行一次

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. //创建一个线程对象,一个多态
  4. Thread t =new MyThread3();
  5. t.setName("t");
  6. t.start();
  7. try {//问题:这行代码会让线程t进入休眠状态吗?
  8. t.sleep(1000*3);//在执行的时候还是会转成:Thread.sleep(1000*5) 这是静态方法
  9. //这行代码的作用是:让当前线程进入休眠,也就是说main进入休眠.
  10. //这样代码出现在main方法中,main线程睡眠。
  11. } catch (InterruptedException e) {
  12. e.printStackTrace();
  13. }
  14. //3秒之后执行到这里
  15. System.out.println("hello word");
  16. }
  17. }
  18. class MyThread3 extends Thread{
  19. public void run(){
  20. for(int i =0;i<10000;i++){
  21. System.out.println(Thread.currentThread().getName()+"------>"+i);
  22. }
  23. }
  24. }

4.1合理终止一个线程

  1. public class MyThread {
  2. public static void main(String[] args) {
  3. MyThread4 r =new MyThread4();
  4. Thread t =new Thread(r);
  5. t.setName("t");
  6. t.start();
  7. //模拟5秒
  8. try {
  9. Thread.sleep(5000);
  10. } catch (InterruptedException e) {
  11. e.printStackTrace();
  12. }//终止线程
  13. //你想要什么时候终止t的执行,那么你可以把标记修改为false,就结束了。
  14. r.run=false;
  15. }
  16. }
  17. class MyThread4 implements Runnable{
  18. //打一个布尔标记
  19. boolean run=true;
  20. public void run(){
  21. for(int i =0;i<10;i++){
  22. if(run){
  23. System.out.println(Thread.currentThread().getName()+"--->"+i);
  24. try {
  25. Thread.sleep(1000);
  26. } catch (InterruptedException e) {
  27. e.printStackTrace();
  28. }
  29. }else {
  30. //return就结束了,你在结束之前还有什么没保存的。在这里可以保存
  31. //终止当前线程
  32. return;
  33. }
  34. }
  35. }
  36. }
  1. t--->0
  2. t--->1
  3. t--->2
  4. t--->3
  5. t--->4

五 、线程安全问题

1: 什么时候数据在多线程并发的环境下会存在安全问题呢? 三个条件: 条件1:多线程并发。 条件2:有共享数据。 条件3:共享数据有修改的行为。
满足以上3个条件之后,就会存在线程安全问题。

  1. synchronized有三种写法:
  2. 第一种:同步代码块
  3. 灵活
  4. synchronized(线程共享对象){
  5. 同步代码块;
  6. }
  7. 第二种:在实例方法上使用synchronized
  8. 表示共享对象一定是this
  9. 并且同步代码块是整个方法体。
  10. 第三种:在静态方法上使用synchronized
  11. 表示找类锁。
  12. 类锁永远只有1把。
  13. 就算创建了100个对象,那类锁也只有一把。
  14. 对象锁:1个对象1把锁,100个对象100把锁。
  15. 类锁:100个对象,也可能只是1把类锁。

synchronized () 括号中中写什么?
那要看你想让哪些线程同步。
假设t1、t2、t3、t4、t5,有5个线程,
你只希望t1 t2 t3排队,t4 t5不需要排队。怎么办?
你一定要在()中写一个t1 t2 t3共享的对象。而这个
对象对于t4 t5来说不是共享的。
JUC基础 - 图4
下面模拟两个人取同一个账户

  1. package ThreadSafe;
  2. public class Account {
  3. private String actno;
  4. private double balance;
  5. public Account() {
  6. }
  7. public Account(String actno, double balance) {
  8. this.actno = actno;
  9. this.balance = balance;
  10. }
  11. public String getActno() {
  12. return actno;
  13. }
  14. public void setActno(String actno) {
  15. this.actno = actno;
  16. }
  17. public double getBalance() {
  18. return balance;
  19. }
  20. public void setBalance(double balance) {
  21. this.balance = balance;
  22. }
  23. //取款的方法 第一种方式
  24. public void withdraw(double money) {
  25. synchronized (this) {
  26. //取款之前户余额
  27. double before = this.getBalance();
  28. //取款之后的余额
  29. double after = before - money;
  30. try {
  31. Thread.sleep(1000);
  32. } catch (InterruptedException e) {
  33. e.printStackTrace();
  34. }
  35. //更新余额
  36. this.setBalance(after);
  37. }
  38. }
  39. }
  40. /* 第二种方式
  41. public synchronized void withdraw(double money){
  42. double before = this.getBalance(); // 10000
  43. double after = before - money;
  44. try {
  45. Thread.sleep(1000);
  46. } catch (InterruptedException e) {
  47. e.printStackTrace();
  48. }
  49. this.setBalance(after);
  50. }
  51. } */
  1. package ThreadSafe;
  2. public class AccountThread extends Thread {
  3. // 两个线程必须共享同一个账户对象。
  4. private Account act;
  5. // 通过构造方法传递过来账户对象
  6. public AccountThread(Account act) {
  7. this.act = act;
  8. }
  9. public void run(){
  10. // run方法的执行表示取款操作。
  11. // 假设取款5000
  12. double money = 5000;
  13. // 取款
  14. // 多线程并发执行这个方法。
  15. act.withdraw(money);
  16. //第三种方式
  17. /* synchronized (act) { // 这种方式也可以,只不过扩大了同步的范围,效率更低了。
  18. act.withdraw(money);
  19. }*/
  20. System.out.println(Thread.currentThread().getName() + "对"+act.getActno()+"取款"+money+"成功,余额" + act.getBalance());
  21. }
  22. }
  1. package ThreadSafe;
  2. public class Test {
  3. public static void main(String[] args) {
  4. // 创建账户对象(只创建1个)
  5. Account act = new Account("act-001", 10000);
  6. // 创建两个线程
  7. Thread t1 = new AccountThread(act);
  8. Thread t2 = new AccountThread(act);
  9. // 设置name
  10. t1.setName("t1");
  11. t2.setName("t2");
  12. // 启动线程取款
  13. t1.start();
  14. t2.start();
  15. }
  16. }

六、死锁

JUC基础 - 图5

  1. 死锁
  2. package com.bjpowernode.java.deadlock;
  3. /*
  4. 死锁代码要会写。
  5. 一般面试官要求你会写。
  6. 只有会写的,才会在以后的开发中注意这个事儿。
  7. 因为死锁很难调试。
  8. */
  9. public class DeadLock {
  10. public static void main(String[] args) {
  11. Object o1 = new Object();
  12. Object o2 = new Object();
  13. // t1和t2两个线程共享o1,o2
  14. Thread t1 = new MyThread1(o1,o2);
  15. Thread t2 = new MyThread2(o1,o2);
  16. t1.start();
  17. t2.start();
  18. }
  19. }
  20. class MyThread1 extends Thread{
  21. Object o1;
  22. Object o2;
  23. public MyThread1(Object o1,Object o2){
  24. this.o1 = o1;
  25. this.o2 = o2;
  26. }
  27. public void run(){
  28. synchronized (o1){
  29. try {
  30. Thread.sleep(1000);
  31. } catch (InterruptedException e) {
  32. e.printStackTrace();
  33. }
  34. synchronized (o2){
  35. }
  36. }
  37. }
  38. }
  39. class MyThread2 extends Thread {
  40. Object o1;
  41. Object o2;
  42. public MyThread2(Object o1,Object o2){
  43. this.o1 = o1;
  44. this.o2 = o2;
  45. }
  46. public void run(){
  47. synchronized (o2){
  48. try {
  49. Thread.sleep(1000);
  50. } catch (InterruptedException e) {
  51. e.printStackTrace();
  52. }
  53. synchronized (o1){
  54. }
  55. }
  56. }
  57. }

七、守护线程

  1. public class ThreadTest14 {
  2. public static void main(String[] args) {
  3. Thread t = new BakDataThread();
  4. t.setName("备份数据线程");
  5. //启动线程之前,将线程设置为守护线程
  6. t.setDaemon(true);//守护线程
  7. t.start();
  8. //主线程,主线是用户线程
  9. for(int i=0;i<10;i++){
  10. System.out.println(Thread.currentThread().getName()+"--->"+i);
  11. try {
  12. Thread.sleep(1000);
  13. } catch (InterruptedException e) {
  14. e.printStackTrace();
  15. }
  16. }
  17. }
  18. }
  19. class BakDataThread extends Thread{
  20. public void run(){
  21. int i=0;
  22. //即使是死循环,但由于该线程是守护线程,当用户线程结束,守护线程自动停止。
  23. while (true){
  24. System.out.println(Thread.currentThread().getName()+"--->"+(++i));
  25. try {
  26. Thread.sleep(1000);
  27. } catch (InterruptedException e) {
  28. e.printStackTrace();
  29. }
  30. }
  31. }
  32. }

7.1实现定时器

  1. package com.bjpowernode.java.thread;
  2. import java.text.SimpleDateFormat;
  3. import java.util.Date;
  4. import java.util.Timer;
  5. import java.util.TimerTask;
  6. /*
  7. 使用定时器指定定时任务。
  8. */
  9. public class TimerTest {
  10. public static void main(String[] args) throws Exception {
  11. // 创建定时器对象
  12. Timer timer = new Timer();
  13. //Timer timer = new Timer(true); //守护线程的方式
  14. // 指定定时任务
  15. //timer.schedule(定时任务, 第一次执行时间, 间隔多久执行一次);
  16. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  17. Date firstTime = sdf.parse("2020-03-14 09:34:30");
  18. //timer.schedule(new LogTimerTask() , firstTime, 1000 * 10);
  19. // 每年执行一次。
  20. //timer.schedule(new LogTimerTask() , firstTime, 1000 * 60 * 60 * 24 * 365);
  21. //匿名内部类方式
  22. timer.schedule(new TimerTask(){
  23. @Override
  24. public void run() {
  25. // code....
  26. }
  27. } , firstTime, 1000 * 10);
  28. }
  29. }
  30. // 编写一个定时任务类
  31. // 假设这是一个记录日志的定时任务
  32. class LogTimerTask extends TimerTask {
  33. @Override
  34. public void run() {
  35. // 编写你需要执行的任务就行了。
  36. SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
  37. String strTime = sdf.format(new Date());
  38. System.out.println(strTime + ":成功完成了一次数据备份!");
  39. }

八、wait和notify方法

注意:wait和notify方法不是线程对象的方法,是java中任何一个java对象都有的方法,因为这两个方式是Object类中自带的。

  1. Object o = new Object();
  2. o.wait();

2.wait()方法的作用:o.wait()让正在o对象上活动的线程t进入等待状态,并且释放掉t线程之前占有的o对象的锁。
3、notify方法作用:o.notify()让正在o对象上等待的线程唤醒,只是通知,不会释放o对象上之前占有的锁。
4.notifyAll()方法这个方法是唤醒o对象上处于等待的所有线程。

下面模拟生产者与消费者

  1. public class Box {
  2. //定义一个成员变量,表示第x瓶奶
  3. private int milk;
  4. //定义一个成员变量,表示奶箱的状态
  5. private boolean state=false;
  6. //提供储存和获取牛奶的操作
  7. public synchronized void put( int milk){
  8. //如果有牛奶,等待消费
  9. if(state){
  10. try {
  11. //当前线程进入等待状态,并且释放Producer之前占有的Box锁
  12. wait();
  13. } catch (InterruptedException e) {
  14. throw new RuntimeException(e);
  15. }
  16. }
  17. //如果没有牛奶,就生产牛奶
  18. this.milk=milk;
  19. System.out.println("送奶工将第"+this.milk+"瓶奶放入奶箱");
  20. //生产完成之后,修改奶箱状态
  21. state=true;
  22. //唤醒其他等待的线程
  23. notifyAll();
  24. }
  25. public synchronized void get(){
  26. //如果没有牛奶,等待生产
  27. if(!state){
  28. try {
  29. //当前线程进入等待状态,并且释放Customer之前占有的Box锁。
  30. wait();
  31. } catch (InterruptedException e) {
  32. throw new RuntimeException(e);
  33. }
  34. }
  35. //如果有牛奶,就消费牛奶
  36. System.out.println("用户拿到第"+this.milk+"瓶奶");
  37. //消费完毕之后,修改奶箱状态
  38. state=false;
  39. //唤醒其他等待的线程
  40. notifyAll();
  41. }
  42. }
  1. public class Producer implements Runnable{
  2. private Box b;
  3. public Producer(Box b){
  4. this.b=b;
  5. }
  6. @Override
  7. public void run() {
  8. for(int i=1;i<=5;i++){
  9. b.put(i);
  10. }
  11. }
  12. }
  1. public class Customer implements Runnable {
  2. private Box b;
  3. public Customer(Box b) {
  4. this.b=b;
  5. }
  6. @Override
  7. public void run() {
  8. while (true){
  9. b.get();
  10. }
  11. }
  12. }
  1. public class BoxDemo {
  2. public static void main(String[] args) {
  3. //创建奶箱对象,这是共享数据区域
  4. Box b =new Box();
  5. //创建生产者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用储存牛奶的操作
  6. Producer p =new Producer(b);
  7. //创建消费者对象,把奶箱对象作为构造方法参数传递,因为在这个类中要调用获取牛奶的操作
  8. Customer c =new Customer(b);
  9. //创建两个线程对象,分别把生产者对象和消费者对象作为构造方法参数传递
  10. Thread t1= new Thread(p);
  11. Thread t2= new Thread(c);
  12. //启动线程
  13. t1.start();
  14. t2.start();
  15. }
  16. }