浅谈Java多线程实现及同步互斥通讯
作者:jingxian 发布时间:2022-11-17 17:50:53
Java多线程深入理解本文主要从三个方面了解和掌握多线程:
1. 多线程的实现方式,通过继承Thread类和通过实现Runnable接口的方式以及异同点。
2. 多线程的同步与互斥中synchronized的使用方法。
3. 多线程的通讯中的notify(),notifyAll(),及wait(),的使用方法,以及简单的生成者和消费者的代码实现。
下面来具体的讲解Java中的多线程:
一:多线程的实现方式
通过继承Threa类来实现多线程主要分为以下三步:
第一步:继承 Thread,实现Thread类中的run()方法。
第二步:定义一个Thread子类的实例。
第三步:通过调用Thread类的start()方法来启动线程。
下面是简单的代码实现:
class myThread extends Thread{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread mythread=new myThread();
mythread.setName("子线程");
mythread.start();
}
}
上面线程中用到的几个方法:Thread.currentThraed().getName()方法得到当前线程的名字。mythread.setName(“子线程”);为mythread线程重命名为“子线程”。
通过实现Runnable 接口来实现多线程主要分为以下几步:
第一步:实现Runnable接口中的run()方法。生成一个Runnable的实例。
第二步:定义一个Thread类,并且将上面的Runnable实例传给Thread类的构造方法。
第三步:通过调用Thread类的start()方法来启动线程。
下面是简单的代码实现:
class myRunnable implements Runnable{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myRunnable myrunnable = new myRunnable();
Thread mythread=new Thread(myrunnable);
mythread.setName("子线程");
mythread.start();
}
}
既然通过继承Thread类,和实现Runnable方法都能实现多线程的运行那么两种方式到底有什么不同呢?下面通过一个买票的类子来看看到底二者有什么不同:
假设售票处总共有100张票,通过三个窗口来进行售票也就是说我们要开辟三个不同的线程来实现买票:首先看看通过Thread类来实现买票的方式:
class myThread extends Thread{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myThread m1=new myThread();
myThread m2=new myThread();
myThread m3=new myThread();
m1.setName("窗口1");
m2.setName("窗口2");
m3.setName("窗口3");
m1.start();
m2.start();
m3.start();
}
}
结果太长了我不展示了,可以看到原本三个窗口共同买100张票的,但是结果每个窗口都买了100张票,这个很好理解因为每个窗口都是一个独立的对象,都有自己的n=100。所以通过Thread类来实现买票功能是不可行的,其实用Thread方法来实现多线程,其中每个线程执行的代码并不是同一段代码。
下面来看看实现Runnable接口是怎样实现买票功能的:
class myRunnable implements Runnable{
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myRunnable myrunnable=new myRunnable();
Thread m1=new Thread(myrunnable);
Thread m2=new Thread(myrunnable);
Thread m3=new Thread(myrunnable);
m1.setName("窗口1");
m2.setName("窗口2");
m3.setName("窗口3");
m1.start();
m2.start();
m3.start();
}
}
可以看出上面三个线程公用的是同一个Runnable的子类,所以只是开辟三条线程来执行同一段runnable的代码。所以不会出现买300张票的情况。但是这个程序还是有问题的当讲到了后面的线程的同步与互斥后我们再来完善这段程序。
二:多线程的同步与互斥中synchronized和volatile的使用方法。
现在截取一段上面代码执行过程中出现的问题并且分析一下问题是如何产生的,再来通过synchronied来解决此问题。
:窗口2…1
:窗口1…1
:窗口3…1
:窗口1…2
:窗口2…2
:窗口1…3
:窗口3…2
:窗口1…4
:窗口2…3
:窗口1…5
:窗口3…3
:窗口1…6
:窗口2…4
上面代码的结果是通过实现Runnable接口产生的,上面窗口1,窗口2,窗口3,同时产生1是怎么产生的呢?
int n=100;
public void run() {
while (true) {
if (n > 0) {
System.out.println(":"Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
这是三个线程共同执行的同一段代码,上面结果产生的一种原因可能是当窗口2执行完输出i=1时此时虚拟机执行了窗口2,窗口2执行输出I,此时的i还未执行++,所以i的值还是1,此时虚拟机又把执行权给了窗口3,这个时候窗口3输出的i仍然是1,程序产生上面问题的主要原因是存在公共变量i,i的值在程序执行的过程中未保持同步。上面的for循环体应该单独执行完之后才能让其他的线程抢占虚拟机。Synchronized关键字就是用来实现保证线程在执行一段公共资源是不被其他线程抢占。
被synchronized修饰的代码块称作同步代码块,被synchronized修饰的方法称为同步方法。下面通过加入synchronized关键字来实现买票功能:
class myRunnable implements Runnable {
int n = 100;
public void run() {
while (true) {
synchronized (this) {
if (n > 0) {
if (n % 10 == 0) {
try {
Thread.currentThread().sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(":" + Thread.currentThread().getName()
+ "..." + n--);
} else {
break;
}
}
}
}
}
public class ThreadTest {
public static void main(String[] args) {
myRunnable myrunnable = new myRunnable();
Thread m1 = new Thread(myrunnable);
Thread m2 = new Thread(myrunnable);
Thread m3 = new Thread(myrunnable);
m1.setName("窗口1");
m2.setName("窗口2");
m3.setName("窗口3");
m1.start();
m2.start();
m3.start();
}
}
此时是可以正确的完成售票功能的。
上面代码中synchronized(this)中的this代表的是当前的对象,因为三个线程执行的都是myRunnable 的对象,所以三个线程公用的是同一个锁,其实这个this可以用任何的对象来代替,一般我们可以 String str=new String(“”);虽然str的值为空字符串,但是也是一个对象。Synchronized实现互斥的原理是每一个对象都有一个特定的变量值,当任何一个线程调用了synchronized想要进入公共资源区时,先判断该变量的值,若该变量的值为0则可以进入公共资源区,进程在进入公共资源区之前先把对象的中的该变量值变为1,出同步区后再将该变量的值变为0,从而实现线程互斥访问公共资源。
三:多线程的通讯中的notify(),notifyAll(),及wait(),的使用方法,以及简单的生成者和消费者的代码实现。
在讲解notify(),notifyAll(),wait()之前,先看看生产者和消费者问题:生产者生产面包,消费者消费面包,但是存放面包的容器有限,生产者一次最多只能生产20个面包,消费者每次在容器中拿一个面包。通过分析可以知道,当生产者生产了20个面包之后必须停下来,等容器里的面包数目小于20个时再继续生产,消费者看到容器里面面包个数为0时也必须停下来,等到有面包时才能消费。这时候就涉及到了生产者和消费者的通信。notify()是用于唤醒等待队列中的线程,wait()用于阻塞当前线程。Notify和wait()都必须用于synchronized修饰的同步代码块或同步方法中。
下面直接看生产者消费者代码。
class Consumer implements Runnable {
Clerk clerk;
Consumer(Clerk clerk) {
this.clerk = clerk;
}
public void run() {
while(true)
clerk.consumeProduct();
}
}
class Producter implements Runnable {
Clerk clerk;
Producter(Clerk clerk)
{
this.clerk = clerk;
}
public void run() {
while(true)
clerk.addProduct();
}
}
class Clerk {
int product ;
public synchronized void consumeProduct() {
while (true) {
if (product <= 0) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
product--;
notifyAll();
System.out.println("消费者消费了:" + product);
}
}
}
public synchronized void addProduct() {
if (product > 20) {
try {
wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
} else {
product++;
notifyAll();
System.out.println("生产者生产了:" + product);
}
}
}
public class Test {
public static void main(String[] args) {
Clerk clerk=new Clerk();
Consumer consumer=new Consumer(clerk);
Thread c=new Thread(consumer);
Producter producter=new Producter(clerk);
Thread p=new Thread(producter);
c.start();
p.start();
}
}
猜你喜欢
- Spring Boot1.为什么要使用 Spring Boot因为Spring, SpringMVC 需要使用的大量的配置文件 (xml文件
- 1. 简介Jpa 是一套ORM 的规范hibernate 不就是一个 ORM 框架也提供了对于 JPA 的实现JPA(Java Persis
- 本文实例讲述了Java二维数组简单定义与使用方法。分享给大家供大家参考,具体如下:Java的二维数组是先创建一个一维数组,然后该数组的元素再
- 包含默认敏感词过滤和自定义敏感词过滤。导入依赖<dependency> <groupId>com.git
- PROPAGATION_REQUIRED: 如果存在一个事务,则支持当前事务。如果没有事务则开启事务;PROPAGATION_REQUIRE
- 在PDF文档中,图层可以使部分内容选择性地被隐藏或显示。通过添加图层,我们可以将文本、图片、表格等元素精确定位于页面指定位置,并可将这些元素
- 前言本文的记录如何用CustomPaint、GestureDetector实现一个进度条控件。首先需要说明的是 flutter Materi
- 缘起经过前面三章的入门,我们大概了解了Mybatis的主线逻辑是什么样子的,在本章中,我们将正式进入Mybatis的源码海洋。Mybatis
- IDEA快速创建getter和setter方法找到generate我的是Mac,右击鼠标就可以打开,相信windows也不难。选择gette
- 一、Rxjava使用场景为了模拟实际场景,从wanandroid网站找了二个接口,如下:(对Wanandroid表示感谢!)public i
- springboot启动失败的问题springboot版本是1.3.0.M1,连接的mysql版本为8,用spring-boot-start
- 1.基本思路我现阶段的分页查询的实现是基于sql语句的。select * from user where id limit a, b构造出相
- 一、Shiro整体概述1.简介Apache Shiro是Java的一个安全框架,功能强大,使用简单,Shiro为开发人员提供了一个直观而全面
- IDEA安装后,前进 后退快捷按钮默认不在工具栏显示,需要手动将其添加到工具栏*按照图一选中Toolbar Run Actions ,点击右
- 本文的目的是要实现左右滑动的指引效果。那么什么是指引效果呢?现在的应用为了有更好的用户体验,一般会在应用开始显示一些指引帮助页面,使用户能更
- 这是之前软工课设我写的java访问mysql工具类,它经过了多轮的测试,应该能够适应大多数的操作需求。比之前大二写的更鲁棒,更易用。pack
- 每一个基于java的应用程序都有一个共同工作来展示给用户看到的内容作为工作的应用几个对象。当编写一个复杂的Java应用程序,应用程序类应该尽
- 1.pom.xml<?xml version="1.0" encoding="UTF-8"?&
- 前言各位精通CRUD的老司机,相信大家在工作中mybatis或者mybatisplus使用的肯定是比较多的,那么大家或多或少都应该对下面的行
- java 实现文件夹的拷贝实例代码 这里就直接上代码,