java 窗口独占_写完感叹,不愧是我!Java 多线程与同步你到底懂了多少?

基本概念

  • 程序 :为完成特定任务用某种语言编写的指令的集合,属于一段 静态 代码。
  • 进程 :是程序的依次执行过程,正在运行的程序,存在生命周期。 进程为资源分配的单位 。每个进程在内存中有独占一个方法区和堆空间,被多个线程共享。
  • 线程 :进程可以进一步细化为线程,是程序内部的一条执行路径。 线程作为调度和执行的单位 。每个线程拥有独立的虚拟栈空间和程序计数器。
  • 一个java应用程序实际上至少有三个线程, main主线程gc()垃圾回收机制的运行线程异常处理线程
  • 线程分为两类: 用户线程守护线程 。守护线程适用于服务用户线程的。用户线程结束,守护线程也就结束,所以守护线程是依赖于用户线程的。举个例子:java程序中,main是用户线程,垃圾回收就是守护线程。可以利用 thread.setDaemon(true) 将用户线程变成守护线程。
c195841633a34dd147baaaf6edef29ff.png

线程的创建和使用

线程的创建

  • 方式一 :创建继承 Thread 类的子类,需要重写父类的 run() 方法,然后创建子类的对象,通过子类对象调用 start() 方法(包括采用匿名子类)。
public class MultiThreadingTest1 {public static void main(String[] args) {MyThread1 myThread = new MyThread1();myThread.start();//如果直接调用run方法不属于多线程,因为没有开启新线程//myThread.run();//同一个新线程的对象不能重复start启动,如想重新启动新线程,需要创建一个新的对象//myThread.start();//运行报错:java.lang.IllegalThreadStateExceptionSystem.out.println("我是主线程");//匿名子类写法创建多线程new Thread() {@Overridepublic void run() {// 此线程执行需要执行的操作声明在run中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是匿名子类新线程");}}.start();    }}class MyThread1 extends Thread {@Overridepublic void run() {// 此线程执行需要执行的操作声明在run中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是新线程");}}复制代码
  • 方式二 :创建实现 Runnable 的类,实现 Runnable 中的抽象方法 run() ,创建类的对象,将此对象传入 Thread 类的构造器中创建 Thread 类的对象,调用 Thread 类的对象的 start()方法(包括匿名写法)。
public class MultiTheadingTest2 {public static void main(String[] args) {MyThread2 myThread2 = new MyThread2();Thread thread = new Thread(myThread2);thread.start();System.out.println("我是主线程");//匿名写法new Thread(new Runnable() {@Overridepublic void run() {// 此线程执行需要执行的操作声明在run中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是匿名新线程");}}).start();}}class MyThread2 implements Runnable{@Overridepublic void run() {// 此线程执行需要执行的操作声明在run中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是新线程");}}复制代码
  • 方式三 :JDK5.0新特性。实现 Callable 接口。需要借助 Future 接口的唯一实现类 FutureTask 辅助线程的对象创建和返回值获取( FutureTask 还实现了 Runnable 接口),再创建 Thread 对象,将 FutureTask 类的对象作为构造器参数传入,完成线程的创建,最后调用 start() 方法完成线程启动。
import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.FutureTask;public class MultiThreadTest3 {public static void main(String[] args) {MyThread3 myThread3 = new MyThread3();FutureTask futureTask = new FutureTask(myThread3);Thread thread = new Thread(futureTask);thread.start();try {Object sum = futureTask.get();//System.out.println(sum);} catch (InterruptedException e) {e.printStackTrace();} catch (ExecutionException e) {e.printStackTrace();}System.out.println("我是主线程");// 匿名写法new Thread(new FutureTask(new Callable() {@Overridepublic Object call() throws Exception {// 此线程执行需要执行的操作声明在call中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是匿名新线程");return sum;// int类型赋值给Object,自动装箱}}) ).start();}}class MyThread3 implements Callable {@Overridepublic Object call() throws Exception {// 此线程执行需要执行的操作声明在call中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是新线程");return sum;// int类型赋值给Object,自动装箱}}复制代码
  • 方式四 :JDK5.0新特性。使用线程池,提前创建好多个线程放入线程池中,使用时直接获取,使用完放回线程池中。可以做到提高响应速度(减少线程创建的时间)和降低资源消耗(可重复利用线程)。利用 Executors 工具类创建线程池,然后提供 Runnable ( excute() )或 Callable ( submit() )接口的实现类的对象,执行指定线程的操作。最后,关闭线程池 shutdown() 。
import java.util.concurrent.Callable;import java.util.concurrent.Executor;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;public class MultiThreadTest4 {public static void main(String[] args) {// 利用工具类Executors创建线程池ExecutorService threadPool = Executors.newFixedThreadPool(10);threadPool.submit(new MyThread4());// 适用于实现Callable接口的线程threadPool.execute(new MyThread5());// 适用于实现Runnable接口的线程threadPool.shutdown();}}class MyThread4 implements Callable{@Overridepublic Object call() throws Exception {// 此线程执行需要执行的操作声明在call中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是CALL新线程");return sum;}}class MyThread5 implements Runnable {@Overridepublic void run() {// 此线程执行需要执行的操作声明在call中int sum = 0;for (int i = 0; i <= 100; i++) {sum += i;}System.out.println("我是RUN新线程");}}复制代码

创建线程方式间的比较

JDK5.0前两种方式的比较(继承Thread类与实现Runnable接口)

  • 继承方式的弊端:单继承约束下线程只能继承于 Thread 类,不能继承于其他的类
  • 实现方式的优势:天然存在共享数据的情况,不需要将共享的数据设置为静态
  • Thread 类实际上是 Runnable 接口的实现类

实现Runnable接口和Callable接口方式的比较

  • Runnable 接口实现方法需要重写 run() ,但是 run() 方法没有返回值, Callable 接口实现方式重写 Call() 方法,可以有返回值;
  • Runnable 接口实现方法不能抛出异常,只能 try-catch 捕获异常, Callable 接口实现方式可以 throws 抛出异常;
  • Runnable 接口实现方法不支持泛型, Callable 接口实现方式支持泛型;
  • Callable 接口实现方式返回值需要借助 FutureTask 类获取返回值。

线程的常用方法

  • 1. start() :启动当前线程,自动调用 run() 方法;
  • 2. run() :通常需要子类重写,并将新线程需要执行的操作声明在 run() 方法中;
  • 3. currentThread() :静态方法,返回当前执行的线程的对象(返回对象类型为 Thread );
  • 4. getName() :获取当前线程的名称,不设置名称情况下,默认调用 Thread 的空参构造器Thread-0等等(线程名不等于类名);
  • 5. setName() :设置当前线程的名称, Thread.currentThread.setName("") 或者 对象名.setName("") ;
  • 6. yield() : 静态方法 ,线程让步,释放当前CPU的执行,可以让多个线程同时竞争资源;
  • 7. join() :在线程A中调用线程B的 join() 方法,相当于让线程B直接插入线程A方法中运行(线程A阻塞),直至线程B结束,继续线程A;
  • 8. stop() :强制结束线程的生命周期,不推荐使用( Deprecated );
  • 9. sleep(long millis) : 静态方法 ,毫秒单位,睡眠一段时间之后重新加入CPU资源竞争,睡眠的时候仍然握锁;
  • 10. isAlive() :判断当前线程是否存活。

线程的优先级

  • MAX_PRIORITY :默认线程最高优先级为10
  • MIN_PRIORITY :默认线程最低优先级为1
  • NORM_PRIORITY :不设置优先级情况下,默认线程优先级为5
  • getPriority() : final ,获取线程优先级
  • setPriority(int) :设置线程优先级
  • 注:高优先级并不意味着先执行,只是更高概率先抢占资源执行,所以还是存在交替输出的结果。

多线程共享数据

ThreadRunnable

经典例子:三个窗口卖票,采用方式一创建多线程,需要将票的总数需要设置为静态。存在线程不安全的问题。

线程的生命周期

JDK中 Thread.State 枚举类定义了线程的几种状态:

Threadstart()
389e52e27e131bcadb6e88b2ea8d2eb2.png

线程安全与线程同步

线程安全问题产生原因:当一个线程在执行操作共享数据的多条代码过程中,其他线程也参与了运算,就有可能导致线程安全问题的产生。 解决方案 :同步机制。

同步机制

方式一:同步代码块将共享数据资源的代码块“包”起来,加 synchronized 关键字和同步锁。

synchronized(同步监视器(锁)){//需要被同步的代码块}复制代码

说明:

  • 操作共享数据的代码即为需要被同步的代码;
  • 多个线程共同操作的变量即为共享数据;
  • 任何一个类的对象都能作为锁; 多个线程必须共用一把锁 ,比如用方法一创建线程需要设置 锁对象为静态
  • 方式二 创建线程中,解决线程安全问题**,锁可以设置为当前对象**,即用关键字 this 表示, synchronized(this){同步代码块} ;
  • 方式一 创建线程中,解决线程安全问题, 锁可以设置为类.class ,即 synchronized(继承Thread的子类.class){同步代码块} ;
  • 在操作同步代码块时,只有一个线程参与,其他线程等待,相当于一个单线程的过程,效率低。

方式二:同步方法如果操作共享数据的代码完整声明在一个方法中,不妨将此方法声明为同步的。

  • 方式一 创建线程时,可以直接把操作共享数据的代码封装进一个方法里,并把该方法声明为 静态****和同步 ,即 static 和 synchronized ,这里的隐藏同步监视器即为当前类本身, 类.class
  • 方式二 创建线程时,可以直接把操作共享数据的代码封装进一个方法里,并把该方法声明为 synchronized ,这里实际上仍然有隐藏同步监视器存在,即为 this 。必要情况下,可以直接把 run() 方法设置为 synchronized ,但是相当于变成一个单进程过程,共享数据会被第一个进程全部执行。

方式三:同步锁(Lock)

  • JDK5.0新特性,同步锁由 Lock 对象充当。 ReentrantLock 类实现 Lock 接口。
  • java.util.concurrent.locks.Lock 接口:控制多个线程对共享数据资源进行访问的工具。锁提供了对共享 数据资源的独占访问,每次只有一个线程对 Lock 对象加锁,线程开始访问共享数据资源之前需要先获取Lock 对象。
  • 同样,使用 方式一 创建线程需要主要 lock 对象的 静态问题
ReentrantLock reentrantLock = new ReentrantLock();try {//加锁reentrantLock.lock();//需要同步的代码块}finally {//释放锁reentrantLock.unlock();}复制代码

两大类解决线程安全问题方法的不同之处(synchronized和lock(ReentrantLock))

synchronizedLock

单例模式线程安全问题

单例模式的创建,一般分为两种方式: 饿汉式懒汉式

  • 饿汉式:在还没需要使用对象前,提前在开辟内存空间,创建对象;
  • 懒汉式:在需要使用对象的时候,判断是否需要新建对象的时候考虑创建对象;

懒汉式单例设计模式,存在线程安全的问题,有可能多个线程“同时”判断是否需要创建新对象,导致创建的对象个数多于一个。下面代码是两种创建模式的简单代码示例,并直接利用同步方法解决懒汉式单例模式的线程安全问题。

public class singletonTest {public static void main(String[] args) {Bank bank1 = Bank.getBankInfo();School school1 = School.getSchoolInfo();}}//饿汉式单例模式:没用就直接造好了 class Bank{ //构造器私有化 private Bank(){  }  //内部创建对象  private static Bank bank = new Bank();  //静态开放方法调用  public static Bank getBankInfo() {  return bank;  } }  //懒汉式单例模式:用的时候造,开辟空间 class School{ private School() { } private static School school = null; public static synchronized School getSchoolInfo() { if(school == null) { school = new  School(); } return school; } }复制代码

死锁

不同的线程分别占用对方需要的同步资源(锁)不放弃,都在等待对方放弃同步资源,形成线程的死锁。出现死锁后,不会出现异常,不会有提示信息,只是所有的线程都处于阻塞状态,无法继续。若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的进程称为死锁进程。

public class ThreadDeadlock {public static void main(String[] args) {StringBuffer s1 = new StringBuffer();StringBuffer s2 = new StringBuffer();//方式一new Thread() {@Overridepublic 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(){@Overridepublic 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();}}复制代码

线程通信

线程通信常用方法

  • wait() :定义在Object类, final ,线程进入阻塞状态,释放锁(和sleep不同);
  • notify() :定义在Object类, final ,唤醒正在等待锁的线程,进入就绪状态(有优先级按优先级,没有随机唤醒一个);
  • notifyAll() :定义在Object类, final ,唤醒所有正在等待锁的线程;
  • 这三种方法只能出现在 同步代码块或同步方法 里,且不能用在lock里,否则会报错 java.lang.IllegalMonitorStateException: current thread not owner 。
  • 这三个方法的调用者必须是同步代码块或同步方法中的 同步监视器 ,默认情况下是 this 或者 类.class(当前类的对象)

sleep()和wait()的异同?

同:一旦使用,均可使当前线程进入阻塞状态; 异:

  • 声明位置不同: sleep() 声明在Thread类中, wait() 声明在Object类中;
  • 调用要求不同: sleep() 可以使用在各种需要的地方,而 wait() 只能用在同步代码块或同步方法里;
  • sleep() 使用不释放锁,而 wait() 使用后会释放锁。

线程通信经典案例:生产者消费者问题

/* * 线程通信经典问题:生产者与消费者 * 生产者生产产品给店员,消费者从店员消费产品,店员一次只能固定最多持有一定数量的产品(比如:20件), * 当店员满额产品,生产者试图多生产产品时,店员会让生产者停一停; * 当店员产品不足,消费者试图继续消费时,店员会让消费者等一等; * */public class ThreadCommExe {public static void main(String[] args) {Clerk clerk = new Clerk();Productor productor = new Productor(clerk);Customer customer = new Customer(clerk);Thread threadp = new Thread(productor);Thread threadc = new Thread(customer);threadp.setName("生产者线程1");threadc.setName("消费者线程1");threadp.start();threadc.start();}}//店员实际上是共享资源,生产者与消费者两个线程需要判断店员的存货情况class Clerk {private int productCount = 0;public synchronized void prodeceRespone() {if(productCount<20) {productCount++;System.out.println(Thread.currentThread().getName()+"正在生产第"+productCount+"件产品");notify();}else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}public synchronized void customeRespone() {if(productCount>0) {System.out.println(Thread.currentThread().getName()+"正在消费第"+productCount+"件产品");productCount--;notify();}else {try {wait();} catch (InterruptedException e) {e.printStackTrace();}}}}//生产者线程class Productor implements Runnable {private Clerk clerk;public Productor(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {System.out.println("疯狂生产中......");while(true) {clerk.prodeceRespone();}}}//消费者线程class Customer implements Runnable {private Clerk clerk;public Customer(Clerk clerk) {this.clerk = clerk;}@Overridepublic void run() {System.out.println("疯狂消费中......");while(true){clerk.customeRespone();}}}复制代码

注意事项

单元测试需要特殊设置,不然不支持多线程验证。如果采用单元测试测试多线程,可能出现新线程执行不完整的情况,因为单元测试在main线程执行完之后自动 System.exit() 关闭java虚拟机,导致新线程无法继续执行

关注我! 需要架构学习资料,PDF文档最新面试题。可以私信!
关注我! 需要架构学习资料,PDF文档最新面试题。可以私信!
关注我! 需要架构学习资料,PDF文档最新面试题。可以私信!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值