0602创建多线程4种方式
💃 线程的生命周期
JDK 中用Thread.State类定义了线程的几种状态
要想实现多线程,必须在主线程中创建新的线程对象。Java语言使用Thread类及其子类的对象来表示线程,在它的一个完整的生命周期中通常要经历如下的五****种状态 :
新建: 当一个Thread类或其子类的对 象被声明并创建时,新生的线程对象处于新建状态
**就绪:**处于新建状态的线程被start()后,将进入线程队列等待CPU时间片,此时它已具备了运行的条件,只是没分配到CPU资源
**运行:**当就绪的线程被调度并获得CPU资源时,便进入运行状态, run()方法定义了线程的操作和功能
**阻塞:**在某种特殊情况下,被人为挂起或执行输入输出操作时,让出 CPU 并临时中止自己的执行,进入阻塞状态
**死亡:**线程完成了它的全部工作或线程被提前强制性地中止或出现异常导致结
✋卖票中的线程安全问题
原因:存在多个线程共享一个数据时,如果线程a正在处理数据,此时线程a 因为阻塞或者其他问题没有执行完(such as : Thread.currentThread.sleep() ),线程b也开始处理这个数据,此时数据由于线程a 没有执行完,数据没有改变,就会造成不良后果。这就是线程安全问题
阻塞越久,影响越恶劣。(如下的Thread.currentThread.sleep() 的参数大小,以及sleep的位置)
解决方法:当一个线程在操作数据时,其他的线程不能参与进来,直到线程a操作完。即使a 阻塞,也不影响数据操作
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 package pers.dhx_.java0602;public class SafeTest { public static void main (String[] args) { Thread1 t1 = new Thread1 (); Thread tt1 = new Thread (t1); Thread tt2 = new Thread (t1); Thread tt3 = new Thread (t1); tt1.setName("线程1" ); tt2.setName("线程2" ); tt3.setName("线程3" ); tt1.start(); tt2.start(); tt3.start(); try { Thread.currentThread().sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程1: " + Thread1.test.cnt_1); System.out.println("线程2: " + Thread1.test.cnt_2); System.out.println("线程3: " + Thread1.test.cnt_3); System.out.print("total:" ); System.out.println(Thread1.test.cnt_3 + Thread1.test.cnt_2 + Thread1.test.cnt_1); } } class Thread1 implements Runnable { @Override public void run () { while (total > 0 ) { System.out.println(Thread.currentThread().getName() + " :票号 " + total--); if (Thread.currentThread().getName() == "线程1" ) test.cnt_1++; else if (Thread.currentThread().getName() == "线程2" ) test.cnt_2++; else test.cnt_3++; try { Thread.currentThread().sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } } } static Thread1 test = new Thread1 (); public int cnt_1 = 0 ; public int cnt_2 = 0 ; public int cnt_3 = 0 ; public int total = 100 ; }
⭐java解决线程安全问题 :涉及锁
🍔同步代码块 ----method1
1 2 3 4 5 6 7 8 9 10 public void run () { synchronized () { } }
操作 共享数据 的代码即为需要被同步代码
共享数据:多个Thread共同操作的的数据
同步监视器,俗称 ,⭐锁 !
任何一个类的对象都可以充当锁
要求多个线程共用同一把锁
💔坏处 :操作同步代码时,只有一个线程参与,其他线程等待,相当于是一个线程在工作,效率低
⭐同步机制中的锁
**同步锁机制:**在《Thinking in Java》中,是这么说的:对于并发工作,你需要某种方式来防止两个任务访问相同的资源(其实就是共享资源竞争)。 防止这种冲突的方法就是当资源被一个任务使用时,在其上加锁。第一个访问某项资源的任务必须锁定这项资源,使其他任务在其被解锁之前,就无法访问它了,而在其被解锁之时,另一个任务就可以锁定并使用它了。 -
synchronized****的锁是什么?
1. 任意对象都可以作为同步锁。所有对象都自动含有单一的锁(监视器)。
2. 同步方法的锁:静态方法(类名.class)、非静态方法(this)
3. 同步代码块:自己指定,很多时候也是指定为this或类名.class
🍅注意:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 package pers.dhx_.java0602;public class SafeTest { public static void main (String[] args) { Thread2 t1 = new Thread2 (); Thread tt1 = new Thread (t1); Thread tt2 = new Thread (t1); Thread tt3 = new Thread (t1); tt1.setName("线程1" ); tt2.setName("线程2" ); tt3.setName("线程3" ); tt1.start(); tt2.start(); tt3.start(); try { Thread.currentThread().sleep(2000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("线程1: " + Thread1.test.cnt_1); System.out.println("线程2: " + Thread1.test.cnt_2); System.out.println("线程3: " + Thread1.test.cnt_3); System.out.print("total:" ); System.out.println(Thread1.test.cnt_3 + Thread1.test.cnt_2 + Thread1.test.cnt_1); } } class Thread1 implements Runnable { Object o1 = new Object (); @Override public void run () { while (true ) { synchronized (o1) { if (total > 0 ) { System.out.println(Thread.currentThread().getName() + " :票号 " + total--); if (Thread.currentThread().getName() == "线程1" ) test.cnt_1++; else if (Thread.currentThread().getName() == "线程2" ) test.cnt_2++; else test.cnt_3++; try { Thread.currentThread().sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } } } } } static Thread1 test = new Thread1 (); public int cnt_1 = 0 ; public int cnt_2 = 0 ; public int cnt_3 = 0 ; public int total = 100 ; }
✋同步方法 ----method2
1 2 3 4 5 6 7 8 public void run () {}private synchronized void method () { }
👿线程a执行完后,线程b才能进来
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package pers.dhx_.java0602;public class TsbuMethodTest { public static void main (String[] args) { Thread2 t1 = new Thread2 (); Thread tt1 = new Thread (t1); Thread tt2 = new Thread (t1); Thread tt3 = new Thread (t1); tt1.setName("线程1" ); tt2.setName("线程2" ); tt3.setName("线程3" ); tt1.start(); tt2.start(); tt3.start(); } } class Thread3 implements Runnable { public static final Object o1 = new Object (); @Override public void run () { while (true ) { f(); } } private synchronized void f () { if (total > 0 ) { try { Thread.currentThread().sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } } } public int total = 100 ; }
🎈总结:(同步方法)
❤️线程安全的单例模式
⭐法一
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 class Bank { private Bank () { } private static Bank instance = null ; private static synchronized Bank getInstance () { if (instance == null ) instance = new Bank (); return instance; } } class Bank { private Bank () { } private static Bank instance = null ; private static Bank getInstance () { synchronized (Bank.class) { if (instance == null ) instance = new Bank (); } return instance; } }
⚔️法二
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 class Bank { private Bank () { } private static Bank instance = null ; private static Bank getInstance () {: if (instance == null ) { synchronized (Bank.class) { if (instance == null ) instance = new Bank (); } } return instance; } }
🌟线程的死锁问题
不同的线程分别占用对方需要的同步资源不放弃,都在等待对方放弃自己需要的同步资源,就形成了线程的死锁
出现死锁后,不会出现异常,不会出现提示,只是所有的线程都处于阻塞状态,无法继续
专门的算法、原则
尽量减少同步资源的定义
尽量避免嵌套同步
死锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 package pers.dhx_.java0602;public class ThreadTest { 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(); } }
⭐Lock (锁)JDK5.0 -------method3
从JDK 5.0开始,Java提供了更强大的线程同步机制——通过显式定义同
步锁对象来实现同步。同步锁使用Lock对象充当。
java.util.concurrent.locks.Lock接口是控制多个线程对共享资源进行访问的工具。锁提供了对共享资源的独占访问,每次只能有一个线程对Lock对象加锁,线程开始访问共享资源之前应先获得Lock对象。
ReentrantLock 类实现了 Lock ,它拥有与 synchronized 相同的并发性和
内存语义,在实现线程安全的控制中,比较常用的是ReentrantLock,可以显式加锁、释放锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package pers.dhx_.java0602;import java.util.concurrent.locks.ReentrantLock;public class LockTest { public static void main (String[] args) { Window w1 = new Window (); Thread t1 = new Thread (w1); Thread t2 = new Thread (w1); Thread t3 = new Thread (w1); t1.setName("窗口1 " ); t2.setName("窗口2 " ); t3.setName("窗口3 " ); t1.start(); t2.start(); t3.start(); } } class Window implements Runnable { private int tickets = 100 ; private ReentrantLock lock = new ReentrantLock (true ); @Override public void run () { while (true ) { try { lock.lock(); if (tickets > 0 ) { try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " 票号:" + tickets--); } else break ; } finally { lock.unlock(); } } } }
💃 synchronized 与 Lock 的对比
Lock是显式锁(手动开启和关闭锁,别忘记关闭锁),synchronized是隐式锁,出了作用域自动释放
Lock只有代码块锁,synchronized有代码块锁和方法锁
使用Lock 锁,JVM将花费较少的时间来调度线程,性能更好。并且具有更好的扩展性(提供更多的子类)
🍅优先使用顺序:
Lock > 同步代码块 (已经进入了方法体,分配了相应资源) > 同步方法 (在方法体之外)
💃练习
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 package pers.dhx_.java0602;public class AccountTest { public static void main (String[] args) { Account acct = new Account (0 ); Customer c1 = new Customer (acct); Customer c2 = new Customer (acct); c1.setName("路人甲" ); c2.setName("路人乙" ); c2.start(); c1.start(); try { Thread.currentThread().sleep(4000 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(acct.getBalance()); } } class Customer extends Thread { private Account acct; Customer(Account acct) { this .acct = acct; } @Override public void run () { for (int i = 0 ; i < 3 ; i++) { acct.deposit(1000 ); } } } class Account { private double balance; Account(double balance) { this .balance = balance; } public double getBalance () { return balance; } public void deposit (double add) { synchronized (this ) { if (add > 0 ) { try { Thread.sleep(500 ); } catch (InterruptedException e) { e.printStackTrace(); } balance += add; System.out.println(Thread.currentThread().getName() + "存钱成功! 余额为: " + balance); } } } }
⭐注意点:
注意如果使用继承方式 ,new了多个对象,并且使用的是Lock类,那么记得要把lock 设为 static
🗡️线程的通信
🎅例 题
使用两个线程打印 1-100 。线程1,线程2 交替打印
👡wait() 与 notify() 和 notifyAll()
🌟不理解就看代码注释
wait() :令当前线程挂起并放弃CPU、同步资源并等待,使别的线程可访问并修改共享资源,而当前线程排队等候其他线程调用notify()或notifyAll()方法唤醒,唤醒后等待重新获得对监视器的所有权后才能继续执行。
notify() :唤醒正在排队等待同步资源的线程中优先级最高者结束等待
notifyAll () :唤醒正在排队等待资源的所有线程结束等待.
⭐这三个方法只有在synchronized方法或synchronized代码块中才能使用,否则会报java.lang.IllegalMonitorStateException异常。
因为这三个方法必须有锁对象调用,而任意对象都可以作为synchronized的同步锁,因此这三个方法只能在Object类中声明。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 package pers.dhx_.java0602;public class CommunicationTest { public static void main (String[] args) { Number x1 = new Number (); Thread t1 = new Thread (x1); Thread t2 = new Thread (x1); t1.setName("Thread 1" ); t2.setName("Thread 2" ); t1.start(); t2.start(); } } class Number extends Thread { int Number = 1 ; @Override public void run () { while (true ) { synchronized (this ) { notify(); if (Number <= 100 ) { System.out.println(Thread.currentThread().getName() + ": " + Number++); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } else break ; } } } }
🌟案例: 生产者/消费者问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 package pers.dhx_.java0602;public class ProduceTest { public static void main (String[] args) { Clerk clerk = new Clerk (); Producer p1 = new Producer (clerk); p1.setName("生产者1 " ); Consumer c1 = new Consumer (clerk); c1.setName("消费者1 " ); p1.start(); c1.start(); } } class Clerk { private int num; public synchronized void produceProduct () { if (num < 20 ) { try { Thread.sleep(50 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( Thread.currentThread().getName() + " 正在生产 第" + (num++) + "个 产品" ); notify(); } else { try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } public synchronized void consumeProduct () { if (num > 1 ) { try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println( Thread.currentThread().getName() + " 正在消费 第" + (num--) + "个 产品" ); this .notify(); } else { try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } class Producer extends Thread { private Clerk clerk; public Producer (Clerk c) { this .clerk = c; } @Override public void run () { System.out.println(Thread.currentThread().getName() + " 开始生产 " ); while (true ) { try { Thread.sleep(100 ); } catch (InterruptedException e) { e.printStackTrace(); } clerk.produceProduct(); } } } class Consumer extends Thread { private Clerk clerk; public Consumer (Clerk c) { this .clerk = c; } @Override public void run () { System.out.println(Thread.currentThread().getName() + " 开始消费 " ); while (true ) { try { Thread.sleep(10 ); } catch (InterruptedException e) { e.printStackTrace(); } clerk.consumeProduct(); } } }
💋 JDK5.0新增线程创建方式
😛实现Callable接口
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 package pers.dhx_.java0602;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class CallableTest { public static void main (String[] args) { NewThread_1 t1 = new NewThread_1 (); FutureTask futureTask = new FutureTask (t1); new Thread (futureTask).start(); try { Object sum = futureTask.get(); System.out.println(sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } } class NewThread_1 implements Callable <Object> { public Integer 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; } }
😛使用线程池
**背景:**经常创建和销毁、使用量特别大的资源,比如并发情况下的线程,
对性能影响很大。
**思路:**提前创建好多个线程,放入线程池中,使用时直接获取,使用完
放回池中。可以避免频繁创建销毁、实现重复利用。类似生活中的公共交
通工具。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 package src.SUMMER_java.common;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.ThreadPoolExecutor;public class Day0529 { public static void main (String[] args) { ExecutorService service = Executors.newFixedThreadPool(10 ); System.out.println(service.getClass()); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; service1.getMaximumPoolSize(); service.execute(new NumberThread ()); service.execute(new NumberThread1 ()); service.shutdown(); } } 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); } } } 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); } } }